Merge branch 'master' into dashboard-master

This commit is contained in:
decabeza
2018-10-19 01:48:37 +02:00
1586 changed files with 126348 additions and 9884 deletions

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@
public/sitemap.xml
public/system/
/public/ckeditor_assets/

View File

@@ -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

View File

@@ -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).

View File

@@ -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,9 +38,18 @@ 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 . .

31
Gemfile
View File

@@ -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

View File

@@ -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)

View File

View File

@@ -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

View File

@@ -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";
};

View File

View File

@@ -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', ->

View File

@@ -12,3 +12,5 @@ App.LegislationAdmin =
else
$(this).val("")
$("#nested-question-options").on "cocoon:after-insert", ->
App.Globalize.refresh_visible_translations()

View File

@@ -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")

View File

@@ -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()

View File

@@ -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
// -----------

View File

@@ -878,11 +878,6 @@ footer {
h1 {
margin-top: $line-height;
img {
height: rem-calc(80);
width: rem-calc(80);
}
a {
color: #fff;
display: block;

View File

@@ -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 {

View File

@@ -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;
}
}
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -5,7 +5,9 @@ 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)
@proposal_notifications = ProposalNotification.only_hidden
.send(@current_filter)
.order(hidden_at: :desc)
.page(params[:page])
end

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -18,8 +18,13 @@ class Admin::StatsController < Admin::BaseController
@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

View File

@@ -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

View File

@@ -5,7 +5,9 @@ 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

View File

@@ -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,
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,
image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy])
*translation_params(Widget::Card),
image_attributes: image_attributes
)
end
def header_card?
params[:header_card].present?
end
def resource
Widget::Card.find(params[:id])
end
end

View File

@@ -3,18 +3,32 @@ module FlagActions
def flag
Flag.flag(current_user, flaggable)
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)
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
if resource_model.to_s == 'Budget::Investment'
instance_variable_get("@investment")
else
instance_variable_get("@#{resource_model.to_s.downcase}")
end
end
end

View File

@@ -3,8 +3,12 @@ module Polymorphic
private
def resource
if resource_model.to_s == 'Budget::Investment'
@resource ||= instance_variable_get("@investment")
else
@resource ||= instance_variable_get("@#{resource_name}")
end
end
def resource_name
@resource_name ||= resource_model.to_s.downcase

View File

@@ -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

View File

@@ -1,8 +0,0 @@
class GuidesController < ApplicationController
skip_authorization_check
def new
end
end

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }

View File

@@ -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?

View File

@@ -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

View File

@@ -1,19 +1,31 @@
module AdminHelper
def side_menu
if namespace == 'moderation/budgets'
render "/moderation/menu"
else
render "/#{namespace}/menu"
end
end
def namespaced_root_path
if namespace == 'moderation/budgets'
"/moderation"
else
"/#{namespace}"
end
end
def namespaced_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?

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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|

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -0,0 +1,4 @@
class Ckeditor::Asset < ActiveRecord::Base
include Ckeditor::Orm::ActiveRecord::AssetBase
include Ckeditor::Backend::Paperclip
end

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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? }

View File

@@ -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,10 +26,19 @@ 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)
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
status == 'draft' ? "#{title} *" : title
end

View File

@@ -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',

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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) }

View File

@@ -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 }

View File

@@ -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'

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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 }

View File

@@ -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]

View File

@@ -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}"

View File

@@ -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

View File

@@ -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

View File

@@ -7,7 +7,7 @@
<strong><%= t("admin.menu.title_polls") %></strong>
</a>
<ul id="polls_menu" <%= "class=is-active" if menu_polls? || controller.class.parent == Admin::Poll::Questions::Answers %>>
<li <%= "class=is-active" if controller_name == "polls" && action_name != "booth_assignments" %>>
<li <%= "class=is-active" if %w(polls recounts results).include?(controller_name) %>>
<%= link_to t("admin.menu.polls"), admin_polls_path %>
</li>
@@ -25,7 +25,7 @@
<strong><%= t("admin.menu.title_booths") %></strong>
</a>
<ul id="booths_menu" <%= "class=is-active" if menu_booths? || controller_name == "polls" && action_name == "booth_assignments" %>>
<li <%= "class=is-active" if controller_name == "officers" %>>
<li <%= "class=is-active" if %w(officers officer_assignments).include?(controller_name) %>>
<%= link_to t("admin.menu.poll_officers"), admin_officers_path %>
</li>
@@ -35,12 +35,12 @@
</li>
<li <%= "class=is-active" if (controller_name == "polls" && action_name == "booth_assignments") ||
(controller_name == "booth_assignments" && action_name == "manage") %>>
controller_name == "booth_assignments" %>>
<%= link_to t("admin.menu.poll_booth_assignments"), booth_assignments_admin_polls_path %>
</li>
<li <%= "class=is-active" if %w(shifts booths).include?(controller_name) &&
action_name == "available" %>>
%w(available new).include?(action_name) %>>
<%= link_to t("admin.menu.poll_shifts"), available_admin_booths_path %>
</li>
</ul>
@@ -79,21 +79,24 @@
</li>
<% end %>
<% newsletters_notifications_sections = %w(newsletters emails_download admin_notifications) %>
<% newsletters_menu_active = newsletters_notifications_sections.include?(controller_name) %>
<li class="section-title" <%= "class=active" if newsletters_menu_active %>>
<% messages_sections = %w(newsletters emails_download admin_notifications system_emails) %>
<% messages_menu_active = messages_sections.include?(controller_name) %>
<li class="section-title" <%= "class=is-active" if messages_menu_active %>>
<a href="#">
<span class="icon-zip"></span>
<strong><%= t("admin.menu.newsletters_and_notifications") %></strong>
<strong><%= t("admin.menu.messaging_users") %></strong>
</a>
<ul id="newsletters_and_notifications_menu" <%= "class=is-active" if newsletters_menu_active %>>
<li <%= "class=active" if controller_name == "newsletters" %>>
<ul id="messaging_users_menu" <%= "class=is-active" if messages_menu_active %>>
<li <%= "class=is-active" if controller_name == "newsletters" %>>
<%= link_to t("admin.menu.newsletters"), admin_newsletters_path %>
</li>
<li <%= "class=active" if controller_name == "admin_notifications" %>>
<li <%= "class=is-active" if controller_name == "admin_notifications" %>>
<%= link_to t("admin.menu.admin_notifications"), admin_admin_notifications_path %>
</li>
<li <%= "class=active" if controller_name == "emails_download" %>>
<li <%= "class=is-active" if controller_name == "system_emails" %>>
<%= link_to t("admin.menu.system_emails"), admin_system_emails_path %>
</li>
<li <%= "class=is-active" if controller_name == "emails_download" %>>
<%= link_to t("admin.menu.emails_download"), admin_emails_download_index_path %>
</li>
</ul>
@@ -107,7 +110,7 @@
<ul <%= "class=is-active" if menu_customization? &&
controller.class.parent != Admin::Poll::Questions::Answers %>>
<li <%= "class=active" if menu_homepage? %>>
<li <%= "class=is-active" if menu_homepage? %>>
<%= link_to t("admin.menu.site_customization.homepage"), admin_homepage_path %>
</li>
@@ -118,6 +121,10 @@
<li <%= "class=is-active" if controller_name == "banners" %>>
<%= link_to t("admin.menu.banner"), admin_banners_path %>
</li>
<li <%= "class=is-active" if controller_name == "information_texts" %>>
<%= link_to t("admin.menu.site_customization.information_texts"), admin_site_customization_information_texts_path %>
</li>
</ul>
</li>
@@ -148,6 +155,12 @@
</li>
<% end %>
<% if feature?(:budgets) %>
<li <%= "class=is-active" if controller_name == "hidden_budget_investments" %>>
<%= link_to t("admin.menu.hidden_budget_investments"), admin_hidden_budget_investments_path %>
</li>
<% end %>
<li <%= "class=is-active" if controller_name == "comments" %>>
<%= link_to t("admin.menu.hidden_comments"), admin_comments_path %>
</li>

View File

@@ -33,12 +33,16 @@
<%= activity.actionable.username %> (<%= activity.actionable.email %>)
<% when "Comment" %>
<%= activity.actionable.body %>
<% when "Newsletter" %>
<strong><%= activity.actionable.subject %></strong>
<% when "ProposalNotification" %>
<strong><%= activity.actionable.title %></strong>
<br>
<%= activity.actionable.body %>
<% else %>
<strong><%= activity.actionable.title %></strong>
<br>
<div class="proposal-description">
<%= activity.actionable.description %>
</div>
<% end %>
<td class="align-top">
<%= activity.user.name %> (<%= activity.user.email %>)

View File

@@ -1,13 +1,19 @@
<%= form_for [:admin, @admin_notification] do |f| %>
<%= render "admin/shared/globalize_locales", resource: @admin_notification %>
<%= translatable_form_for [:admin, @admin_notification] do |f| %>
<%= render 'shared/errors', resource: @admin_notification %>
<%= f.select :segment_recipient, options_for_select(user_segments_options,
@admin_notification[:segment_recipient]) %>
<%= f.text_field :title %>
<%= f.translatable_text_field :title %>
<%= f.text_field :link %>
<%= f.text_area :body %>
<%= f.translatable_text_area :body %>
<div class="margin-top">
<%= f.submit class: "button success" %>
<%= f.submit t("admin.admin_notifications.#{admin_submit_action(@admin_notification)}.submit_button"),
class: "button success" %>
</div>
<% end %>

View File

@@ -9,7 +9,7 @@
<th><%= t("admin.admin_notifications.index.title") %></th>
<th><%= t("admin.admin_notifications.index.segment_recipient") %></th>
<th><%= t("admin.admin_notifications.index.sent") %></th>
<th class="small-5 text-right"><%= t("admin.admin_notifications.index.actions") %></th>
<th class="small-5"><%= t("admin.admin_notifications.index.actions") %></th>
</tr>
</thead>
<tbody>
@@ -28,21 +28,29 @@
<%= l admin_notification.sent_at.to_date %>
<% end %>
</td>
<td class="text-right">
<td>
<% if admin_notification.draft? %>
<div class="small-4 column">
<%= link_to t("admin.admin_notifications.index.edit"),
edit_admin_admin_notification_path(admin_notification),
method: :get, class: "button hollow" %>
method: :get, class: "button expanded hollow" %>
</div>
<div class="small-4 column">
<%= link_to t("admin.admin_notifications.index.delete"),
admin_admin_notification_path(admin_notification),
method: :delete, class: "button hollow alert" %>
method: :delete, class: "button expanded hollow alert" %>
</div>
<div class="small-4 column">
<%= link_to t("admin.admin_notifications.index.preview"),
admin_admin_notification_path(admin_notification),
class: "button" %>
class: "button expanded" %>
</div>
<% else %>
<div class="small-4 column">
<%= link_to t("admin.admin_notifications.index.view"),
admin_admin_notification_path(admin_notification),
class: "button" %>
class: "button expanded" %>
</div>
<% end %>
</td>
</tr>

View File

@@ -2,9 +2,7 @@
<h2><%= t("admin.admin_notifications.show.section_title") %></h2>
<div class="small-12 column">
<div class="callout highlight">
<div class="row">
<div class="small-12 column callout highlight">
<div class="small-12 medium-6 column">
<strong><%= t("admin.admin_notifications.show.sent_at") %></strong><br>
<% if @admin_notification.draft? %>
@@ -13,23 +11,22 @@
<%= l(@admin_notification.sent_at.to_date) %>
<% end %>
</div>
<div class="small-12 medium-6 column">
<strong><%= t("admin.admin_notifications.show.title") %></strong><br>
<%= @admin_notification.title %>
</div>
</div>
<div class="row">
<div class="small-12 medium-6 column">
<strong><%= t("admin.admin_notifications.show.body") %></strong><br>
<%= @admin_notification.body %>
</div>
<div class="small-12 medium-6 column">
<strong><%= t("admin.admin_notifications.show.link") %></strong><br>
<%= @admin_notification.link %>
</div>
</div>
<div class="row">
<div class="small-12 column">
<strong><%= t("admin.admin_notifications.show.segment_recipient") %></strong><br>
<%= segment_name(@admin_notification.segment_recipient) %>
@@ -42,7 +39,6 @@
<% end %>
</div>
</div>
</div>
<p class="help-text" id="phase-description-help-text">
<% if @admin_notification.draft? %>
@@ -65,13 +61,15 @@
</ul>
</div>
<hr>
</div>
<% if @admin_notification.draft? && @admin_notification.valid_segment_recipient? %>
<div class="small-12 medium-6 large-3 column end">
<%= link_to t("admin.admin_notifications.show.send"),
deliver_admin_admin_notification_path(@admin_notification),
"data-alert": t("admin.admin_notifications.show.send_alert",
n: @admin_notification.list_of_recipients_count),
method: :post,
id: "js-send-admin_notification-alert",
class: "button success" %>
class: "button success expanded" %>
</div>
<% end %>

Some files were not shown because too many files have changed in this diff Show More