diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000000..565cbcd6f6
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,48 @@
+# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
+# Remove commits converting files to 2-spaces
+e9074d60ea28f9fb98e82be4fe15d0de5d637eff
+3fbd060cea72f717df8b0651a69c79f4fdd509f8
+81c0cf2d2eb2dcccbdbad9dc8937793dc244424b
+3fb4bade8546052f69f68311ff3b5eb530bdce06
+137cb672f1532f916239969756f2392337f1c8df
+4eee188e093bc8bf3782ec4bd14c3529b0b01586
+e58feafc6ab8a9b6d51946c55cde6adae9116568
+390c000ab8f3dd081db7feced594a3798cdf2147
+83429e1295b90e684cd67108737d6dabf80ae883
+c58f6013d9c374664dffbfa645fb8fed60d335d8
+5dec2b34f0512dadc470a8d006e46d260881a2ca
+423a748e925c0e196a540197594f289f8a948648
+f2dc73ce4e676a390205bafd6732a217f3de3c89
+0b498ff3b0fe95496d90fd63be718e895a24537c
+ec07eae1d3e4ce65cad2d5cbe5221a89e06e45af
+423ef61c3df718df6bb2a9b0dbf5d5c7180230cd
+9202518ad9018182680fca33902d63a825c3fe16
+cefe07dee896adb7aa542601b3f7cc75f990df6c
+953b9e225b2029d75d8f864d7a038aeb0860b32d
+7fbd7299e238e41cc341bc3e6d65ed757d4e870a
+9e556bcfa67c6cb4843c5b5398c96f84c4639708
+0da7b93c5f6e3ebff376e5b5dd01c9f03ec1c2c8
+19a3591d6246dc3af866982a1fe8d6d609d8514b
+bb60b2632117e692f933810dbf321f22d27604fd
+38da000ae7b6d11c620f28866715922ef980360b
+92563edca5d7552235c9dd069aabc375be9c5b74
+872dd47ca574ed8b06597e49be4d21712e8a6a0b
+b5caf1f17165c597b00020805c909b1eb8907e97
+844453b95c497b810fc27005ce376a90ea754fa9
+c85fd8205c2f5eab598de9a14d1acda4f3380992
+b805700e3e632be1dcbea4d96a667722129d2c2b
+30681a0a22b49dfff2af6139472757dce3d2c040
+cf020f3bce0dc6525b6a8a45258ddb55448be17c
+db492303b1d2a22de4be039cde0961c48761d7ce
+f07280eeb98f6058b75a3ca94afb73254c7963c7
+d35e92ba521b1183bf644e03aad8bbea721d9894
+ce8b44f8669b276767e03965865e4b8411e0abd3
+4be25b1ca7754f8b2ad2aef8b1010911d1705dc6
+8d0a624458733bb6b84c9185e3cc6dd087fbe087
+b0aac8fc3d038553ca13fdd9cae3a85ae6a2a6d8
+b80cc391530136d6178f0539100aef7c78a63a63
+da75289f3414b498f0708b24bbdfc09864fe2da5
+29aeb19078f68e72ac781fbe27607a744d2b712a
+6691f97656f1aff03b3da8cdf42c94eb05d9fc07
+0b4059b3f3a4898526b7c31d5f4db6429b9a26ef
+f2c538c1a25d91de33dd583a5ee34e433ffaa8a6
diff --git a/.github/git-io-urls.md b/.github/git-io-urls.md
new file mode 100644
index 0000000000..6eccc6e1af
--- /dev/null
+++ b/.github/git-io-urls.md
@@ -0,0 +1,26 @@
+# git.io URL shortener URLs
+
+GitHub [announced it was shutting down git.io](https://github.blog/changelog/2022-04-25-git-io-deprecation)
+on Friday, April 29, 2022 and as a consequence all links on
+[git.io](https://git.io) will stop redirecting.
+
+We've replaced these URLs in the source code and GitHub issues where possible.
+
+## Commit Messages
+
+Commit messages can't be changed, so this table acts as a manual lookup for
+git.io URLs known to be included in commits in Alaveteli.
+
+```csv
+https://git.io/JvMcI,https://github.com/rails/rails/blob/v5.1.7/actionview/lib/action_view/helpers/javascript_helper.rb#L25-L32
+https://git.io/JvMcL,https://github.com/rails/rails/blob/v5.2.4.1/actionview/lib/action_view/helpers/javascript_helper.rb#L27-L34
+https://git.io/JvMcm,https://github.com/rails/rails/commit/b5aeef5703dab7da9ebb47cc20e4c8b64f7f5866
+https://git.io/hR7f,https://github.com/alexdunae/holidays/blob/master/CHANGELOG.md#120
+https://git.io/v0QN2,https://github.com/holidays/holidays/blob/master/CHANGELOG.md#220
+https://git.io/v6otV,https://github.com/svenfuchs/routing-filter/issues/47#issue-14760397
+https://git.io/vKpwO,https://github.com/rails/rails/commit/5f189f41258b83d49012ec5a0678d827327e7543
+https://git.io/vozPG,https://github.com/mikel/mail/blob/a217776355befa3d8191c4bd3c1fad54e0e27471/lib/mail/version_specific/ruby_1_9.rb#L89
+https://git.io/vun4e,https://github.com/travis-ci/travis-ci/issues/1242#issuecomment-21660547
+https://git.io/vvv93,https://github.com/mysociety/alaveteli/blob/8c72a2590a6c0a5f21491b96f44eeb8da53663bd/spec/integration/admin_public_body_edit_spec.rb#L48
+https://git.io/vvv9t,https://github.com/mysociety/alaveteli/blob/8c72a2590a6c0a5f21491b96f44eeb8da53663bd/spec/integration/admin_public_body_edit_spec.rb#L42-L70
+```
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 84b17f151d..51486cb02c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,23 +5,31 @@ on:
branches: [master, develop]
pull_request:
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
jobs:
rspec:
- name: Ruby ${{ matrix.ruby }} / ${{ matrix.gemfile || 'Gemfile' }}
+ name: Ruby ${{ matrix.ruby }} / PostgreSQL ${{ matrix.postgres }} / ${{ matrix.gemfile || 'Gemfile' }}
runs-on: ubuntu-20.04
+ permissions:
+ checks: write # for coverallsapp/github-action to create new checks
+
strategy:
fail-fast: false
matrix:
include:
- - { ruby: 2.5 }
- - { ruby: 2.6 }
- - { ruby: 2.7 }
- - { ruby: 2.7, gemfile: 'Gemfile.rails_next' }
+ - { ruby: 2.7, postgres: 13.5 }
+ - { ruby: 2.7, postgres: 13.5, gemfile: 'Gemfile.rails_next' }
services:
postgres:
- image: fixmystreet/postgres:latest
+ image: fixmystreet/postgres:${{ matrix.postgres }}
env:
POSTGRES_PASSWORD: postgres
ports:
@@ -42,6 +50,7 @@ jobs:
uses: actions/checkout@v2
with:
submodules: true
+ fetch-depth: 0
- name: Install packages
env:
@@ -55,7 +64,6 @@ jobs:
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- bundler: ${{ matrix.bundler || '2.1.4' }}
bundler-cache: true
- name: Setup database
@@ -66,9 +74,13 @@ jobs:
CREATE DATABASE alaveteli_test TEMPLATE template_utf8;
EOSQL
- - name: Install theme
+ - name: Configure application and storage
run: |
cp config/general.yml-example config/general.yml
+ cp config/storage.yml-example config/storage.yml
+
+ - name: Install theme
+ run: |
bundle exec rake themes:install
- name: Migrate database
@@ -88,6 +100,8 @@ jobs:
parallel: true
coveralls:
+ permissions:
+ checks: write
name: Coveralls
needs: rspec
runs-on: ubuntu-20.04
diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml
index f255a60310..3a3757a775 100644
--- a/.github/workflows/rubocop.yml
+++ b/.github/workflows/rubocop.yml
@@ -2,6 +2,9 @@ name: RuboCop
on: [pull_request]
+permissions:
+ contents: read
+
jobs:
build:
runs-on: ubuntu-20.04
@@ -11,7 +14,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
- ruby-version: 2.6
+ ruby-version: 2.7
- name: Run RuboCop linter
uses: reviewdog/action-rubocop@v1
diff --git a/.gitignore b/.gitignore
index 82cd9588ad..8886015edc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@
/config/memcached.yml
/config/newrelic.yml
/config/rails_env.rb
+/config/storage.yml
/config/user_spam_scorer.yml
/config/xapian.yml
/coverage/
@@ -39,6 +40,7 @@
/public/foi-user-use.png
/public/google*.html
/public/wordpress
+/storage/
/tmp/
/vendor/bundle/
/vendor/data/
diff --git a/.ruby-style.yml b/.ruby-style.yml
index 639c80ca83..bcb5015442 100644
--- a/.ruby-style.yml
+++ b/.ruby-style.yml
@@ -4,7 +4,7 @@ require:
- rubocop-rails
AllCops:
- TargetRubyVersion: 2.5
+ TargetRubyVersion: 2.7
RubyInterpreters:
- ruby
- rake
@@ -53,12 +53,21 @@ Bundler/OrderedGems:
Gemspec/DateAssignment:
Enabled: false
+Gemspec/DependencyVersion:
+ Enabled: false
+
+Gemspec/DeprecatedAttributeAssignment:
+ Enabled: false
+
Gemspec/DuplicatedAssignment:
Enabled: false
Gemspec/OrderedDependencies:
Enabled: false
+Gemspec/RequireMFA:
+ Enabled: false
+
Gemspec/RequiredRubyVersion:
Enabled: false
@@ -237,6 +246,14 @@ Layout/LineLength:
- "^\\s*context\\s+.*do$"
- "^\\s*describe\\s+.*do$"
- "^\\s*class\\s+[A-Z].*<.*"
+ Exclude:
+ - bin/setup
+ - config/environments/development.rb
+ - config/environments/production.rb
+ - config/environments/test.rb
+ - config/initializers/backtrace_silencers.rb
+ - config/initializers/content_security_policy.rb
+ - config/initializers/new_framework_defaults*
Layout/MultilineArrayBraceLayout:
Enabled: true
@@ -621,6 +638,9 @@ Lint/RedundantWithIndex:
Lint/RedundantWithObject:
Enabled: false
+Lint/RefinementImportMethods:
+ Enabled: false
+
Lint/RegexpAsCondition:
Enabled: false
@@ -729,10 +749,10 @@ Lint/UselessAccessModifier:
Lint/UselessAssignment:
Enabled: false
-Lint/UselessElseWithoutRescue:
+Lint/UselessMethodDefinition:
Enabled: false
-Lint/UselessMethodDefinition:
+Lint/UselessRuby2Keywords:
Enabled: false
Lint/UselessSetterCall:
@@ -789,6 +809,9 @@ Naming/AsciiIdentifiers:
Naming/BinaryOperatorParameterName:
Enabled: false
+Naming/BlockForwarding:
+ Enabled: false
+
Naming/BlockParameterName:
Enabled: false
@@ -959,6 +982,9 @@ Performance/Squeeze:
Performance/StartWith:
Enabled: false
+Performance/StringIdentifierArgument:
+ Enabled: false
+
Performance/StringInclude:
Enabled: false
@@ -979,6 +1005,9 @@ Performance/UriDefaultParser:
#################### Rails ####################
+Rails/ActionControllerTestCase:
+ Enabled: false
+
Rails/ActionFilter:
Enabled: false
@@ -1030,6 +1059,9 @@ Rails/Blank:
Rails/BulkChangeTable:
Enabled: false
+Rails/CompactBlank:
+ Enabled: false
+
Rails/ContentTag:
Enabled: false
@@ -1048,6 +1080,21 @@ Rails/Delegate:
Rails/DelegateAllowBlank:
Enabled: false
+Rails/DeprecatedActiveModelErrorsMethods:
+ Enabled: false
+
+Rails/DotSeparatedKeys:
+ Enabled: false
+
+Rails/DuplicateAssociation:
+ Enabled: false
+
+Rails/DuplicateScope:
+ Enabled: false
+
+Rails/DurationArithmetic:
+ Enabled: false
+
Rails/DynamicFindBy:
Enabled: false
@@ -1099,9 +1146,15 @@ Rails/HttpPositionalArguments:
Rails/HttpStatus:
Enabled: false
+Rails/I18nLazyLookup:
+ Enabled: false
+
Rails/I18nLocaleAssignment:
Enabled: false
+Rails/I18nLocaleTexts:
+ Enabled: false
+
Rails/IgnoredSkipActionFilterOption:
Enabled: false
@@ -1129,6 +1182,9 @@ Rails/MailerName:
Rails/MatchRoute:
Enabled: false
+Rails/MigrationClassName:
+ Enabled: false
+
Rails/NegateInclude:
Enabled: false
@@ -1177,6 +1233,9 @@ Rails/RedundantAllowNil:
Rails/RedundantForeignKey:
Enabled: false
+Rails/RedundantPresenceValidationOnBelongsTo:
+ Enabled: false
+
Rails/RedundantReceiverInWithOptions:
Enabled: false
@@ -1210,6 +1269,12 @@ Rails/ReversibleMigration:
Rails/ReversibleMigrationMethodDefinition:
Enabled: false
+Rails/RootJoinChain:
+ Enabled: false
+
+Rails/RootPublicPath:
+ Enabled: false
+
Rails/SafeNavigation:
Enabled: false
@@ -1219,6 +1284,9 @@ Rails/SafeNavigationWithBlank:
Rails/SaveBang:
Enabled: false
+Rails/SchemaComment:
+ Enabled: false
+
Rails/ScopeArgs:
Enabled: false
@@ -1231,12 +1299,24 @@ Rails/SkipsModelValidations:
Rails/SquishedSQLHeredocs:
Enabled: false
+Rails/StripHeredoc:
+ Enabled: false
+
+Rails/TableNameAssignment:
+ Enabled: false
+
Rails/TimeZone:
Enabled: false
Rails/TimeZoneAssignment:
Enabled: false
+Rails/ToFormattedS:
+ Enabled: false
+
+Rails/TransactionExitStatement:
+ Enabled: false
+
Rails/UniqBeforePluck:
Enabled: false
@@ -1263,6 +1343,9 @@ Rails/WhereNot:
#################### Security ####################
+Security/CompoundHash:
+ Enabled: false
+
Security/Eval:
Enabled: false
@@ -1454,6 +1537,9 @@ Style/EndBlock:
Style/EndlessMethod:
Enabled: false
+Style/EnvHome:
+ Enabled: false
+
Style/EvalWithLocation:
Enabled: false
@@ -1469,6 +1555,15 @@ Style/ExplicitBlockArgument:
Style/ExponentialNotation:
Enabled: false
+Style/FetchEnvVar:
+ Enabled: false
+
+Style/FileRead:
+ Enabled: false
+
+Style/FileWrite:
+ Enabled: false
+
Style/FloatDivision:
Enabled: false
@@ -1569,6 +1664,12 @@ Style/LambdaCall:
Style/LineEndConcatenation:
Enabled: true
+Style/MapCompactWithConditionalBlock:
+ Enabled: false
+
+Style/MapToHash:
+ Enabled: false
+
Style/MethodCallWithArgsParentheses:
Enabled: false
@@ -1641,6 +1742,9 @@ Style/NegatedUnless:
Style/NegatedWhile:
Enabled: true
+Style/NestedFileDirname:
+ Enabled: false
+
Style/NestedModifier:
Enabled: true
@@ -1680,9 +1784,15 @@ Style/NumericLiterals:
Style/NumericPredicate:
Enabled: false
+Style/ObjectThen:
+ Enabled: false
+
Style/OneLineConditional:
Enabled: false
+Style/OpenStructUse:
+ Enabled: false
+
Style/OptionHash:
Enabled: false
@@ -1755,6 +1865,9 @@ Style/RedundantFileExtensionInRequire:
Style/RedundantFreeze:
Enabled: true
+Style/RedundantInitialize:
+ Enabled: false
+
Style/RedundantInterpolation:
Enabled: true
@@ -1860,6 +1973,18 @@ Style/StringHashKeys:
Style/StringLiterals:
Enabled: false
+ EnforcedStyle: single_quotes
+ Exclude:
+ - bin/rails
+ - bin/rake
+ - bin/setup
+ - config.ru
+ - config/application.rb
+ - config/boot.rb
+ - config/environment.rb
+ - config/environments/development.rb
+ - config/environments/production.rb
+ - config/environments/test.rb
Style/StringLiteralsInInterpolation:
Enabled: false
diff --git a/.ruby-version.example b/.ruby-version.example
index ecd7ee50cb..a4dd9dba4f 100644
--- a/.ruby-version.example
+++ b/.ruby-version.example
@@ -1 +1 @@
-2.5.8
+2.7.4
diff --git a/.vagrant.yml.example b/.vagrant.yml.example
index 368b96250a..2e9751df20 100644
--- a/.vagrant.yml.example
+++ b/.vagrant.yml.example
@@ -4,7 +4,7 @@ ip: 10.10.10.30
public_network: false
memory: 1536
themes_dir: ../alaveteli-themes
-os: stretch64
+os: bullseye64
use_nfs: false
show_settings: false
# By default CPU count is calculated dynamically
diff --git a/Gemfile b/Gemfile
index a5f359a896..4fec996d65 100644
--- a/Gemfile
+++ b/Gemfile
@@ -84,43 +84,51 @@ def rails_upgrade?
%w[1 true].include?(ENV['RAILS_UPGRADE'])
end
-gem 'rails', rails_upgrade? ? '~> 6.1.4' : '~> 6.0.3'
+if rails_upgrade?
+ gem 'rails', '~> 7.0.3'
+else
+ gem 'rails', '~> 6.1.6'
+end
-gem 'pg', '~> 1.2.3'
+gem 'pg', '~> 1.4.1'
# New gem releases aren't being done. master is newer and supports Rails > 3.0
gem 'acts_as_versioned', :git => 'https://github.com/technoweenie/acts_as_versioned.git', :ref => '63b1fc8529d028'
gem 'active_model_otp'
-gem 'bcrypt', '~> 3.1.16'
-gem 'cancancan', '~> 3.3.0'
+gem 'bcrypt', '~> 3.1.18'
+gem 'cancancan', '~> 3.4.0'
gem 'charlock_holmes', '~> 0.7.7'
-gem 'dalli', '~> 3.0.4'
-gem 'exception_notification', '~> 4.4.3'
+gem 'dalli', '~> 3.2.2'
+gem 'exception_notification', '~> 4.5.0'
gem 'fancybox-rails', '~> 0.3.0'
gem 'gnuplot', '~> 2.6.0'
gem 'htmlentities', '~> 4.3.0'
gem 'icalendar', '~> 2.7.1'
-gem 'jquery-rails', '~> 4.4.0'
+gem 'jquery-rails', '~> 4.5.0'
gem 'jquery-ui-rails', '~> 6.0.0'
-gem 'json', '~> 2.6.1'
-gem 'holidays', '~> 8.4.1'
+gem 'json', '~> 2.6.2'
+gem 'holidays', '~> 8.5.0'
gem 'iso_country_codes', '~> 0.7.8'
gem 'mail', '~> 2.7.1'
gem 'maxmind-db', '~> 1.0.0'
gem 'mahoro', '~> 0.5'
-gem 'nokogiri', '~> 1.12.5'
+gem 'nokogiri', '~> 1.13.6'
gem 'open4', '~> 1.3.0'
gem 'rack', '~> 2.2.3'
gem 'rack-utf8_sanitizer', '~> 1.7.0'
-gem 'recaptcha', '~> 5.8.1', require: 'recaptcha/rails'
+gem 'recaptcha', '~> 5.10.0', require: 'recaptcha/rails'
gem 'mini_magick', '~> 4.11.0'
gem 'rolify', '~> 5.3.0'
gem 'ruby-msg', '~> 1.5.0', :git => 'https://github.com/mysociety/ruby-msg.git', :branch => 'ascii-encoding'
gem 'rubyzip', '~> 2.3.2'
gem 'secure_headers', '~> 6.3.3'
gem 'statistics2', '~> 0.54'
-gem 'strip_attributes', :git => 'https://github.com/mysociety/strip_attributes.git', :branch => 'globalize3-rails5.2'
-gem 'stripe', '~> 5.39.0'
+if rails_upgrade?
+ gem 'strip_attributes', :git => 'https://github.com/mysociety/strip_attributes.git', :branch => 'globalize3-rails7'
+else
+ gem 'strip_attributes', :git => 'https://github.com/mysociety/strip_attributes.git', :branch => 'globalize3-rails5.2'
+end
+gem 'stripe', '~> 5.55.0'
gem 'syslog_protocol', '~> 0.9.0'
gem 'thin', '~> 1.8.1'
gem 'vpim', '~> 13.11.11'
@@ -133,14 +141,14 @@ gem 'zip_tricks', '~> 5.6.0'
gem 'gender_detector', '~> 2.0.0'
# Gems related to internationalisation
-gem 'i18n', '~> 1.8.11'
-gem 'rails-i18n', '~> 6.0.0'
+gem 'i18n', '~> 1.10.0'
+gem 'rails-i18n', '~> 7.0.3'
gem 'gettext_i18n_rails', '~> 1.8.1'
- gem 'fast_gettext', '~> 2.1.0'
-gem 'gettext', '~> 3.4.1'
-gem 'globalize', rails_upgrade? ? '~> 6.0.0' : '~> 5.3.0'
+ gem 'fast_gettext', '~> 2.2.0'
+gem 'gettext', '~> 3.4.3'
+gem 'globalize', '~> 6.2.1'
gem 'locale', '~> 2.1.3'
-gem 'routing-filter', rails_upgrade? ? '~> 0.7.0' : '~> 0.6.2'
+gem 'routing-filter', '~> 0.7.0'
gem 'unicode', '~> 0.4.4'
gem 'unidecoder', '~> 1.1.0'
gem 'money', '~> 6.16.0'
@@ -150,43 +158,53 @@ gem 'mime-types', '< 3.0.0', require: false
# Assets
gem 'bootstrap-sass', '~> 2.3.2.2'
-gem 'mini_racer', '~> 0.4.0'
+gem 'mini_racer', '~> 0.6.2'
gem 'sass-rails', '~> 5.0.8'
gem 'uglifier', '~> 4.2.0'
# Feature flags
gem 'alaveteli_features', :path => 'gems/alaveteli_features'
+# Storage backends
+gem 'aws-sdk-s3', require: false
+gem 'azure-storage', require: false
+gem 'google-cloud-storage', '~> 1.36', require: false
+
+if rails_upgrade? && RUBY_VERSION < '3.1'
+ gem 'net-http', '0.1.1'
+ gem 'uri', '0.10.0'
+end
+
group :test do
gem 'fivemat', '~> 1.3.7'
gem 'webmock', '~> 3.14.0'
gem 'simplecov', '~> 0.17.1'
gem 'simplecov-lcov', '~> 0.7.0'
- gem 'capybara', '~> 3.35.3'
+ gem 'capybara', '~> 3.37.1'
gem 'stripe-ruby-mock', git: 'https://github.com/stripe-ruby-mock/stripe-ruby-mock',
ref: '2c925fd'
gem('rails-controller-testing')
end
group :test, :development do
- gem 'bullet', '~> 6.1.5'
+ gem 'bullet', '~> 7.0.2'
gem 'factory_bot_rails', '~> 6.2.0'
gem 'oink', '~> 0.10.1'
gem 'rspec-activemodel-mocks', '~> 1.1.0'
- gem 'rspec-rails', '~> 5.0.2'
+ gem 'rspec-rails', '~> 5.1.2'
gem 'pry', '~> 0.13.0'
gem 'pry-byebug', '~> 3.9.0'
end
group :development do
- gem 'annotate', '< 3.1.1'
+ gem 'annotate', '< 3.2.1'
gem 'capistrano', '~> 2.15.0', '< 3.0.0'
gem 'net-ssh', '~> 6.1.0'
gem 'net-ssh-gateway', '>= 1.1.0', '< 3.0.0'
gem 'launchy', '< 2.5.0'
- gem 'listen', '>= 3.0.5', '< 3.7.1'
+ gem 'listen', '>= 3.0.5', '< 3.7.2'
gem 'web-console', '>= 3.3.0'
- gem 'rubocop', '~> 1.22.3', require: false
+ gem 'rubocop', '~> 1.30.1', require: false
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 4473e2e871..315d90ea50 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -40,43 +40,45 @@ PATH
flipper (~> 0.10)
flipper-active_record (~> 0.10)
mime-types (< 3.0.0)
- rails (~> 6.0.3)
+ rails (~> 6.1.4)
GEM
remote: https://rubygems.org/
specs:
- actioncable (6.0.4.1)
- actionpack (= 6.0.4.1)
+ actioncable (6.1.6)
+ actionpack (= 6.1.6)
+ activesupport (= 6.1.6)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.0.4.1)
- actionpack (= 6.0.4.1)
- activejob (= 6.0.4.1)
- activerecord (= 6.0.4.1)
- activestorage (= 6.0.4.1)
- activesupport (= 6.0.4.1)
+ actionmailbox (6.1.6)
+ actionpack (= 6.1.6)
+ activejob (= 6.1.6)
+ activerecord (= 6.1.6)
+ activestorage (= 6.1.6)
+ activesupport (= 6.1.6)
mail (>= 2.7.1)
- actionmailer (6.0.4.1)
- actionpack (= 6.0.4.1)
- actionview (= 6.0.4.1)
- activejob (= 6.0.4.1)
+ actionmailer (6.1.6)
+ actionpack (= 6.1.6)
+ actionview (= 6.1.6)
+ activejob (= 6.1.6)
+ activesupport (= 6.1.6)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.0.4.1)
- actionview (= 6.0.4.1)
- activesupport (= 6.0.4.1)
- rack (~> 2.0, >= 2.0.8)
+ actionpack (6.1.6)
+ actionview (= 6.1.6)
+ activesupport (= 6.1.6)
+ rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.0.4.1)
- actionpack (= 6.0.4.1)
- activerecord (= 6.0.4.1)
- activestorage (= 6.0.4.1)
- activesupport (= 6.0.4.1)
+ actiontext (6.1.6)
+ actionpack (= 6.1.6)
+ activerecord (= 6.1.6)
+ activestorage (= 6.1.6)
+ activesupport (= 6.1.6)
nokogiri (>= 1.8.5)
- actionview (6.0.4.1)
- activesupport (= 6.0.4.1)
+ actionview (6.1.6)
+ activesupport (= 6.1.6)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@@ -84,49 +86,77 @@ GEM
active_model_otp (2.3.1)
activemodel
rotp (~> 6.2.0)
- activejob (6.0.4.1)
- activesupport (= 6.0.4.1)
+ activejob (6.1.6)
+ activesupport (= 6.1.6)
globalid (>= 0.3.6)
- activemodel (6.0.4.1)
- activesupport (= 6.0.4.1)
- activerecord (6.0.4.1)
- activemodel (= 6.0.4.1)
- activesupport (= 6.0.4.1)
- activestorage (6.0.4.1)
- actionpack (= 6.0.4.1)
- activejob (= 6.0.4.1)
- activerecord (= 6.0.4.1)
- marcel (~> 1.0.0)
- activesupport (6.0.4.1)
+ activemodel (6.1.6)
+ activesupport (= 6.1.6)
+ activerecord (6.1.6)
+ activemodel (= 6.1.6)
+ activesupport (= 6.1.6)
+ activestorage (6.1.6)
+ actionpack (= 6.1.6)
+ activejob (= 6.1.6)
+ activerecord (= 6.1.6)
+ activesupport (= 6.1.6)
+ marcel (~> 1.0)
+ mini_mime (>= 1.1.0)
+ activesupport (6.1.6)
concurrent-ruby (~> 1.0, >= 1.0.2)
- i18n (>= 0.7, < 2)
- minitest (~> 5.1)
- tzinfo (~> 1.1)
- zeitwerk (~> 2.2, >= 2.2.2)
+ i18n (>= 1.6, < 2)
+ minitest (>= 5.1)
+ tzinfo (~> 2.0)
+ zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
- annotate (3.1.0)
- activerecord (>= 3.2, < 7.0)
+ annotate (3.2.0)
+ activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
- bcrypt (3.1.16)
+ aws-eventstream (1.2.0)
+ aws-partitions (1.590.0)
+ aws-sdk-core (3.131.1)
+ aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (~> 1, >= 1.525.0)
+ aws-sigv4 (~> 1.1)
+ jmespath (~> 1, >= 1.6.1)
+ aws-sdk-kms (1.57.0)
+ aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sigv4 (~> 1.1)
+ aws-sdk-s3 (1.114.0)
+ aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.4)
+ aws-sigv4 (1.5.0)
+ aws-eventstream (~> 1, >= 1.0.2)
+ azure-core (0.1.15)
+ faraday (~> 0.9)
+ faraday_middleware (~> 0.10)
+ nokogiri (~> 1.6)
+ azure-storage (0.15.0.preview)
+ azure-core (~> 0.1)
+ faraday (~> 0.9)
+ faraday_middleware (~> 0.10)
+ nokogiri (~> 1.6, >= 1.6.8)
+ bcrypt (3.1.18)
bindex (0.8.1)
bootstrap-sass (2.3.2.2)
sass (~> 3.2)
builder (3.2.4)
- bullet (6.1.5)
+ bullet (7.0.2)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
- cancancan (3.3.0)
+ cancancan (3.4.0)
capistrano (2.15.9)
highline
net-scp (>= 1.0.0)
net-sftp (>= 2.0.0)
net-ssh (>= 2.0.14)
net-ssh-gateway (>= 1.1.0)
- capybara (3.35.3)
+ capybara (3.37.1)
addressable
+ matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
@@ -135,20 +165,23 @@ GEM
xpath (~> 3.2)
charlock_holmes (0.7.7)
coderay (1.1.3)
- concurrent-ruby (1.1.9)
+ concurrent-ruby (1.1.10)
crack (0.4.5)
rexml
crass (1.0.6)
daemons (1.4.0)
- dalli (3.0.4)
+ dalli (3.2.2)
dante (0.2.0)
- diff-lcs (1.4.4)
+ declarative (0.0.20)
+ diff-lcs (1.5.0)
+ digest-crc (0.6.4)
+ rake (>= 12.0.0, < 14.0.0)
docile (1.3.5)
erubi (1.10.0)
eventmachine (1.2.7)
- exception_notification (4.4.3)
- actionmailer (>= 4.0, < 7)
- activesupport (>= 4.0, < 7)
+ exception_notification (4.5.0)
+ actionmailer (>= 5.2, < 8)
+ activesupport (>= 5.2, < 8)
execjs (2.7.0)
factory_bot (6.2.0)
activesupport (>= 5.0.0)
@@ -157,70 +190,119 @@ GEM
railties (>= 5.0.0)
fancybox-rails (0.3.1)
railties (>= 3.1.0)
- fast_gettext (2.1.0)
- ffi (1.15.3)
+ faraday (0.17.5)
+ multipart-post (>= 1.2, < 3)
+ faraday_middleware (0.14.0)
+ faraday (>= 0.7.4, < 1.0)
+ fast_gettext (2.2.0)
+ ffi (1.15.5)
fivemat (1.3.7)
flipper (0.22.1)
flipper-active_record (0.22.1)
activerecord (>= 4.2, < 7)
flipper (~> 0.22.1)
+ forwardable (1.3.2)
gender_detector (2.0.0)
- gettext (3.4.1)
+ gettext (3.4.3)
+ erubi
locale (>= 2.0.5)
+ prime
text (>= 1.3.0)
gettext_i18n_rails (1.8.1)
fast_gettext (>= 0.9.0)
- globalid (0.5.2)
+ globalid (1.0.0)
activesupport (>= 5.0)
- globalize (5.3.1)
- activemodel (>= 4.2, < 6.1)
- activerecord (>= 4.2, < 6.1)
+ globalize (6.2.1)
+ activemodel (>= 4.2, < 7.1)
+ activerecord (>= 4.2, < 7.1)
request_store (~> 1.0)
gnuplot (2.6.2)
+ google-apis-core (0.4.2)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.a)
+ rexml
+ webrick
+ google-apis-iamcredentials_v1 (0.10.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-storage_v1 (0.13.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-cloud-core (1.6.0)
+ google-cloud-env (~> 1.0)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.6.0)
+ faraday (>= 0.17.3, < 3.0)
+ google-cloud-errors (1.2.0)
+ google-cloud-storage (1.36.2)
+ addressable (~> 2.8)
+ digest-crc (~> 0.4)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.1)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
+ mini_mime (~> 1.0)
+ googleauth (1.1.3)
+ faraday (>= 0.17.3, < 3.a)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (>= 0.16, < 2.a)
hashdiff (1.0.1)
highline (2.0.0)
hodel_3000_compliant_logger (0.1.1)
- holidays (8.4.1)
+ holidays (8.5.0)
htmlentities (4.3.4)
- i18n (1.8.11)
+ httpclient (2.8.3)
+ i18n (1.10.0)
concurrent-ruby (~> 1.0)
icalendar (2.7.1)
ice_cube (~> 0.16)
ice_cube (0.16.3)
iso_country_codes (0.7.8)
- jquery-rails (4.4.0)
+ jmespath (1.6.1)
+ jquery-rails (4.5.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (6.0.1)
railties (>= 3.2.16)
- json (2.6.1)
+ json (2.6.2)
+ jwt (2.3.0)
launchy (2.4.3)
addressable (~> 2.3)
- libv8-node (15.14.0.1)
- listen (3.7.0)
+ libv8-node (16.10.0.0)
+ libv8-node (16.10.0.0-aarch64-linux)
+ libv8-node (16.10.0.0-x86_64-linux)
+ listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
locale (2.1.3)
- loofah (2.12.0)
+ loofah (2.18.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mahoro (0.5)
mail (2.7.1)
mini_mime (>= 0.1.1)
- marcel (1.0.1)
+ marcel (1.0.2)
+ matrix (0.4.2)
maxmind-db (1.0.0)
+ memoist (0.16.2)
method_source (1.0.0)
mime-types (2.99.3)
mini_magick (4.11.0)
- mini_mime (1.1.1)
- mini_portile2 (2.6.1)
- mini_racer (0.4.0)
- libv8-node (~> 15.14.0.0)
- minitest (5.14.4)
+ mini_mime (1.1.2)
+ mini_portile2 (2.8.0)
+ mini_racer (0.6.2)
+ libv8-node (~> 16.10.0.0)
+ minitest (5.16.1)
money (6.16.0)
i18n (>= 0.6.4, <= 2)
- multi_json (1.13.1)
+ multi_json (1.15.0)
+ multipart-post (2.1.1)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-sftp (2.1.2)
@@ -229,44 +311,52 @@ GEM
net-ssh-gateway (2.0.0)
net-ssh (>= 4.0.0)
nio4r (2.5.8)
- nokogiri (1.12.5)
- mini_portile2 (~> 2.6.1)
+ nokogiri (1.13.6)
+ mini_portile2 (~> 2.8.0)
+ racc (~> 1.4)
+ nokogiri (1.13.6-aarch64-linux)
+ racc (~> 1.4)
+ nokogiri (1.13.6-x86_64-linux)
racc (~> 1.4)
oink (0.10.1)
activerecord
hodel_3000_compliant_logger
open4 (1.3.4)
- parallel (1.21.0)
- parser (3.0.2.0)
+ os (1.1.4)
+ parallel (1.22.1)
+ parser (3.1.2.0)
ast (~> 2.4.1)
- pg (1.2.3)
+ pg (1.4.1)
+ prime (0.1.2)
+ forwardable
+ singleton
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.9.0)
byebug (~> 11.0)
pry (~> 0.13.0)
- public_suffix (4.0.6)
- racc (1.5.2)
- rack (2.2.3)
+ public_suffix (4.0.7)
+ racc (1.6.0)
+ rack (2.2.3.1)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rack-utf8_sanitizer (1.7.0)
rack (>= 1.0, < 3.0)
- rails (6.0.4.1)
- actioncable (= 6.0.4.1)
- actionmailbox (= 6.0.4.1)
- actionmailer (= 6.0.4.1)
- actionpack (= 6.0.4.1)
- actiontext (= 6.0.4.1)
- actionview (= 6.0.4.1)
- activejob (= 6.0.4.1)
- activemodel (= 6.0.4.1)
- activerecord (= 6.0.4.1)
- activestorage (= 6.0.4.1)
- activesupport (= 6.0.4.1)
- bundler (>= 1.3.0)
- railties (= 6.0.4.1)
+ rails (6.1.6)
+ actioncable (= 6.1.6)
+ actionmailbox (= 6.1.6)
+ actionmailer (= 6.1.6)
+ actionpack (= 6.1.6)
+ actiontext (= 6.1.6)
+ actionview (= 6.1.6)
+ activejob (= 6.1.6)
+ activemodel (= 6.1.6)
+ activerecord (= 6.1.6)
+ activestorage (= 6.1.6)
+ activesupport (= 6.1.6)
+ bundler (>= 1.15.0)
+ railties (= 6.1.6)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@@ -277,44 +367,49 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
- rails-i18n (6.0.0)
+ rails-i18n (7.0.3)
i18n (>= 0.7, < 2)
- railties (>= 6.0.0, < 7)
- railties (6.0.4.1)
- actionpack (= 6.0.4.1)
- activesupport (= 6.0.4.1)
+ railties (>= 6.0.0, < 8)
+ railties (6.1.6)
+ actionpack (= 6.1.6)
+ activesupport (= 6.1.6)
method_source
- rake (>= 0.8.7)
- thor (>= 0.20.3, < 2.0)
- rainbow (3.0.0)
+ rake (>= 12.2)
+ thor (~> 1.0)
+ rainbow (3.1.1)
rake (13.0.6)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
- recaptcha (5.8.1)
+ recaptcha (5.10.0)
json
- regexp_parser (2.1.1)
- request_store (1.5.0)
+ regexp_parser (2.5.0)
+ representable (3.1.1)
+ declarative (< 0.1.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
+ uber (< 0.2.0)
+ request_store (1.5.1)
rack (>= 1.4)
+ retriable (3.1.2)
rexml (3.2.5)
rolify (5.3.0)
rotp (6.2.0)
- routing-filter (0.6.3)
- actionpack (>= 4.2)
- activesupport (>= 4.2)
+ routing-filter (0.7.0)
+ actionpack (>= 6.1)
+ activesupport (>= 6.1)
rspec-activemodel-mocks (1.1.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
rspec-mocks (>= 2.99, < 4.0)
- rspec-core (3.10.1)
- rspec-support (~> 3.10.0)
- rspec-expectations (3.10.1)
+ rspec-core (3.11.0)
+ rspec-support (~> 3.11.0)
+ rspec-expectations (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.10.0)
- rspec-mocks (3.10.2)
+ rspec-support (~> 3.11.0)
+ rspec-mocks (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.10.0)
- rspec-rails (5.0.2)
+ rspec-support (~> 3.11.0)
+ rspec-rails (5.1.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
@@ -322,22 +417,22 @@ GEM
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
- rspec-support (3.10.2)
- rubocop (1.22.3)
+ rspec-support (3.11.0)
+ rubocop (1.30.1)
parallel (~> 1.10)
- parser (>= 3.0.0.0)
+ parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
- rexml
- rubocop-ast (>= 1.12.0, < 2.0)
+ rexml (>= 3.2.5, < 4.0)
+ rubocop-ast (>= 1.18.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
- rubocop-ast (1.13.0)
- parser (>= 3.0.1.1)
- rubocop-performance (1.12.0)
+ rubocop-ast (1.18.0)
+ parser (>= 3.1.1.0)
+ rubocop-performance (1.14.2)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
- rubocop-rails (2.12.4)
+ rubocop-rails (2.15.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
@@ -352,40 +447,47 @@ GEM
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
secure_headers (6.3.3)
+ signet (0.16.1)
+ addressable (~> 2.8)
+ faraday (>= 0.17.5, < 3.0)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
simplecov (0.17.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
simplecov-lcov (0.7.0)
+ singleton (0.1.1)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.2.2)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
+ sprockets-rails (3.4.2)
+ actionpack (>= 5.2)
+ activesupport (>= 5.2)
sprockets (>= 3.0.0)
statistics2 (0.54)
- stripe (5.39.0)
+ stripe (5.55.0)
syslog_protocol (0.9.2)
text (1.3.1)
thin (1.8.1)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3)
- thor (1.1.0)
- thread_safe (0.3.6)
+ thor (1.2.1)
tilt (2.0.10)
- tzinfo (1.2.9)
- thread_safe (~> 0.1)
+ trailblazer-option (0.1.2)
+ tzinfo (2.0.4)
+ concurrent-ruby (~> 1.0)
+ uber (0.1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unicode (0.4.4.4)
- unicode-display_width (2.1.0)
+ unicode-display_width (2.2.0)
unidecoder (1.1.2)
- uniform_notifier (1.14.2)
+ uniform_notifier (1.16.0)
vpim (13.11.11)
- web-console (4.1.0)
+ web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
@@ -394,6 +496,7 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
+ webrick (1.7.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -403,72 +506,77 @@ GEM
rexml
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.5.1)
+ zeitwerk (2.6.0)
zip_tricks (5.6.0)
PLATFORMS
+ aarch64-linux
ruby
+ x86_64-linux
DEPENDENCIES
active_model_otp
acts_as_versioned!
alaveteli_features!
- annotate (< 3.1.1)
- bcrypt (~> 3.1.16)
+ annotate (< 3.2.1)
+ aws-sdk-s3
+ azure-storage
+ bcrypt (~> 3.1.18)
bootstrap-sass (~> 2.3.2.2)
- bullet (~> 6.1.5)
- cancancan (~> 3.3.0)
+ bullet (~> 7.0.2)
+ cancancan (~> 3.4.0)
capistrano (~> 2.15.0, < 3.0.0)
- capybara (~> 3.35.3)
+ capybara (~> 3.37.1)
charlock_holmes (~> 0.7.7)
- dalli (~> 3.0.4)
- exception_notification (~> 4.4.3)
+ dalli (~> 3.2.2)
+ exception_notification (~> 4.5.0)
factory_bot_rails (~> 6.2.0)
fancybox-rails (~> 0.3.0)
- fast_gettext (~> 2.1.0)
+ fast_gettext (~> 2.2.0)
fivemat (~> 1.3.7)
gender_detector (~> 2.0.0)
- gettext (~> 3.4.1)
+ gettext (~> 3.4.3)
gettext_i18n_rails (~> 1.8.1)
- globalize (~> 5.3.0)
+ globalize (~> 6.2.1)
gnuplot (~> 2.6.0)
- holidays (~> 8.4.1)
+ google-cloud-storage (~> 1.36)
+ holidays (~> 8.5.0)
htmlentities (~> 4.3.0)
- i18n (~> 1.8.11)
+ i18n (~> 1.10.0)
icalendar (~> 2.7.1)
iso_country_codes (~> 0.7.8)
- jquery-rails (~> 4.4.0)
+ jquery-rails (~> 4.5.0)
jquery-ui-rails (~> 6.0.0)
- json (~> 2.6.1)
+ json (~> 2.6.2)
launchy (< 2.5.0)
- listen (>= 3.0.5, < 3.7.1)
+ listen (>= 3.0.5, < 3.7.2)
locale (~> 2.1.3)
mahoro (~> 0.5)
mail (~> 2.7.1)
maxmind-db (~> 1.0.0)
mime-types (< 3.0.0)
mini_magick (~> 4.11.0)
- mini_racer (~> 0.4.0)
+ mini_racer (~> 0.6.2)
money (~> 6.16.0)
net-ssh (~> 6.1.0)
net-ssh-gateway (>= 1.1.0, < 3.0.0)
- nokogiri (~> 1.12.5)
+ nokogiri (~> 1.13.6)
oink (~> 0.10.1)
open4 (~> 1.3.0)
- pg (~> 1.2.3)
+ pg (~> 1.4.1)
pry (~> 0.13.0)
pry-byebug (~> 3.9.0)
rack (~> 2.2.3)
rack-utf8_sanitizer (~> 1.7.0)
- rails (~> 6.0.3)
+ rails (~> 6.1.6)
rails-controller-testing
- rails-i18n (~> 6.0.0)
- recaptcha (~> 5.8.1)
+ rails-i18n (~> 7.0.3)
+ recaptcha (~> 5.10.0)
rolify (~> 5.3.0)
- routing-filter (~> 0.6.2)
+ routing-filter (~> 0.7.0)
rspec-activemodel-mocks (~> 1.1.0)
- rspec-rails (~> 5.0.2)
- rubocop (~> 1.22.3)
+ rspec-rails (~> 5.1.2)
+ rubocop (~> 1.30.1)
rubocop-performance
rubocop-rails
ruby-msg (~> 1.5.0)!
@@ -479,7 +587,7 @@ DEPENDENCIES
simplecov-lcov (~> 0.7.0)
statistics2 (~> 0.54)
strip_attributes!
- stripe (~> 5.39.0)
+ stripe (~> 5.55.0)
stripe-ruby-mock!
syslog_protocol (~> 0.9.0)
thin (~> 1.8.1)
diff --git a/Gemfile.rails_next.lock b/Gemfile.rails_next.lock
index 75c402a0b6..9c98803de2 100644
--- a/Gemfile.rails_next.lock
+++ b/Gemfile.rails_next.lock
@@ -9,11 +9,11 @@ GIT
GIT
remote: https://github.com/mysociety/strip_attributes.git
- revision: 62a5e1ee26501ad4c111b855cd73a5653091300b
- branch: globalize3-rails5.2
+ revision: 842a889258a897692296dff8445bb9dc12e676f8
+ branch: globalize3-rails7
specs:
- strip_attributes (1.11.0)
- activemodel (>= 3.0, < 7.0)
+ strip_attributes (1.12.0)
+ activemodel (>= 3.0, < 8.0)
GIT
remote: https://github.com/stripe-ruby-mock/stripe-ruby-mock
@@ -40,45 +40,52 @@ PATH
flipper (~> 0.10)
flipper-active_record (~> 0.10)
mime-types (< 3.0.0)
- rails (~> 6.1.4)
+ rails (~> 7.0.2)
GEM
remote: https://rubygems.org/
specs:
- actioncable (6.1.4.1)
- actionpack (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actioncable (7.0.3)
+ actionpack (= 7.0.3)
+ activesupport (= 7.0.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.1.4.1)
- actionpack (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actionmailbox (7.0.3)
+ actionpack (= 7.0.3)
+ activejob (= 7.0.3)
+ activerecord (= 7.0.3)
+ activestorage (= 7.0.3)
+ activesupport (= 7.0.3)
mail (>= 2.7.1)
- actionmailer (6.1.4.1)
- actionpack (= 6.1.4.1)
- actionview (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ net-imap
+ net-pop
+ net-smtp
+ actionmailer (7.0.3)
+ actionpack (= 7.0.3)
+ actionview (= 7.0.3)
+ activejob (= 7.0.3)
+ activesupport (= 7.0.3)
mail (~> 2.5, >= 2.5.4)
+ net-imap
+ net-pop
+ net-smtp
rails-dom-testing (~> 2.0)
- actionpack (6.1.4.1)
- actionview (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- rack (~> 2.0, >= 2.0.9)
+ actionpack (7.0.3)
+ actionview (= 7.0.3)
+ activesupport (= 7.0.3)
+ rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.1.4.1)
- actionpack (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actiontext (7.0.3)
+ actionpack (= 7.0.3)
+ activerecord (= 7.0.3)
+ activestorage (= 7.0.3)
+ activesupport (= 7.0.3)
+ globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
- actionview (6.1.4.1)
- activesupport (= 6.1.4.1)
+ actionview (7.0.3)
+ activesupport (= 7.0.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@@ -86,51 +93,76 @@ GEM
active_model_otp (2.3.1)
activemodel
rotp (~> 6.2.0)
- activejob (6.1.4.1)
- activesupport (= 6.1.4.1)
+ activejob (7.0.3)
+ activesupport (= 7.0.3)
globalid (>= 0.3.6)
- activemodel (6.1.4.1)
- activesupport (= 6.1.4.1)
- activerecord (6.1.4.1)
- activemodel (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- activestorage (6.1.4.1)
- actionpack (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- marcel (~> 1.0.0)
+ activemodel (7.0.3)
+ activesupport (= 7.0.3)
+ activerecord (7.0.3)
+ activemodel (= 7.0.3)
+ activesupport (= 7.0.3)
+ activestorage (7.0.3)
+ actionpack (= 7.0.3)
+ activejob (= 7.0.3)
+ activerecord (= 7.0.3)
+ activesupport (= 7.0.3)
+ marcel (~> 1.0)
mini_mime (>= 1.1.0)
- activesupport (6.1.4.1)
+ activesupport (7.0.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
- zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
- annotate (3.1.0)
- activerecord (>= 3.2, < 7.0)
+ annotate (3.2.0)
+ activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
- bcrypt (3.1.16)
+ aws-eventstream (1.2.0)
+ aws-partitions (1.590.0)
+ aws-sdk-core (3.131.1)
+ aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (~> 1, >= 1.525.0)
+ aws-sigv4 (~> 1.1)
+ jmespath (~> 1, >= 1.6.1)
+ aws-sdk-kms (1.57.0)
+ aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sigv4 (~> 1.1)
+ aws-sdk-s3 (1.114.0)
+ aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.4)
+ aws-sigv4 (1.5.0)
+ aws-eventstream (~> 1, >= 1.0.2)
+ azure-core (0.1.15)
+ faraday (~> 0.9)
+ faraday_middleware (~> 0.10)
+ nokogiri (~> 1.6)
+ azure-storage (0.15.0.preview)
+ azure-core (~> 0.1)
+ faraday (~> 0.9)
+ faraday_middleware (~> 0.10)
+ nokogiri (~> 1.6, >= 1.6.8)
+ bcrypt (3.1.18)
bindex (0.8.1)
bootstrap-sass (2.3.2.2)
sass (~> 3.2)
builder (3.2.4)
- bullet (6.1.5)
+ bullet (7.0.2)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
- cancancan (3.3.0)
+ cancancan (3.4.0)
capistrano (2.15.9)
highline
net-scp (>= 1.0.0)
net-sftp (>= 2.0.0)
net-ssh (>= 2.0.14)
net-ssh-gateway (>= 1.1.0)
- capybara (3.35.3)
+ capybara (3.37.1)
addressable
+ matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
@@ -139,20 +171,24 @@ GEM
xpath (~> 3.2)
charlock_holmes (0.7.7)
coderay (1.1.3)
- concurrent-ruby (1.1.9)
+ concurrent-ruby (1.1.10)
crack (0.4.5)
rexml
crass (1.0.6)
daemons (1.4.0)
- dalli (3.0.4)
+ dalli (3.2.2)
dante (0.2.0)
- diff-lcs (1.4.4)
+ declarative (0.0.20)
+ diff-lcs (1.5.0)
+ digest (3.1.0)
+ digest-crc (0.6.4)
+ rake (>= 12.0.0, < 14.0.0)
docile (1.3.5)
erubi (1.10.0)
eventmachine (1.2.7)
- exception_notification (4.4.3)
- actionmailer (>= 4.0, < 7)
- activesupport (>= 4.0, < 7)
+ exception_notification (4.5.0)
+ actionmailer (>= 5.2, < 8)
+ activesupport (>= 5.2, < 8)
execjs (2.7.0)
factory_bot (6.2.0)
activesupport (>= 5.0.0)
@@ -161,117 +197,190 @@ GEM
railties (>= 5.0.0)
fancybox-rails (0.3.1)
railties (>= 3.1.0)
- fast_gettext (2.1.0)
- ffi (1.15.3)
+ faraday (0.17.5)
+ multipart-post (>= 1.2, < 3)
+ faraday_middleware (0.14.0)
+ faraday (>= 0.7.4, < 1.0)
+ fast_gettext (2.2.0)
+ ffi (1.15.5)
fivemat (1.3.7)
- flipper (0.22.1)
- flipper-active_record (0.22.1)
- activerecord (>= 4.2, < 7)
- flipper (~> 0.22.1)
+ flipper (0.24.1)
+ flipper-active_record (0.24.1)
+ activerecord (>= 4.2, < 8)
+ flipper (~> 0.24.1)
+ forwardable (1.3.2)
gender_detector (2.0.0)
- gettext (3.4.1)
+ gettext (3.4.3)
+ erubi
locale (>= 2.0.5)
+ prime
text (>= 1.3.0)
gettext_i18n_rails (1.8.1)
fast_gettext (>= 0.9.0)
- globalid (0.5.2)
+ globalid (1.0.0)
activesupport (>= 5.0)
- globalize (6.0.1)
- activemodel (>= 4.2, < 7.0)
- activerecord (>= 4.2, < 7.0)
+ globalize (6.2.1)
+ activemodel (>= 4.2, < 7.1)
+ activerecord (>= 4.2, < 7.1)
request_store (~> 1.0)
gnuplot (2.6.2)
+ google-apis-core (0.4.2)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.a)
+ rexml
+ webrick
+ google-apis-iamcredentials_v1 (0.10.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-storage_v1 (0.13.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-cloud-core (1.6.0)
+ google-cloud-env (~> 1.0)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.6.0)
+ faraday (>= 0.17.3, < 3.0)
+ google-cloud-errors (1.2.0)
+ google-cloud-storage (1.36.2)
+ addressable (~> 2.8)
+ digest-crc (~> 0.4)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.1)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
+ mini_mime (~> 1.0)
+ googleauth (1.1.3)
+ faraday (>= 0.17.3, < 3.a)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (>= 0.16, < 2.a)
hashdiff (1.0.1)
highline (2.0.0)
hodel_3000_compliant_logger (0.1.1)
- holidays (8.4.1)
+ holidays (8.5.0)
htmlentities (4.3.4)
- i18n (1.8.11)
+ httpclient (2.8.3)
+ i18n (1.10.0)
concurrent-ruby (~> 1.0)
icalendar (2.7.1)
ice_cube (~> 0.16)
ice_cube (0.16.3)
iso_country_codes (0.7.8)
- jquery-rails (4.4.0)
+ jmespath (1.6.1)
+ jquery-rails (4.5.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (6.0.1)
railties (>= 3.2.16)
- json (2.6.1)
+ json (2.6.2)
+ jwt (2.3.0)
launchy (2.4.3)
addressable (~> 2.3)
- libv8-node (15.14.0.1)
- listen (3.7.0)
+ libv8-node (16.10.0.0)
+ libv8-node (16.10.0.0-aarch64-linux)
+ libv8-node (16.10.0.0-x86_64-linux)
+ listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
locale (2.1.3)
- loofah (2.12.0)
+ loofah (2.18.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mahoro (0.5)
mail (2.7.1)
mini_mime (>= 0.1.1)
- marcel (1.0.1)
+ marcel (1.0.2)
+ matrix (0.4.2)
maxmind-db (1.0.0)
+ memoist (0.16.2)
method_source (1.0.0)
mime-types (2.99.3)
mini_magick (4.11.0)
- mini_mime (1.1.1)
- mini_portile2 (2.6.1)
- mini_racer (0.4.0)
- libv8-node (~> 15.14.0.0)
- minitest (5.14.4)
+ mini_mime (1.1.2)
+ mini_portile2 (2.8.0)
+ mini_racer (0.6.2)
+ libv8-node (~> 16.10.0.0)
+ minitest (5.16.1)
money (6.16.0)
i18n (>= 0.6.4, <= 2)
- multi_json (1.13.1)
+ multi_json (1.15.0)
+ multipart-post (2.1.1)
+ net-http (0.1.1)
+ net-protocol
+ uri
+ net-imap (0.2.3)
+ digest
+ net-protocol
+ strscan
+ net-pop (0.1.1)
+ digest
+ net-protocol
+ timeout
+ net-protocol (0.1.3)
+ timeout
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-sftp (2.1.2)
net-ssh (>= 2.6.5)
+ net-smtp (0.3.1)
+ digest
+ net-protocol
+ timeout
net-ssh (6.1.0)
net-ssh-gateway (2.0.0)
net-ssh (>= 4.0.0)
nio4r (2.5.8)
- nokogiri (1.12.5)
- mini_portile2 (~> 2.6.1)
+ nokogiri (1.13.6)
+ mini_portile2 (~> 2.8.0)
+ racc (~> 1.4)
+ nokogiri (1.13.6-aarch64-linux)
+ racc (~> 1.4)
+ nokogiri (1.13.6-x86_64-linux)
racc (~> 1.4)
oink (0.10.1)
activerecord
hodel_3000_compliant_logger
open4 (1.3.4)
- parallel (1.21.0)
- parser (3.0.2.0)
+ os (1.1.4)
+ parallel (1.22.1)
+ parser (3.1.2.0)
ast (~> 2.4.1)
- pg (1.2.3)
+ pg (1.4.1)
+ prime (0.1.2)
+ forwardable
+ singleton
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.9.0)
byebug (~> 11.0)
pry (~> 0.13.0)
- public_suffix (4.0.6)
- racc (1.5.2)
- rack (2.2.3)
+ public_suffix (4.0.7)
+ racc (1.6.0)
+ rack (2.2.3.1)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rack-utf8_sanitizer (1.7.0)
rack (>= 1.0, < 3.0)
- rails (6.1.4.1)
- actioncable (= 6.1.4.1)
- actionmailbox (= 6.1.4.1)
- actionmailer (= 6.1.4.1)
- actionpack (= 6.1.4.1)
- actiontext (= 6.1.4.1)
- actionview (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activemodel (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ rails (7.0.3)
+ actioncable (= 7.0.3)
+ actionmailbox (= 7.0.3)
+ actionmailer (= 7.0.3)
+ actionpack (= 7.0.3)
+ actiontext (= 7.0.3)
+ actionview (= 7.0.3)
+ activejob (= 7.0.3)
+ activemodel (= 7.0.3)
+ activerecord (= 7.0.3)
+ activestorage (= 7.0.3)
+ activesupport (= 7.0.3)
bundler (>= 1.15.0)
- railties (= 6.1.4.1)
- sprockets-rails (>= 2.0.0)
+ railties (= 7.0.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -281,25 +390,31 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
- rails-i18n (6.0.0)
+ rails-i18n (7.0.3)
i18n (>= 0.7, < 2)
- railties (>= 6.0.0, < 7)
- railties (6.1.4.1)
- actionpack (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ railties (>= 6.0.0, < 8)
+ railties (7.0.3)
+ actionpack (= 7.0.3)
+ activesupport (= 7.0.3)
method_source
- rake (>= 0.13)
+ rake (>= 12.2)
thor (~> 1.0)
- rainbow (3.0.0)
+ zeitwerk (~> 2.5)
+ rainbow (3.1.1)
rake (13.0.6)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
- recaptcha (5.8.1)
+ recaptcha (5.10.0)
json
- regexp_parser (2.1.1)
- request_store (1.5.0)
+ regexp_parser (2.5.0)
+ representable (3.1.1)
+ declarative (< 0.1.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
+ uber (< 0.2.0)
+ request_store (1.5.1)
rack (>= 1.4)
+ retriable (3.1.2)
rexml (3.2.5)
rolify (5.3.0)
rotp (6.2.0)
@@ -310,15 +425,15 @@ GEM
activemodel (>= 3.0)
activesupport (>= 3.0)
rspec-mocks (>= 2.99, < 4.0)
- rspec-core (3.10.1)
- rspec-support (~> 3.10.0)
- rspec-expectations (3.10.1)
+ rspec-core (3.11.0)
+ rspec-support (~> 3.11.0)
+ rspec-expectations (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.10.0)
- rspec-mocks (3.10.2)
+ rspec-support (~> 3.11.0)
+ rspec-mocks (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.10.0)
- rspec-rails (5.0.2)
+ rspec-support (~> 3.11.0)
+ rspec-rails (5.1.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
@@ -326,22 +441,22 @@ GEM
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
- rspec-support (3.10.2)
- rubocop (1.22.3)
+ rspec-support (3.11.0)
+ rubocop (1.30.1)
parallel (~> 1.10)
- parser (>= 3.0.0.0)
+ parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
- rexml
- rubocop-ast (>= 1.12.0, < 2.0)
+ rexml (>= 3.2.5, < 4.0)
+ rubocop-ast (>= 1.18.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
- rubocop-ast (1.13.0)
- parser (>= 3.0.1.1)
- rubocop-performance (1.12.0)
+ rubocop-ast (1.18.0)
+ parser (>= 3.1.1.0)
+ rubocop-performance (1.14.2)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
- rubocop-rails (2.12.4)
+ rubocop-rails (2.15.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
@@ -356,39 +471,50 @@ GEM
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
secure_headers (6.3.3)
+ signet (0.16.1)
+ addressable (~> 2.8)
+ faraday (>= 0.17.5, < 3.0)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
simplecov (0.17.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
simplecov-lcov (0.7.0)
+ singleton (0.1.1)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.2.2)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
+ sprockets-rails (3.4.2)
+ actionpack (>= 5.2)
+ activesupport (>= 5.2)
sprockets (>= 3.0.0)
statistics2 (0.54)
- stripe (5.39.0)
+ stripe (5.55.0)
+ strscan (3.0.3)
syslog_protocol (0.9.2)
text (1.3.1)
thin (1.8.1)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3)
- thor (1.1.0)
+ thor (1.2.1)
tilt (2.0.10)
+ timeout (0.2.0)
+ trailblazer-option (0.1.2)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
+ uber (0.1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unicode (0.4.4.4)
- unicode-display_width (2.1.0)
+ unicode-display_width (2.2.0)
unidecoder (1.1.2)
- uniform_notifier (1.14.2)
+ uniform_notifier (1.16.0)
+ uri (0.10.0)
vpim (13.11.11)
- web-console (4.1.0)
+ web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
@@ -397,6 +523,7 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
+ webrick (1.7.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -406,72 +533,78 @@ GEM
rexml
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.5.1)
+ zeitwerk (2.5.4)
zip_tricks (5.6.0)
PLATFORMS
+ aarch64-linux
ruby
+ x86_64-linux
DEPENDENCIES
active_model_otp
acts_as_versioned!
alaveteli_features!
- annotate (< 3.1.1)
- bcrypt (~> 3.1.16)
+ annotate (< 3.2.1)
+ aws-sdk-s3
+ azure-storage
+ bcrypt (~> 3.1.18)
bootstrap-sass (~> 2.3.2.2)
- bullet (~> 6.1.5)
- cancancan (~> 3.3.0)
+ bullet (~> 7.0.2)
+ cancancan (~> 3.4.0)
capistrano (~> 2.15.0, < 3.0.0)
- capybara (~> 3.35.3)
+ capybara (~> 3.37.1)
charlock_holmes (~> 0.7.7)
- dalli (~> 3.0.4)
- exception_notification (~> 4.4.3)
+ dalli (~> 3.2.2)
+ exception_notification (~> 4.5.0)
factory_bot_rails (~> 6.2.0)
fancybox-rails (~> 0.3.0)
- fast_gettext (~> 2.1.0)
+ fast_gettext (~> 2.2.0)
fivemat (~> 1.3.7)
gender_detector (~> 2.0.0)
- gettext (~> 3.4.1)
+ gettext (~> 3.4.3)
gettext_i18n_rails (~> 1.8.1)
- globalize (~> 6.0.0)
+ globalize (~> 6.2.1)
gnuplot (~> 2.6.0)
- holidays (~> 8.4.1)
+ google-cloud-storage (~> 1.36)
+ holidays (~> 8.5.0)
htmlentities (~> 4.3.0)
- i18n (~> 1.8.11)
+ i18n (~> 1.10.0)
icalendar (~> 2.7.1)
iso_country_codes (~> 0.7.8)
- jquery-rails (~> 4.4.0)
+ jquery-rails (~> 4.5.0)
jquery-ui-rails (~> 6.0.0)
- json (~> 2.6.1)
+ json (~> 2.6.2)
launchy (< 2.5.0)
- listen (>= 3.0.5, < 3.7.1)
+ listen (>= 3.0.5, < 3.7.2)
locale (~> 2.1.3)
mahoro (~> 0.5)
mail (~> 2.7.1)
maxmind-db (~> 1.0.0)
mime-types (< 3.0.0)
mini_magick (~> 4.11.0)
- mini_racer (~> 0.4.0)
+ mini_racer (~> 0.6.2)
money (~> 6.16.0)
+ net-http (= 0.1.1)
net-ssh (~> 6.1.0)
net-ssh-gateway (>= 1.1.0, < 3.0.0)
- nokogiri (~> 1.12.5)
+ nokogiri (~> 1.13.6)
oink (~> 0.10.1)
open4 (~> 1.3.0)
- pg (~> 1.2.3)
+ pg (~> 1.4.1)
pry (~> 0.13.0)
pry-byebug (~> 3.9.0)
rack (~> 2.2.3)
rack-utf8_sanitizer (~> 1.7.0)
- rails (~> 6.1.4)
+ rails (~> 7.0.3)
rails-controller-testing
- rails-i18n (~> 6.0.0)
- recaptcha (~> 5.8.1)
+ rails-i18n (~> 7.0.3)
+ recaptcha (~> 5.10.0)
rolify (~> 5.3.0)
routing-filter (~> 0.7.0)
rspec-activemodel-mocks (~> 1.1.0)
- rspec-rails (~> 5.0.2)
- rubocop (~> 1.22.3)
+ rspec-rails (~> 5.1.2)
+ rubocop (~> 1.30.1)
rubocop-performance
rubocop-rails
ruby-msg (~> 1.5.0)!
@@ -482,13 +615,14 @@ DEPENDENCIES
simplecov-lcov (~> 0.7.0)
statistics2 (~> 0.54)
strip_attributes!
- stripe (~> 5.39.0)
+ stripe (~> 5.55.0)
stripe-ruby-mock!
syslog_protocol (~> 0.9.0)
thin (~> 1.8.1)
uglifier (~> 4.2.0)
unicode (~> 0.4.4)
unidecoder (~> 1.1.0)
+ uri (= 0.10.0)
vpim (~> 13.11.11)
web-console (>= 3.3.0)
webmock (~> 3.14.0)
diff --git a/README.md b/README.md
index ab3a9806a6..9fe4a3ecbd 100644
--- a/README.md
+++ b/README.md
@@ -37,11 +37,9 @@ see [the project website](http://alaveteli.org) for instructions on installing A
Every Alaveteli commit is tested by GitHub Actions on the [following Ruby platforms](https://github.com/mysociety/alaveteli/blob/develop/.github/workflows/ci.yml#L15)
-* ruby-2.5
-* ruby-2.6
* ruby-2.7
-If you use a ruby version management tool (such as RVM or .rbenv) and want to use the default development version used by the Alaveteli team (currently 2.5.8), you can create a `.ruby-version` symlink with a target of `.ruby-version.example` to switch to that automatically in the project directory.
+If you use a ruby version management tool (such as RVM or .rbenv) and want to use the default development version used by the Alaveteli team (currently 2.7.4), you can create a `.ruby-version` symlink with a target of `.ruby-version.example` to switch to that automatically in the project directory.
## How to contribute
diff --git a/Vagrantfile b/Vagrantfile
index 02b04fe5ec..401d8ec794 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -102,7 +102,7 @@ DEFAULTS = {
'public_network' => false,
'memory' => 1536,
'themes_dir' => '../alaveteli-themes',
- 'os' => 'stretch64',
+ 'os' => 'bullseye64',
'name' => 'default',
'use_nfs' => false,
'show_settings' => false,
@@ -124,22 +124,10 @@ else
end
SUPPORTED_OPERATING_SYSTEMS = {
- 'bionic64' => {
- box: 'ubuntu/bionic64',
- box_url: 'https://app.vagrantup.com/ubuntu/boxes/bionic64'
- },
'focal64' => {
box: 'ubuntu/focal64',
box_url: 'https://app.vagrantup.com/ubuntu/boxes/focal64'
},
- 'stretch64' => {
- box: 'debian/stretch64',
- box_url: 'https://app.vagrantup.com/debian/boxes/stretch64'
- },
- 'buster64' => {
- box: 'debian/buster64',
- box_url: 'https://app.vagrantup.com/debian/boxes/buster64'
- },
'bullseye64' => {
box: 'debian/bullseye64',
box_url: 'https://app.vagrantup.com/debian/boxes/bullseye64'
diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js
index cb13379c27..00eaf95da2 100644
--- a/app/assets/config/manifest.js
+++ b/app/assets/config/manifest.js
@@ -19,6 +19,7 @@
//= link new-request.js
//= link time_series.js
//= link admin.css
+//= link admin/print.css
//= link bootstrap-dropdown.js
//= link widget.css
//= link request-attachments.js
diff --git a/app/assets/images/act-links-sprite.png b/app/assets/images/act-links-sprite.png
index 3fefed8f6a..171a399f6d 100644
Binary files a/app/assets/images/act-links-sprite.png and b/app/assets/images/act-links-sprite.png differ
diff --git a/app/assets/images/act-links-sprite@2.png b/app/assets/images/act-links-sprite@2.png
index b2937813ee..3fc83bbf96 100644
Binary files a/app/assets/images/act-links-sprite@2.png and b/app/assets/images/act-links-sprite@2.png differ
diff --git a/app/assets/javascripts/admin/admin.js b/app/assets/javascripts/admin/admin.js
index b5fc50ad3a..afadf45677 100644
--- a/app/assets/javascripts/admin/admin.js
+++ b/app/assets/javascripts/admin/admin.js
@@ -2,10 +2,10 @@
jQuery(function() {
$('.locales a:first').tab('show');
$('.accordion-body').on('hidden', function() {
- return $(this).prev().find('i').first().removeClass().addClass('icon-chevron-right');
+ return $(this).prev().find('i.icon-chevron-down').first().removeClass().addClass('icon-chevron-right');
});
$('.accordion-body').on('shown', function() {
- return $(this).prev().find('i').first().removeClass().addClass('icon-chevron-down');
+ return $(this).prev().find('i.icon-chevron-right').first().removeClass().addClass('icon-chevron-down');
});
$('.toggle-hidden').on('click', function() {
$(this).parents('td').find('div:hidden').show();
@@ -90,3 +90,30 @@
});
}).call(this);
+
+$(function() {
+ $('.select_all').click(function (event) {
+ var selectables = $("input[name='" + $(this).data('target') + "']");
+ var state = $(this).data('state');
+
+ if (state == "unchecked") {
+ selectables.each(function () {
+ $(this).prop('checked', true);
+ });
+
+ $(this).data('state', 'checked');
+ $(this).text("Deselect all");
+ return false;
+ } else {
+ selectables.each(function () {
+ $(this).prop('checked', false);
+ });
+
+ $(this).data('state', 'unchecked');
+ $(this).text("Select all");
+ return false;
+ }
+ });
+
+ return false;
+});
diff --git a/app/assets/javascripts/alaveteli_pro/alaveteli_pro.js b/app/assets/javascripts/alaveteli_pro/alaveteli_pro.js
index cfc0e5980a..8bd641d40c 100644
--- a/app/assets/javascripts/alaveteli_pro/alaveteli_pro.js
+++ b/app/assets/javascripts/alaveteli_pro/alaveteli_pro.js
@@ -4,6 +4,7 @@
//= require alaveteli_pro/embargo_dropdown
//= require alaveteli_pro/status_dropdown
//= require alaveteli_pro/marketing
+//= require alaveteli_pro/existing_batch
// These modules must be initialised first, because later sub components may
// need access to things defined in either one of them.
@@ -22,5 +23,6 @@
//= require alaveteli_pro/batch_authority_search/results
//= require alaveteli_pro/batch_authority_search/pagination
//= require alaveteli_pro/batch_authority_search/result
+//= require alaveteli_pro/batch_authority_search/count
//= require alaveteli_pro/request_navigation
\ No newline at end of file
diff --git a/app/assets/javascripts/alaveteli_pro/batch_authority_search/browse.js b/app/assets/javascripts/alaveteli_pro/batch_authority_search/browse.js
index 3e69f34bc9..0caacaa52f 100644
--- a/app/assets/javascripts/alaveteli_pro/batch_authority_search/browse.js
+++ b/app/assets/javascripts/alaveteli_pro/batch_authority_search/browse.js
@@ -22,7 +22,7 @@
var fetchBodies = function fetchBodies(url, group) {
toggleSpinner(group);
$.ajax({
- url: url,
+ url: DraftBatchSummary.urlWithDraftID(url),
dataType: 'html',
success: function (data) {
group.append(data);
@@ -30,6 +30,7 @@
toggleSpinner(group);
$draft.trigger(DraftEvents.bodyAdded);
$search.trigger(SearchEvents.domUpdated);
+ $search.trigger(SearchEvents.rendered);
}
});
}
@@ -60,8 +61,6 @@
$search = BatchAuthoritySearch.$el;
$draft = DraftBatchSummary.$el;
- $search.on(SearchEvents.rendered, bindListItemAnchors);
-
collapseTopLevelGroups();
bindListItemAnchors();
});
diff --git a/app/assets/javascripts/alaveteli_pro/batch_authority_search/count.js b/app/assets/javascripts/alaveteli_pro/batch_authority_search/count.js
new file mode 100644
index 0000000000..7ea83428b2
--- /dev/null
+++ b/app/assets/javascripts/alaveteli_pro/batch_authority_search/count.js
@@ -0,0 +1,47 @@
+// Handles updating the batch authority search authority count
+(function($, BatchAuthoritySearch, DraftBatchSummary) {
+ var DraftEvents = DraftBatchSummary.Events;
+
+ var $search,
+ $draft,
+ $count,
+ messageTemplateZero,
+ messageTemplateOne,
+ messageTemplateMany;
+
+ // Update the count
+ var updateCount = function(e) {
+ count = publicBodiesCount();
+ if (count == 0) { messageTemplate = messageTemplateZero; }
+ else if (count == 1) { messageTemplate = messageTemplateOne; }
+ else { messageTemplate = messageTemplateMany; }
+
+ $count.text(messageTemplate.replace('{{count}}', count));
+ };
+
+ // Return the number of public bodies added
+ var publicBodiesCount = function() {
+ return $(
+ '.js-draft-batch-request-summary .batch-builder__list__item', $draft
+ ).length;
+ }
+
+ $(function() {
+ $search = BatchAuthoritySearch.$el;
+ $draft = DraftBatchSummary.$el;
+ $count = $('.batch-builder__actions__count', $search);
+ messageTemplateZero = $count.data('message-template-zero');
+ messageTemplateOne = $count.data('message-template-one');
+ messageTemplateMany = $count.data('message-template-many');
+
+ // not count element present, escape before binding events
+ if (!$count.get(0)) { return }
+
+ updateCount();
+
+ $draft.on(DraftEvents.bodyAdded, updateCount);
+ $draft.on(DraftEvents.bodyRemoved, updateCount);
+ });
+})(window.jQuery,
+ window.AlaveteliPro.BatchAuthoritySearch,
+ window.AlaveteliPro.DraftBatchSummary);
diff --git a/app/assets/javascripts/alaveteli_pro/batch_mode/mode-switcher.js b/app/assets/javascripts/alaveteli_pro/batch_mode/mode-switcher.js
index aac2d54bd4..7093ad89a2 100644
--- a/app/assets/javascripts/alaveteli_pro/batch_mode/mode-switcher.js
+++ b/app/assets/javascripts/alaveteli_pro/batch_mode/mode-switcher.js
@@ -8,28 +8,7 @@
var $tabs = $batch.find('.tab-title');
$tabs.find('a').attr('href', function(i, href) {
- // Parse the href so that we can modify the draft_id param
- var urlParts = href.split('?');
- var path = urlParts[0];
- var querystring = urlParts[1];
- var params = $.deparam(querystring);
-
- // 1. There is a DraftBatchSummary.draftId, but there is no draft_id param
- // in the href, so we want to add the param.
- //
- // 2. There is a DraftBatchSummary.draftId, and we have an existing
- // draft_id param in the href, so we want to update it to make sure we're
- // using the current DraftBatchSummary.draftId.
- //
- // 3. There is no DraftBatchSummary.draftId, but we have a draft_id param
- // in the href, so we want to remove the draft_id param.
- if (DraftBatchSummary.draftId) {
- params.draft_id = DraftBatchSummary.draftId;
- } else if (params.draft_id) {
- delete params.draft_id;
- }
-
- return path + '?' + $.param(params);
+ return DraftBatchSummary.urlWithDraftID(href);
});
};
diff --git a/app/assets/javascripts/alaveteli_pro/draft_batch_summary/body-list.js b/app/assets/javascripts/alaveteli_pro/draft_batch_summary/body-list.js
index 89c7b15186..82f03129e2 100644
--- a/app/assets/javascripts/alaveteli_pro/draft_batch_summary/body-list.js
+++ b/app/assets/javascripts/alaveteli_pro/draft_batch_summary/body-list.js
@@ -34,6 +34,11 @@
DraftBatchSummary.draftId = $(summarySelector, $draft).data('draft-id');
if (previousDraftId != DraftBatchSummary.draftId) {
$('.js-draft-id').val(DraftBatchSummary.draftId);
+
+ // update address bar with new draft ID
+ url = DraftBatchSummary.urlWithDraftID(window.location.href);
+ window.history.pushState({}, '', url);
+
$draft.trigger(DraftEvents.updatedDraftID);
}
};
diff --git a/app/assets/javascripts/alaveteli_pro/draft_batch_summary/initialise.js b/app/assets/javascripts/alaveteli_pro/draft_batch_summary/initialise.js
index f992c01d2e..3c873787e2 100644
--- a/app/assets/javascripts/alaveteli_pro/draft_batch_summary/initialise.js
+++ b/app/assets/javascripts/alaveteli_pro/draft_batch_summary/initialise.js
@@ -42,6 +42,31 @@
});
};
+ DraftBatchSummary.urlWithDraftID = function(url) {
+ // Parse the url so that we can modify the draft_id param
+ var urlParts = url.split('?');
+ var path = urlParts[0];
+ var querystring = urlParts[1];
+ var params = $.deparam(querystring);
+
+ // 1. There is a DraftBatchSummary.draftId, but there is no draft_id param
+ // in the url, so we want to add the param.
+ //
+ // 2. There is a DraftBatchSummary.draftId, and we have an existing
+ // draft_id param in the url, so we want to update it to make sure we're
+ // using the current DraftBatchSummary.draftId.
+ //
+ // 3. There is no DraftBatchSummary.draftId, but we have a draft_id param
+ // in the url, so we want to remove the draft_id param.
+ if (DraftBatchSummary.draftId) {
+ params.draft_id = DraftBatchSummary.draftId;
+ } else if (params.draft_id) {
+ delete params.draft_id;
+ }
+
+ return path + '?' + $.param(params);
+ }
+
var addLoadingClass = function addLoadingClass() {
$el.addClass('loading');
};
diff --git a/app/assets/javascripts/alaveteli_pro/existing_batch.js b/app/assets/javascripts/alaveteli_pro/existing_batch.js
new file mode 100644
index 0000000000..6e6ed1a46f
--- /dev/null
+++ b/app/assets/javascripts/alaveteli_pro/existing_batch.js
@@ -0,0 +1,9 @@
+$(function() {
+ $('#ignore_existing_batch').change(function() {
+ if ($(this).prop('checked')) {
+ $('#submit_button').removeAttr('disabled', '');
+ } else {
+ $('#submit_button').attr('disabled', 'disabled');
+ }
+ }).change();
+});
diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss
index 0b48b15a54..d98b8dafc1 100644
--- a/app/assets/stylesheets/admin.scss
+++ b/app/assets/stylesheets/admin.scss
@@ -253,6 +253,14 @@ body.admin {
}
+/* Timeline */
+
+.timeline_date {
+ .timeline_day {
+ color: #ddd;
+ }
+}
+
/* Users */
.user-labels {
@@ -260,3 +268,40 @@ body.admin {
margin-left: 0.4em;
}
}
+
+/* Bootstrap Extensions */
+/* These must come last because we @import bootstrap in .admin */
+
+.alert.alert-disabled {
+ color: #6b6d70;
+ background-color: #f7f7f9;
+ border-color: #e1e1e8;
+
+ h4 {
+ color: #6b6d70;
+ }
+}
+
+pre.info {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+
+pre.success {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+pre.warning {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #fbeed5;
+}
+
+pre.error {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #eed3d7;
+}
diff --git a/app/assets/stylesheets/admin/_print_style.scss b/app/assets/stylesheets/admin/_print_style.scss
new file mode 100644
index 0000000000..f5f9c75bf8
--- /dev/null
+++ b/app/assets/stylesheets/admin/_print_style.scss
@@ -0,0 +1,152 @@
+body {
+ font-size: 10pt;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
+}
+
+@page {
+ margin-top: 2cm;
+ margin-bottom: 2cm;
+}
+
+#admin-navbar,
+.admin-footer,
+.btn,
+fieldset,
+input.search-query,
+.help-inline,
+.role-filter,
+#mass_add_tag_new_tag_substring {
+ display: none !important;
+}
+
+a {
+ color: #08c !important;
+}
+
+//Open the accordions when printing
+.accordion-body.collapse {
+ height: auto !important;
+}
+
+.accordion-heading {
+ padding-bottom: 15px !important;
+}
+
+// Tables styling
+table {
+ page-break-after: auto;
+ border: 1px solid #d1d1d1;
+ border-collapse: collapse;
+ margin-bottom: 20px;
+}
+
+tr {
+ page-break-inside: avoid;
+ page-break-after: auto;
+ -webkit-print-color-adjust: exact;
+ &:nth-child(odd) {
+ background-color: #f9f9f9;
+ }
+ &:last-child {
+ border-bottom: none;
+ }
+}
+
+td {
+ padding: 4px 5px;
+ page-break-inside: avoid;
+ page-break-after: auto;
+ border-bottom: 1px solid #d1d1d1;
+}
+
+thead {
+ display: table-header-group;
+}
+
+tfoot {
+ display: table-footer-group;
+}
+
+// This should make the
elements more subtle
+.admin hr {
+ border-top: 1px solid #eee;
+ page-break-after: auto;
+ margin: 20px 0;
+}
+
+// This will avoid fieldset element to have a page-break.
+// The fieldset have a display: none on line 21m I'm leaving this code in case
+// we decide to make the elemnets visible again.
+fieldset {
+ page-break-inside: avoid;
+}
+
+blockquote {
+ margin-block-start: 10px;
+ margin-block-end: 0;
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+ font-weight: 600;
+}
+
+//Users page for admin
+#requests, #bodies {
+ .accordion-heading {
+ width: 10cm !important;
+ font-weight: 600;
+ }
+
+ .item-detail.accordion-body.row {
+ width: 10cm !important;
+ border: 1px solid #d1d1d1;
+ margin-bottom: 25px !important;
+
+ div {
+ border-bottom: 1px solid #d1d1d1;
+ padding: 4px 5px;
+ .span6 {
+ &:first-child {
+ margin-right: 5px;
+ }
+ }
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+
+ div:nth-child(odd) {
+ -webkit-print-color-adjust: exact;
+ background-color: #eee;
+ }
+ }
+}
+
+// Styling for "Stats" page
+.hero-unit {
+ background-color: #f3f3f3;
+ -webkit-print-color-adjust: exact;
+ width: 10cm !important;
+ padding: 10px 15px!important;
+ border-radius: 3px;
+ border: 1px solid #e9e9e9;
+ h2 {
+ font-weight: 600;
+ font-size: 9pt;
+ }
+}
+
+.stats-row {
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 15px !important;
+ .label-info {
+ color: #fff;
+ font-weight: bold;
+ background-color: #3a87ad;
+ -webkit-print-color-adjust: exact;
+ border-radius: 3px !important;
+ padding: 2px 4px;
+ margin-top: 10px !important;
+ margin-right: 5px;
+ }
+}
diff --git a/app/assets/stylesheets/admin/print.scss b/app/assets/stylesheets/admin/print.scss
new file mode 100644
index 0000000000..e991c58cd0
--- /dev/null
+++ b/app/assets/stylesheets/admin/print.scss
@@ -0,0 +1 @@
+@import "_print_style";
diff --git a/app/assets/stylesheets/responsive/_global_style.scss b/app/assets/stylesheets/responsive/_global_style.scss
index 5fe83bb5fd..119065eead 100644
--- a/app/assets/stylesheets/responsive/_global_style.scss
+++ b/app/assets/stylesheets/responsive/_global_style.scss
@@ -350,13 +350,15 @@ div.pagination {
.action-menu__info-link {
a {
font-size: 0.625em; //10px
+ text-transform: uppercase;
+ font-weight: 700;
display: inline-block;
line-height: 2.4em; //24px
margin-left: 0.8em;
background-color: rgba(0,0,0,0.05);
border-radius: 3px;
padding: 0 0.8em;
- color: #2688dc;
+ color: #1568ae;
letter-spacing: 0.1em;
position: relative;
top: -3px;
diff --git a/app/assets/stylesheets/responsive/_lists_style.scss b/app/assets/stylesheets/responsive/_lists_style.scss
index 98efc7970e..0e59a74eb6 100644
--- a/app/assets/stylesheets/responsive/_lists_style.scss
+++ b/app/assets/stylesheets/responsive/_lists_style.scss
@@ -60,7 +60,7 @@
.request_short_listing__authority {
font-size: 0.875em;
a {
- color: #777;
+ color: #6a6a6a;
}
}
diff --git a/app/assets/stylesheets/responsive/_new_request_layout.scss b/app/assets/stylesheets/responsive/_new_request_layout.scss
index 75fe3471bb..5d98bf6320 100644
--- a/app/assets/stylesheets/responsive/_new_request_layout.scss
+++ b/app/assets/stylesheets/responsive/_new_request_layout.scss
@@ -96,7 +96,16 @@ span#to_public_body {
.js-loaded {
#request_form_questions {
- label { font-size: 1.1em; }
+ label {
+ font-size: 1.1em;
+ padding-left: 20px;
+
+ input[type="radio"] {
+ // Margin-left: Prevents double line questions to have an uneven vertical aligment.
+ // Margin-bottom: Fixes the large gap between a question with two lines.
+ margin: 0 3px 0 -20px;
+ }
+ }
}
.request_form_response {
display: none;
diff --git a/app/assets/stylesheets/responsive/_password_changes_style.scss b/app/assets/stylesheets/responsive/_password_changes_style.scss
new file mode 100644
index 0000000000..e31f343d6a
--- /dev/null
+++ b/app/assets/stylesheets/responsive/_password_changes_style.scss
@@ -0,0 +1,5 @@
+#change_password form{
+ input[type="email"] {
+ width: 280px;
+ }
+}
diff --git a/app/assets/stylesheets/responsive/_sidebar_style.scss b/app/assets/stylesheets/responsive/_sidebar_style.scss
index f92bee00f5..e550ee40de 100644
--- a/app/assets/stylesheets/responsive/_sidebar_style.scss
+++ b/app/assets/stylesheets/responsive/_sidebar_style.scss
@@ -57,7 +57,7 @@
display: inline-block;
height: 16px;
width: 16px;
- background-size: 128px 16px;
+ background-size: 144px 16px;
background-repeat: no-repeat;
background-image: image-url('act-links-sprite.png');
@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {
@@ -97,6 +97,10 @@
background-position: -112px 0;
}
+.act-link-icon--donation {
+ background-position: -128px 0;
+}
+
.sidebar__section.citations {
.citations-list {
list-style: none;
diff --git a/app/assets/stylesheets/responsive/_wizard_layout.scss b/app/assets/stylesheets/responsive/_wizard_layout.scss
index 398ee7f86c..bd0a1c2bbf 100644
--- a/app/assets/stylesheets/responsive/_wizard_layout.scss
+++ b/app/assets/stylesheets/responsive/_wizard_layout.scss
@@ -80,12 +80,11 @@
*/
[data-block="exemption"] fieldset {
- max-height: 16em;
+ height: 16em;
@include respond-min( 35em ) {
- max-height: 8.3em;
+ height: 8.3em;
}
-
- transition: max-height 0.2s ease-out;
+ transition: height 0.2s ease-out;
overflow: hidden;
}
@@ -93,8 +92,11 @@
/*
* We're toggling the .expanded class with JS
*/
- max-height: none;
- transition: max-height 0.2s ease-out;
+ height: auto;
+ transition: height 0.2s ease-out;
+ @include respond-min( 35em ) {
+ height: 16em;
+ }
}
.maximise-questions {
diff --git a/app/assets/stylesheets/responsive/alaveteli_pro/_batch_request_authority_search_layout.scss b/app/assets/stylesheets/responsive/alaveteli_pro/_batch_request_authority_search_layout.scss
index 2e6a73a99c..70db895a60 100644
--- a/app/assets/stylesheets/responsive/alaveteli_pro/_batch_request_authority_search_layout.scss
+++ b/app/assets/stylesheets/responsive/alaveteli_pro/_batch_request_authority_search_layout.scss
@@ -91,6 +91,11 @@
form {
margin-bottom: 0;
}
+
+ p.batch-builder__actions__count {
+ margin-right: 10px;
+ line-height: 0.25em;
+ }
}
.batch-builder__list {
diff --git a/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_style.scss b/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_style.scss
index 4a2f2f045b..45e313d9ca 100644
--- a/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_style.scss
+++ b/app/assets/stylesheets/responsive/alaveteli_pro/_dashboard_style.scss
@@ -38,7 +38,7 @@
}
.dashboard-activity__item__time {
- color: #999;
+ color: #6a6a6a;
}
.phase-icon {
diff --git a/app/assets/stylesheets/responsive/all.scss b/app/assets/stylesheets/responsive/all.scss
index 91fdd1d82b..ccf50f650f 100644
--- a/app/assets/stylesheets/responsive/all.scss
+++ b/app/assets/stylesheets/responsive/all.scss
@@ -84,6 +84,8 @@
@import "responsive/_time_series";
+@import "responsive/_password_changes_style";
+
@import "responsive/alaveteli_pro/_pro_layout";
@import "responsive/alaveteli_pro/_pro_style";
diff --git a/app/controllers/admin/users/sign_ins_controller.rb b/app/controllers/admin/users/sign_ins_controller.rb
new file mode 100644
index 0000000000..f03335adc7
--- /dev/null
+++ b/app/controllers/admin/users/sign_ins_controller.rb
@@ -0,0 +1,15 @@
+# Display information about User::SignIn attempts
+class Admin::Users::SignInsController < AdminController
+ layout 'admin/users'
+
+ def index
+ @title = 'Listing user sign ins'
+
+ @query = params[:query]
+
+ sign_ins = User::SignIn
+ sign_ins = sign_ins.search(@query) if @query
+
+ @sign_ins = sign_ins.paginate(page: params[:page], per_page: 100)
+ end
+end
diff --git a/app/controllers/admin_comment_controller.rb b/app/controllers/admin_comment_controller.rb
index 66b010d845..3598435cf8 100644
--- a/app/controllers/admin_comment_controller.rb
+++ b/app/controllers/admin_comment_controller.rb
@@ -14,9 +14,9 @@ def index
comments = if @query
Comment.where(["lower(body) LIKE lower('%'||?||'%')", @query]).
- order('created_at DESC')
+ order(created_at: :desc)
else
- Comment.order('created_at DESC')
+ Comment.order(created_at: :desc)
end
if cannot? :admin, AlaveteliPro::Embargo
diff --git a/app/controllers/admin_incoming_message_controller.rb b/app/controllers/admin_incoming_message_controller.rb
index 2df8dd6482..1afa80cb98 100644
--- a/app/controllers/admin_incoming_message_controller.rb
+++ b/app/controllers/admin_incoming_message_controller.rb
@@ -1,6 +1,7 @@
class AdminIncomingMessageController < AdminController
before_action :set_incoming_message, :only => [:edit, :update, :destroy, :redeliver]
+ before_action :set_info_request, :check_info_request
def edit
end
@@ -128,4 +129,15 @@ def set_incoming_message
@incoming_message = IncomingMessage.find(params[:id])
end
+ def set_info_request
+ @info_request = @incoming_message&.info_request || InfoRequest.find(
+ params[:request_id]
+ )
+ end
+
+ def check_info_request
+ return if can? :admin, @info_request
+
+ raise ActiveRecord::RecordNotFound
+ end
end
diff --git a/app/controllers/admin_outgoing_message_controller.rb b/app/controllers/admin_outgoing_message_controller.rb
index 11f84927ef..ef3c429e0e 100644
--- a/app/controllers/admin_outgoing_message_controller.rb
+++ b/app/controllers/admin_outgoing_message_controller.rb
@@ -1,6 +1,7 @@
class AdminOutgoingMessageController < AdminController
before_action :set_outgoing_message, :only => [:edit, :destroy, :update, :resend]
+ before_action :set_info_request, :check_info_request
before_action :set_is_initial_message, :only => [:edit, :destroy]
def edit
@@ -91,6 +92,16 @@ def set_outgoing_message
@outgoing_message = OutgoingMessage.find(params[:id])
end
+ def set_info_request
+ @info_request = @outgoing_message.info_request
+ end
+
+ def check_info_request
+ return if can? :admin, @info_request
+
+ raise ActiveRecord::RecordNotFound
+ end
+
def set_is_initial_message
@is_initial_message = @outgoing_message == last_event_message
end
diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb
index 15f016d505..9b5b3a97a7 100644
--- a/app/controllers/admin_public_body_controller.rb
+++ b/app/controllers/admin_public_body_controller.rb
@@ -18,13 +18,13 @@ def show
@locale = AlaveteliLocalization.locale
AlaveteliLocalization.with_locale(@locale) do
@public_body = PublicBody.find(params[:id])
- info_requests = @public_body.info_requests.order('created_at DESC')
+ info_requests = @public_body.info_requests.order(created_at: :desc)
if cannot? :admin, AlaveteliPro::Embargo
info_requests = info_requests.not_embargoed
end
@info_requests = info_requests.paginate(:page => params[:page],
:per_page => 100)
- @versions = @public_body.versions.order('version DESC')
+ @versions = @public_body.versions.order(version: :desc)
render
end
end
@@ -124,10 +124,10 @@ def destroy
redirect_to admin_bodies_url
end
- def mass_tag_add
+ def mass_tag
lookup_query
- if params[:new_tag] and params[:new_tag] != ""
+ if params[:tag] and params[:tag] != ""
if params[:table_name] == 'exact'
bodies = @public_bodies_by_tag
elsif params[:table_name] == 'substring'
@@ -135,31 +135,19 @@ def mass_tag_add
else
raise "Unknown table_name #{params[:table_name]}"
end
- for body in bodies
- body.add_tag_if_not_already_present(params[:new_tag])
+
+ if request.post?
+ bodies.each { |body| body.add_tag_if_not_already_present(params[:tag]) }
+ flash[:notice] = 'Added tag to table of bodies.'
+ elsif request.delete?
+ bodies.each { |body| body.remove_tag(params[:tag]) }
+ flash[:notice] = 'Removed tag from table of bodies.'
end
- flash[:notice] = "Added tag to table of bodies."
end
redirect_to admin_bodies_url(:query => @query, :page => @page)
end
- def missing_scheme
- # There might be a way to do this in ActiveRecord, but I can't find it
- @public_bodies = PublicBody.find_by_sql("
- SELECT a.id, a.name, a.url_name, COUNT(*) AS howmany
- FROM public_bodies a JOIN info_requests r ON a.id = r.public_body_id
- WHERE a.publication_scheme = ''
- GROUP BY a.id, a.name, a.url_name
- ORDER BY howmany DESC
- LIMIT 20
- ")
- @stats = {
- "total" => PublicBody.count,
- "entered" => PublicBody.where("publication_scheme != ''").count
- }
- end
-
def import_csv
@notes = ""
@errors = ""
@@ -276,7 +264,7 @@ def lookup_query
PublicBody.
joins(:translations).
where(query).
- order('public_body_translations.name').
+ merge(PublicBody::Translation.order(:name)).
paginate(:page => @page, :per_page => 100)
end
diff --git a/app/controllers/admin_raw_email_controller.rb b/app/controllers/admin_raw_email_controller.rb
index 7acfa98aef..4b98eaa4e4 100644
--- a/app/controllers/admin_raw_email_controller.rb
+++ b/app/controllers/admin_raw_email_controller.rb
@@ -8,6 +8,7 @@ class AdminRawEmailController < AdminController
skip_before_action :html_response
before_action :set_raw_email, only: [:show]
+ before_action :set_info_request, :check_info_request
def show
respond_to do |format|
@@ -49,6 +50,16 @@ def set_raw_email
@raw_email = RawEmail.find(params[:id])
end
+ def set_info_request
+ @info_request = @raw_email.incoming_message.info_request
+ end
+
+ def check_info_request
+ return if can? :admin, @info_request
+
+ raise ActiveRecord::RecordNotFound
+ end
+
def in_holding_pen?(raw_email)
raw_email.incoming_message.info_request.holding_pen_request? &&
!raw_email.empty_from_field?
diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb
index f2e82ff9e9..ba76f5e963 100644
--- a/app/controllers/admin_request_controller.rb
+++ b/app/controllers/admin_request_controller.rb
@@ -6,13 +6,10 @@
class AdminRequestController < AdminController
- before_action :set_info_request, :only => [ :show,
- :edit,
- :update,
- :destroy,
- :move,
- :generate_upload_url,
- :hide ]
+ before_action :set_info_request, :check_info_request, only: %i[
+ show edit update destroy move generate_upload_url hide
+ ]
+
def index
@query = params[:query]
if @query
@@ -25,15 +22,12 @@ def index
info_requests = info_requests.not_embargoed
end
- @info_requests = info_requests.order('created_at DESC').paginate(
+ @info_requests = info_requests.order(created_at: :desc).paginate(
:page => params[:page],
:per_page => 100)
end
def show
- if cannot? :admin, @info_request
- raise ActiveRecord::RecordNotFound
- end
end
def edit
@@ -87,12 +81,12 @@ def destroy
# change user or public body of a request magically
def move
+ editor = admin_current_user
+
if params[:commit] == 'Move request to user' && !params[:user_url_name].blank?
destination_user = User.find_by_url_name(params[:user_url_name])
- if @info_request.move_to_user(destination_user,
- :editor => admin_current_user,
- :reindex => true)
+ if @info_request.move_to_user(destination_user, editor: editor)
flash[:notice] = "Message has been moved to new user"
else
flash[:error] = "Couldn't find user '#{params[:user_url_name]}'"
@@ -100,11 +94,11 @@ def move
redirect_to admin_request_url(@info_request)
elsif params[:commit] == 'Move request to authority' && !params[:public_body_url_name].blank?
- destination_public_body = PublicBody.find_by_url_name(params[:public_body_url_name])
+ destination_body = PublicBody.find_by_url_name(
+ params[:public_body_url_name]
+ )
- if @info_request.move_to_public_body(destination_public_body,
- :editor => admin_current_user,
- :reindex => true)
+ if @info_request.move_to_public_body(destination_body, editor: editor)
flash[:notice] = "Request has been moved to new body"
else
flash[:error] = "Couldn't find public body '#{ params[:public_body_url_name] }'"
@@ -121,7 +115,7 @@ def generate_upload_url
if params[:incoming_message_id]
incoming_message = IncomingMessage.find(params[:incoming_message_id])
email = incoming_message.from_email
- name = incoming_message.safe_mail_from || @info_request.public_body.name
+ name = incoming_message.safe_from_name || @info_request.public_body.name
else
email = @info_request.public_body.request_email
name = @info_request.public_body.name
@@ -147,7 +141,7 @@ def generate_upload_url
post_redirect.save!
flash[:notice] = {
- :partial => "upload_email_message.html.erb",
+ :partial => "upload_email_message",
:locals => {
:name => name,
:email => email,
@@ -214,4 +208,9 @@ def set_info_request
@info_request = InfoRequest.find(params[:id].to_i)
end
+ def check_info_request
+ return if can? :admin, @info_request
+
+ raise ActiveRecord::RecordNotFound
+ end
end
diff --git a/app/controllers/admin_track_controller.rb b/app/controllers/admin_track_controller.rb
index 4ec9359930..d146388d1e 100644
--- a/app/controllers/admin_track_controller.rb
+++ b/app/controllers/admin_track_controller.rb
@@ -16,7 +16,7 @@ def index
track_things = TrackThing
end
@admin_tracks =
- track_things.order('created_at DESC').
+ track_things.order(created_at: :desc).
paginate(:page => params[:page], :per_page => 100)
@popular = ActiveRecord::Base.connection.select_all("select count(*) as count, title, info_request_id from track_things join info_requests on info_request_id = info_requests.id where info_request_id is not null group by info_request_id, title order by count desc limit 10;")
end
diff --git a/app/controllers/admin_user_controller.rb b/app/controllers/admin_user_controller.rb
index e925410a69..5d6df278ab 100644
--- a/app/controllers/admin_user_controller.rb
+++ b/app/controllers/admin_user_controller.rb
@@ -5,19 +5,22 @@
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
class AdminUserController < AdminController
+ layout 'admin/users'
- before_action :set_admin_user, :only => [ :show,
- :edit,
- :update,
- :show_bounce_message,
- :clear_bounce,
- :clear_profile_photo ]
+ before_action :set_admin_user, only: %i[show
+ edit
+ update
+ show_bounce_message
+ clear_bounce
+ clear_profile_photo]
- before_action :clear_roles,
+ before_action :clear_roles, :clear_features,
:check_role_authorisation,
- :check_role_requirements, :only => [ :update ]
+ :check_role_requirements, only: %i[update]
def index
+ @title ||= 'Listing users'
+
@query = params[:query].try(:strip)
@roles = params[:roles] || []
@@ -26,16 +29,8 @@ def index
@sort_order =
@sort_options.key?(params[:sort_order]) ? params[:sort_order] : 'name_asc'
- users = if @query.present?
- User.where(
- "lower(users.name) LIKE lower('%'||:query||'%') OR " \
- "lower(users.email) LIKE lower('%'||:query||'%') OR " \
- "lower(users.about_me) LIKE lower('%'||:query||'%')",
- query: @query
- )
- else
- User
- end
+ users = @base_scope || User
+ users = users.search(@query) if @query.present?
# with_all_roles returns an array as it takes multiple queries
# so we need to requery in order to paginate
@@ -47,6 +42,8 @@ def index
@admin_users =
users.order(@sort_options[@sort_order]).
paginate(:page => params[:page], :per_page => 100)
+
+ render action: :index
end
def show
@@ -85,11 +82,22 @@ def update
end
end
+ def active
+ @title = 'Active users'
+ @base_scope = User.active
+ index
+ end
+
def banned
- @banned_users =
- User.banned.
- order('name ASC').
- paginate(:page => params[:page], :per_page => 100)
+ @title = 'Banned users'
+ @base_scope = User.banned
+ index
+ end
+
+ def closed
+ @title = 'Closed users'
+ @base_scope = User.closed
+ index
end
def show_bounce_message
@@ -112,8 +120,13 @@ def clear_profile_photo
end
def modify_comment_visibility
- Comment.where(:id => params[:comment_ids]).
- update_all(:visible => !params[:hide_selected])
+ desired_visibility = params[:hide_selected] ? false : true
+
+ Comment.
+ where(id: params[:comment_ids]).
+ where(visible: !desired_visibility).
+ find_each { |comment| comment.toggle!(:visible) }
+
redirect_back(fallback_location: admin_users_url)
end
@@ -123,7 +136,8 @@ def user_params
if params[:admin_user]
params.require(:admin_user).permit(:name,
:email,
- {:role_ids => []},
+ { role_ids: [] },
+ { features: [] },
:ban_text,
:about_me,
:no_limit,
@@ -139,6 +153,11 @@ def clear_roles
params[:admin_user][:role_ids] ||= []
end
+ def clear_features
+ # Clear features if none checked
+ params[:admin_user][:features] ||= []
+ end
+
# Check all changed roles exist, current user can grant and revoke them
# and requirements are met
def check_role_authorisation
diff --git a/app/controllers/admin_users_sessions_controller.rb b/app/controllers/admin_users_sessions_controller.rb
index 0e5d4cf19f..31b854a285 100644
--- a/app/controllers/admin_users_sessions_controller.rb
+++ b/app/controllers/admin_users_sessions_controller.rb
@@ -7,20 +7,29 @@
class AdminUsersSessionsController < AdminController
def create
# Don't use @user as that is any logged in user
- @admin_user = User.find(params[:id])
+ @user_to_login_as = User.find(params[:id])
- if cannot? :login_as, @admin_user
+ if cannot? :login_as, @user_to_login_as
flash[:error] =
- "You don't have permission to log in as #{ @admin_user.name }"
- return redirect_to admin_user_path(@admin_user)
+ "You don't have permission to log in as #{ @user_to_login_as.name }"
+ return redirect_to admin_user_path(@user_to_login_as)
end
- @admin_user.confirm!
+ @user_to_login_as.confirm!
- session[:user_id] = @admin_user.id
- session[:user_login_token] = @admin_user.login_token
+ session[:admin_id] = current_user.id
+ session[:user_id] = @user_to_login_as.id
+ session[:user_login_token] = @user_to_login_as.login_token
session[:user_circumstance] = 'login_as'
- redirect_to user_path(@admin_user)
+ redirect_to user_path(@user_to_login_as)
+ end
+
+ def destroy
+ @admin_to_revert_to = User.find(session[:admin_id])
+
+ sign_in(@admin_to_revert_to)
+
+ redirect_to admin_user_path(current_user)
end
end
diff --git a/app/controllers/alaveteli_pro/info_request_batches_controller.rb b/app/controllers/alaveteli_pro/info_request_batches_controller.rb
index 2e2a340f1f..242233336b 100644
--- a/app/controllers/alaveteli_pro/info_request_batches_controller.rb
+++ b/app/controllers/alaveteli_pro/info_request_batches_controller.rb
@@ -14,7 +14,7 @@ def new
def preview
@draft_info_request_batch = load_draft
load_data_from_draft(@draft_info_request_batch)
- if all_models_valid?
+ if all_models_valid?(ignore_existing_batch: true)
render 'alaveteli_pro/info_requests/preview'
else
remove_duplicate_errors
@@ -35,6 +35,8 @@ def create
@info_request_batch.save!
@draft_info_request_batch.destroy
redirect_to show_alaveteli_pro_batch_request_path(id: @info_request_batch.id)
+ elsif all_models_valid?(ignore_existing_batch: true)
+ render 'alaveteli_pro/info_requests/preview'
else
remove_duplicate_errors
render 'alaveteli_pro/info_requests/new'
@@ -101,7 +103,10 @@ def load_data_from_draft(draft)
@outgoing_message = @example_info_request.outgoing_messages.first
end
- def all_models_valid?
+ def all_models_valid?(ignore_existing_batch: nil)
+ ignore_existing_batch ||= params.fetch(:ignore_existing_batch, false)
+ @info_request_batch.ignore_existing_batch = ignore_existing_batch
+
@example_info_request.valid? && \
@outgoing_message.valid? && \
(@embargo.nil? || @embargo.present? && @embargo.valid?) && \
diff --git a/app/controllers/alaveteli_pro/info_requests_controller.rb b/app/controllers/alaveteli_pro/info_requests_controller.rb
index 3e0bece21f..c3b51d4cfe 100644
--- a/app/controllers/alaveteli_pro/info_requests_controller.rb
+++ b/app/controllers/alaveteli_pro/info_requests_controller.rb
@@ -129,7 +129,7 @@ def check_public_body_is_requestable
@info_request.public_body.is_requestable?
reason = @info_request.public_body.not_requestable_reason
- view = "request/new_#{reason}.html.erb"
+ view = "request/new_#{reason}"
render view
end
end
diff --git a/app/controllers/alaveteli_pro/subscriptions_controller.rb b/app/controllers/alaveteli_pro/subscriptions_controller.rb
index 52317582cb..e6684c9d64 100644
--- a/app/controllers/alaveteli_pro/subscriptions_controller.rb
+++ b/app/controllers/alaveteli_pro/subscriptions_controller.rb
@@ -120,7 +120,7 @@ def authorise
current_user.add_role(:pro)
flash[:notice] = {
- partial: 'alaveteli_pro/subscriptions/signup_message.html.erb'
+ partial: 'alaveteli_pro/subscriptions/signup_message'
}
flash[:new_pro_user] = true
diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb
index bec3b62370..ce69ba9b3a 100644
--- a/app/controllers/api_controller.rb
+++ b/app/controllers/api_controller.rb
@@ -203,7 +203,7 @@ def body_request_events
joins(:info_request).
where("public_body_id = ?", @public_body.id).
includes([{:info_request => :user}, :outgoing_message]).
- order('info_request_events.created_at DESC')
+ order(created_at: :desc)
if since_date_str
begin
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 8d1c892c51..2548c0dca1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -150,10 +150,14 @@ def sign_in(user, remember_me: nil)
session[:user_id] = user.id
session[:user_login_token] = user.login_token
session[:remember_me] = remember_me
+ # Intentionally allow to fail silently so that we don't have to care whether
+ # sign in recording is enabled.
+ user.sign_ins.create(ip: user_ip, country: country_from_ip)
end
# Logout form
def clear_session_credentials
+ session[:admin_id] = nil
session[:user_id] = nil
session[:user_login_token] = nil
session[:user_circumstance] = nil
@@ -225,13 +229,6 @@ def set_in_pro_area
private
- def user?
- warn 'DEPRECATION: ApplicationController#user? will be removed in 0.41. ' \
- 'It has been replaced with authenticated?'
-
- authenticated?
- end
-
# Override the Rails method to only set the CSRF form token if there is a
# logged in user
def form_authenticity_token(*args)
@@ -239,15 +236,7 @@ def form_authenticity_token(*args)
end
# Check the user is logged in
- def authenticated?(as: nil, **reason_params)
- unless reason_params.empty?
- warn 'DEPRECATION: ApplicationController#authenticated?(reason_params) ' \
- 'will be removed in 0.41. It has been replaced with ' \
- 'ApplicationController#authenticated? || ' \
- 'ApplicationController#ask_to_login(**reason_params)'
- return authenticated?(as: as) || ask_to_login(**reason_params)
- end
-
+ def authenticated?(as: nil)
if as
authenticated_user == as
else
@@ -288,15 +277,6 @@ def ask_to_login(as: nil, **reason_params)
false
end
- def authenticated_as_user?(user, reason_params = nil)
- warn 'DEPRECATION: ApplicationController#authenticated_as_user?(user, ' \
- 'reason_params) will be removed in 0.41. It has been replaced with ' \
- 'ApplicationController#authenticated?(as: user) || ' \
- 'ApplicationController#ask_to_login(as: user, **reason_params)'
-
- authenticated?(as: user) || ask_to_login(as: user, **reason_params)
- end
-
# Return logged in user
def authenticated_user
return unless session[:user_id]
@@ -385,7 +365,7 @@ def check_read_only
if !AlaveteliConfiguration::read_only.empty?
if feature_enabled?(:annotations)
flash[:notice] = {
- :partial => "general/read_only_annotations.html.erb",
+ :partial => "general/read_only_annotations",
:locals => {
:site_name => site_name,
:read_only => AlaveteliConfiguration.read_only
@@ -393,7 +373,7 @@ def check_read_only
}
else
flash[:notice] = {
- :partial => "general/read_only.html.erb",
+ :partial => "general/read_only",
:locals => {
:site_name => site_name,
:read_only => AlaveteliConfiguration.read_only
diff --git a/app/controllers/classifications_controller.rb b/app/controllers/classifications_controller.rb
index a0c81a6e87..b4e4d48357 100644
--- a/app/controllers/classifications_controller.rb
+++ b/app/controllers/classifications_controller.rb
@@ -50,7 +50,7 @@ def create
# Don't give advice on what to do next, as it isn't their request
if session[:request_game]
- flash[:notice] = { partial: 'request_game/thank_you.html.erb',
+ flash[:notice] = { partial: 'request_game/thank_you',
locals: {
info_request_title: @info_request.title,
url: request_path(@info_request)
diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb
index d913f679b4..533ee8a589 100644
--- a/app/controllers/comment_controller.rb
+++ b/app/controllers/comment_controller.rb
@@ -5,6 +5,7 @@
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
class CommentController < ApplicationController
+ before_action :build_comment, only: [:new]
before_action :check_read_only, :only => [ :new ]
before_action :find_info_request, :only => [ :new ]
before_action :create_track_thing, :only => [ :new ]
@@ -13,10 +14,6 @@ class CommentController < ApplicationController
before_action :set_in_pro_area, :only => [ :new ]
def new
- if params[:comment]
- @comment = Comment.new(comment_params.merge({ :user => @user }))
- end
-
if params[:comment]
# TODO: this check should theoretically be a validation rule in the model
@existing_comment = Comment.find_existing(@info_request.id, params[:comment][:body])
@@ -82,6 +79,12 @@ def new
private
+ def build_comment
+ if params[:comment]
+ @comment = Comment.new(comment_params.merge(user: @user))
+ end
+ end
+
def comment_params
params.require(:comment).permit(:body)
end
@@ -116,8 +119,12 @@ def reject_unless_comments_allowed
def reject_if_user_banned
return unless authenticated? && !authenticated_user.can_make_comments?
- @details = authenticated_user.can_fail_html
- render template: 'user/banned'
+ if authenticated_user.exceeded_limit?(:comments)
+ render template: 'comment/rate_limited'
+ else
+ @details = authenticated_user.can_fail_html
+ render template: 'user/banned'
+ end
end
# An override of ApplicationController#set_in_pro_area to set the flag
diff --git a/app/controllers/followups_controller.rb b/app/controllers/followups_controller.rb
index 24e974cbcd..f558eb7fc0 100644
--- a/app/controllers/followups_controller.rb
+++ b/app/controllers/followups_controller.rb
@@ -76,7 +76,7 @@ def check_request_matches_incoming_message
def check_responses_allowed
if @info_request.allow_new_responses_from == "nobody"
- flash.now[:error] = { :partial => "followup_not_sent.html.erb",
+ flash.now[:error] = { :partial => "followup_not_sent",
:locals => {
:help_contact_path => help_contact_path } }
render :action => 'new'
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index df312e8cf2..4ddfa8caff 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -3,14 +3,13 @@
#
# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved.
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
-
class HelpController < ApplicationController
-
- # we don't even have a control subroutine for most help pages, just see their templates
+ # we don't even have a control subroutine for most help pages, just see their
+ # templates
before_action :long_cache
- before_action :catch_spam, :only => [:contact]
- before_action :set_recaptcha_required, :only => [:contact]
+ before_action :catch_spam, only: [:contact]
+ before_action :set_recaptcha_required, only: [:contact]
def index
redirect_to help_about_path
@@ -20,79 +19,80 @@ def unhappy
@country_code = AlaveteliConfiguration.iso_country_code
@info_request = nil
if params[:url_title]
- @info_request = InfoRequest
- .not_embargoed
- .find_by_url_title!(params[:url_title])
+ @info_request = InfoRequest.
+ not_embargoed.
+ find_by_url_title!(params[:url_title])
end
@refusal_advice = RefusalAdvice.default(@info_request)
end
def contact
- @contact_email = AlaveteliConfiguration::contact_email
- if feature_enabled?(:alaveteli_pro) && @user && @user.is_pro?
- @contact_email = AlaveteliConfiguration::pro_contact_email
- end
-
# if they clicked remove for link to request/body, remove it
if params[:remove]
@last_request = nil
- cookies["last_request_id"] = nil
- cookies["last_body_id"] = nil
+ cookies['last_request_id'] = nil
+ cookies['last_body_id'] = nil
end
# look up link to request/body
- request = InfoRequest.find_by(id: cookies["last_request_id"].to_i)
+ request = InfoRequest.find_by(id: cookies['last_request_id'].to_i)
@last_request = request if can?(:read, request)
- @last_body = PublicBody.find_by(id: cookies["last_body_id"].to_i)
+ @last_body = PublicBody.find_by(id: cookies['last_body_id'].to_i)
# submit form
- if params[:submitted_contact_form]
- if @user
- params[:contact][:email] = @user.email
- params[:contact][:name] = @user.name
- end
- @contact = ContactValidator.new(params[:contact])
-
- if (@recaptcha_required &&
- !params[:remove] &&
- !verify_recaptcha)
- flash.now[:error] = _('There was an error with the reCAPTCHA. ' \
- 'Please try again.')
- elsif @contact.valid? && !params[:remove]
- ContactMailer.to_admin_message(
- params[:contact][:name],
- params[:contact][:email],
- params[:contact][:subject],
- params[:contact][:message],
- @user,
- @last_request, @last_body
- ).deliver_now
- flash[:notice] = _("Your message has been sent. Thank you for getting in touch! We'll get back to you soon.")
- redirect_to frontpage_url
- return
- end
-
- if params[:remove]
- @contact.errors.clear
- end
+ return unless params[:submitted_contact_form]
+
+ if @user
+ params[:contact][:email] = @user.email
+ params[:contact][:name] = @user.name
end
+ if params[:remove]
+ contact_validator.errors.clear
+
+ elsif @recaptcha_required && !verify_recaptcha
+ flash.now[:error] = _('There was an error with the reCAPTCHA. ' \
+ 'Please try again.')
+ elsif contact_validator.valid?
+ contact_mailer.deliver_now
+ flash[:notice] = _("Your message has been sent. Thank you for getting " \
+ "in touch! We'll get back to you soon.")
+ redirect_to frontpage_url
+ end
end
private
+ def contact_validator
+ @contact_validator ||= ContactValidator.new(contact_params)
+ end
+
+ def contact_mailer
+ ContactMailer.to_admin_message(
+ contact_params[:name],
+ contact_params[:email],
+ contact_params[:subject],
+ contact_params[:message],
+ @user, @last_request, @last_body
+ )
+ end
+
+ def contact_params
+ params.require(:contact).except(:comment).permit(
+ :name, :email, :subject, :message
+ )
+ end
+
def catch_spam
- if request.post? && params[:contact]
- if !params[:contact][:comment].blank? || !params[:contact].key?(:comment)
- redirect_to frontpage_url
- end
- end
+ return unless request.post? && params[:contact]
+ return if params[:contact].fetch(:comment, '').blank?
+
+ redirect_to frontpage_url
end
def set_recaptcha_required
@recaptcha_required = AlaveteliConfiguration.contact_form_recaptcha
end
-
end
diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb
index 51832cfeed..4486c38f63 100644
--- a/app/controllers/request_controller.rb
+++ b/app/controllers/request_controller.rb
@@ -222,8 +222,8 @@ def new_batch
@public_bodies =
PublicBody.
where(:id => params[:public_body_ids]).
- includes(:translations).
- order('public_body_translations.name')
+ joins(:translations).preload(:translations).
+ merge(PublicBody::Translation.order(:name))
end
if params[:submitted_new_request].nil? || params[:reedit]
@@ -292,7 +292,7 @@ def new
# logged in and we want to include the text of the request so they
# can squirrel it away for tomorrow, so we detect this later after
# we have constructed the InfoRequest.
- user_exceeded_limit = authenticated_user.exceeded_limit?
+ user_exceeded_limit = authenticated_user.exceeded_limit?(:info_requests)
if !user_exceeded_limit
@details = authenticated_user.can_fail_html
render :template => 'user/banned'
@@ -677,28 +677,28 @@ def make_request_zip(info_request, file_path)
def make_request_summary_file(info_request)
done = false
- convert_command = AlaveteliConfiguration::html_to_pdf_command
@render_to_file = true
assign_variables_for_show_template(info_request)
- if !convert_command.blank? && File.exist?(convert_command)
+ if HTMLtoPDFConverter.exist?
html_output = render_to_string(:template => 'request/show')
tmp_input = Tempfile.new(['foihtml2pdf-input', '.html'])
tmp_input.write(html_output)
tmp_input.close
tmp_output = Tempfile.new('foihtml2pdf-output')
- output = AlaveteliExternalCommand.run(convert_command, tmp_input.path, tmp_output.path)
+ command = HTMLtoPDFConverter.new(tmp_input, tmp_output)
+ output = command.run
if !output.nil?
file_info = { :filename => 'correspondence.pdf',
:data => File.open(tmp_output.path).read }
done = true
else
- logger.error("Could not convert info request #{info_request.id} to PDF with command '#{convert_command} #{tmp_input.path} #{tmp_output.path}'")
+ logger.error("Could not convert info request #{info_request.id} to PDF with command '#{command}'")
end
tmp_output.close
tmp_input.delete
tmp_output.delete
else
- logger.warn("No HTML -> PDF converter found at #{convert_command}")
+ logger.warn("No HTML -> PDF converter found")
end
if !done
file_info = { :filename => 'correspondence.txt',
@@ -816,7 +816,7 @@ def render_new_compose(batch)
def render_new_preview
if @outgoing_message.contains_email? || @outgoing_message.contains_postcode?
flash.now[:error] = {
- :partial => "preview_errors.html.erb",
+ :partial => "preview_errors",
:locals => {
:contains_email => @outgoing_message.contains_email?,
:contains_postcode => @outgoing_message.contains_postcode?,
@@ -933,8 +933,9 @@ def block_restricted_country_ips?
def handle_blocked_ip(info_request)
if send_exception_notifications?
- e = Exception.new("Possible spam (ip_in_blocklist) from #{ info_request.user_id }: #{ info_request.title }")
- ExceptionNotifier.notify_exception(e, :env => request.env)
+ msg = "Possible spam request (ip_in_blocklist) from " \
+ "User##{info_request.user_id}: #{user_ip} (#{country_from_ip})"
+ ExceptionNotifier.notify_exception(Exception.new(msg), env: request.env)
end
if block_restricted_country_ips?
diff --git a/app/controllers/request_game_controller.rb b/app/controllers/request_game_controller.rb
index e309018c7c..3e4530d297 100644
--- a/app/controllers/request_game_controller.rb
+++ b/app/controllers/request_game_controller.rb
@@ -29,7 +29,7 @@ def play
if @missing == 0
flash.now[:notice] = {
- :partial => "request_game/game_over.html.erb",
+ :partial => "request_game/game_over",
:locals => {
:helpus_url => help_credits_path(:anchor => "helpus"),
:site_name => site_name
diff --git a/app/controllers/track_controller.rb b/app/controllers/track_controller.rb
index 9ea449ee9a..65fdbf5165 100644
--- a/app/controllers/track_controller.rb
+++ b/app/controllers/track_controller.rb
@@ -150,11 +150,7 @@ def track_set
return true
else
# this will most likely be tripped by a single error - probably track_query length
- if rails_upgrade?
- flash[:error] = @track_thing.errors.map { |e| e.message }.join(", ")
- else
- flash[:error] = @track_thing.errors.map { |_, msg| msg }.join(", ")
- end
+ flash[:error] = @track_thing.errors.map(&:message).join(", ")
return false
end
end
diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb
index 22d7f2d1af..a78de197f7 100644
--- a/app/controllers/user_controller.rb
+++ b/app/controllers/user_controller.rb
@@ -34,6 +34,7 @@ def show
set_view_instance_variables
@same_name_users = User.find_similar_named_users(@display_user)
@is_you = current_user_is_display_user
+ @show_about_me = show_about_me?
set_show_requests if @show_requests
@@ -62,7 +63,7 @@ def show
# All tracks for the user
@track_things = TrackThing.
where(:tracking_user_id => @display_user, :track_medium => 'email_daily').
- order('created_at desc')
+ order(created_at: :desc)
@track_things_grouped = @track_things.group_by(&:track_type)
# Requests you need to describe
@undescribed_requests = @display_user.get_undescribed_requests
@@ -101,7 +102,7 @@ def wall
@track_things = TrackThing.
where(:tracking_user_id => @display_user.id,
:track_medium => 'email_daily').
- order('created_at desc')
+ order(created_at: :desc)
@track_things.each do |track_thing|
# TODO: factor out of track_mailer.rb
xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], track_thing.track_query,
@@ -139,11 +140,7 @@ def signup
if user_alreadyexists
# attempt to remove the 'already in use message' from the errors hash
# so it doesn't get accidentally shown to the end user
- if rails_upgrade?
- @user_signup.errors.delete(:email, :taken)
- else
- @user_signup.errors[:email].delete_if { |message| message == _("This email is already in use") }
- end
+ @user_signup.errors.delete(:email, :taken)
end
if error || !@user_signup.errors.empty?
# Show the form
@@ -155,6 +152,13 @@ def signup
else
# New unconfirmed user
+ # Block signups from suspicious countries
+ # TODO: Add specs (see RequestController#create)
+ # TODO: Extract to UserSpamScorer?
+ if blocked_ip?(country_from_ip, @user_signup)
+ handle_blocked_ip(@user_signup) && return
+ end
+
# Rate limit signups
ip_rate_limiter.record(user_ip)
@@ -180,6 +184,17 @@ def signup
render action: :sign
end
+ # A webserver level redirect can be used to redirect from the signup action to
+ # prevent spam signups from Tor.
+ def tor
+ long_cache
+
+ msg = _('Signups from Tor have been blocked due to extensive misuse. ' \
+ 'Please contact us if this is a problem for you.')
+
+ render plain: msg, status: :forbidden
+ end
+
def ip_rate_limiter
@ip_rate_limiter ||= AlaveteliRateLimiter::IPRateLimiter.new(:signup)
end
@@ -325,7 +340,7 @@ def set_profile_photo
if @user.get_about_me_for_html_display.empty?
- flash[:notice] = { :partial => "user/update_profile_photo.html.erb" }
+ flash[:notice] = { :partial => "user/update_profile_photo" }
redirect_to edit_profile_about_me_url
else
flash[:notice] = _("Thank you for updating your profile photo")
@@ -386,6 +401,31 @@ def set_receive_email_alerts
private
+ def block_restricted_country_ips?
+ AlaveteliConfiguration.block_restricted_country_ips ||
+ AlaveteliConfiguration.enable_anti_spam
+ end
+
+ def blocked_ip?(country, user)
+ AlaveteliConfiguration.restricted_countries.include?(country) &&
+ country != AlaveteliConfiguration.iso_country_code
+ end
+
+ def handle_blocked_ip(user)
+ if send_exception_notifications?
+ msg = "Possible spam signup (ip_in_blocklist) from " \
+ "#{user.email}: #{user_ip} (#{country_from_ip})"
+ ExceptionNotifier.notify_exception(Exception.new(msg), env: request.env)
+ end
+
+ if block_restricted_country_ips?
+ flash.now[:error] = _("Sorry, we're currently unable to create your " \
+ "account. Please try again later.")
+ render action: 'sign'
+ true
+ end
+ end
+
def set_request_from_foreign_country
@request_from_foreign_country =
country_from_ip != AlaveteliConfiguration.iso_country_code
@@ -537,6 +577,15 @@ def current_user_is_display_user
@user.try(:id) == @display_user.id
end
+ def show_about_me?
+ return true if @is_you
+ return false unless @display_user.get_about_me_for_html_display.present?
+ return false unless @display_user.active?
+ return true if @display_user.confirmed_not_spam?
+ return true if @user
+ false
+ end
+
# Redirects to front page later if nothing else specified
def generate_post_redirect_for_signup(redirect_to="/")
redirect_to = "/" if redirect_to.nil?
diff --git a/app/controllers/user_profile/about_me_controller.rb b/app/controllers/user_profile/about_me_controller.rb
index d132e656aa..981faaaeaa 100644
--- a/app/controllers/user_profile/about_me_controller.rb
+++ b/app/controllers/user_profile/about_me_controller.rb
@@ -32,7 +32,7 @@ def update
flash[:notice] = _("You have now changed the text about you on your profile.")
redirect_to user_url(@user)
else
- flash[:notice] = { :partial => "update_profile_text.html.erb" }
+ flash[:notice] = { :partial => "update_profile_text" }
redirect_to set_profile_photo_url
end
else
diff --git a/app/helpers/admin/bootstrap_helper.rb b/app/helpers/admin/bootstrap_helper.rb
new file mode 100644
index 0000000000..e5367a8795
--- /dev/null
+++ b/app/helpers/admin/bootstrap_helper.rb
@@ -0,0 +1,14 @@
+# Helpers for working with Bootstrap elements within the admin interface
+module Admin::BootstrapHelper
+ def nav_li(path)
+ tag.li class: nav_li_class(path) do
+ yield
+ end
+ end
+
+ private
+
+ def nav_li_class(path)
+ 'active' if current_page?(path)
+ end
+end
diff --git a/app/helpers/admin/censor_rules_helper.rb b/app/helpers/admin/censor_rules_helper.rb
new file mode 100644
index 0000000000..0c62648fb7
--- /dev/null
+++ b/app/helpers/admin/censor_rules_helper.rb
@@ -0,0 +1,12 @@
+# Helpers for dealing with CensorRules in the admin interface
+module Admin::CensorRulesHelper
+ def censor_rule_applies_to(censor_rule)
+ censorable = censor_rule.censorable
+ censorable ? both_links(censorable) : tag.strong('everything')
+ end
+
+ def censor_rule_applicable_class(censor_rule)
+ censorable = censor_rule.censorable
+ censorable ? censorable.class.to_s : 'Global'
+ end
+end
diff --git a/app/helpers/admin/link_helper.rb b/app/helpers/admin/link_helper.rb
new file mode 100644
index 0000000000..6abda26e8b
--- /dev/null
+++ b/app/helpers/admin/link_helper.rb
@@ -0,0 +1,55 @@
+# Helpers for rendering record links in the admin interface
+module Admin::LinkHelper
+ def both_links(record)
+ method = "#{record.class.to_s.underscore}_both_links"
+ send(method, record)
+ end
+
+ private
+
+ def info_request_both_links(info_request)
+ title = 'View request on public website'
+ icon = prominence_icon(info_request)
+
+ link_to(icon, request_path(info_request), title: title) + ' ' +
+ link_to(info_request.title, admin_request_path(info_request),
+ title: admin_title)
+ end
+
+ def info_request_batch_both_links(batch)
+ title = 'View batch on public website'
+ icon = prominence_icon(batch)
+
+ link_to(icon, batch, title: title) + ' ' + batch.title
+ end
+
+ def public_body_both_links(public_body)
+ title = 'View authority on public website'
+ icon = eye
+
+ link_to(icon, public_body_path(public_body), title: title) + ' ' +
+ link_to(public_body.name, admin_body_path(public_body),
+ title: admin_title)
+ end
+
+ def user_both_links(user)
+ title = 'View user on public website'
+ icon = prominence_icon(user)
+
+ link_to(icon, user_path(user), title: title) + ' ' +
+ link_to(user.name, admin_user_path(user), title: admin_title)
+ end
+
+ def comment_both_links(comment)
+ title = 'View comment on public website'
+ icon = prominence_icon(comment)
+
+ link_to(icon, comment_path(comment), title: title) + ' ' +
+ link_to(truncate(comment.body), edit_admin_comment_path(comment),
+ title: admin_title)
+ end
+
+ def admin_title
+ 'View full details'
+ end
+end
diff --git a/app/helpers/admin/translated_record_form.rb b/app/helpers/admin/translated_record_form.rb
index 6c45e1be13..db0ed2da7e 100644
--- a/app/helpers/admin/translated_record_form.rb
+++ b/app/helpers/admin/translated_record_form.rb
@@ -73,13 +73,8 @@ def locale_fields(t, locale)
end
def default_locale_error_messages
- if rails_upgrade?
- default_locale_errors = object.errors.reject do |error|
- error.attribute.starts_with?('translation')
- end
- else
- default_locale_errors =
- object.errors.reject { |attr, _| attr.to_s.starts_with?('translation') }
+ default_locale_errors = object.errors.reject do |error|
+ error.attribute.starts_with?('translation')
end
@template.concat(errors_for(default_locale, default_locale_errors))
@@ -98,22 +93,12 @@ def errors_for(locale, errors)
@template.concat(locale_name(locale))
ul = @template.tag.ul do
- if rails_upgrade?
- errors.each do |error|
- content = @template.tag.li do
- "#{ error.attribute } #{ error.message }".html_safe
- end
-
- @template.concat(content)
+ errors.each do |error|
+ content = @template.tag.li do
+ "#{ error.attribute } #{ error.message }".html_safe
end
- else
- errors.each do |attr, message|
- content = @template.tag.li do
- "#{ attr } #{ message }".html_safe
- end
- @template.concat(content)
- end
+ @template.concat(content)
end
end
diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb
index 921d7967a1..2b0fed840a 100644
--- a/app/helpers/admin_helper.rb
+++ b/app/helpers/admin_helper.rb
@@ -1,4 +1,9 @@
module AdminHelper
+ include Admin::BootstrapHelper
+ include Admin::CensorRulesHelper
+ include Admin::LinkHelper
+ include Admin::ProminenceHelper
+
def icon(name)
content_tag(:i, "", :class => "icon-#{name}")
end
@@ -19,28 +24,6 @@ def arrow_right
icon("arrow-right")
end
- def request_both_links(info_request)
- link_to(eye, request_path(info_request), :title => "view request on public website") + " " +
- link_to(info_request.title, admin_request_path(info_request), :title => "view full details")
- end
-
- def public_body_both_links(public_body)
- link_to(eye, public_body_path(public_body), :title => "view authority on public website") + " " +
- link_to(h(public_body.name), admin_body_path(public_body), :title => "view full details")
- end
-
- def user_both_links(user)
- link_to(eye, user_path(user), :title => "view user's page on public website") + " " +
- link_to(h(user.name), admin_user_path(user), :title => "view full details")
- end
-
- def comment_both_links(comment)
- link_to(eye, comment_path(comment),
- :title => "view comment on public website") + " " +
- link_to(h(truncate(comment.body)), edit_admin_comment_path(comment),
- :title => "view full details")
- end
-
def comment_visibility(comment)
comment.visible? ? 'Visible' : 'Hidden'
end
diff --git a/app/helpers/alaveteli_pro/batch_request_authority_searches_helper.rb b/app/helpers/alaveteli_pro/batch_request_authority_searches_helper.rb
index c09fe7f1b6..8b36f749d1 100644
--- a/app/helpers/alaveteli_pro/batch_request_authority_searches_helper.rb
+++ b/app/helpers/alaveteli_pro/batch_request_authority_searches_helper.rb
@@ -5,5 +5,34 @@ def batch_notes_allowed_tags
Alaveteli::Application.config.action_view.sanitized_allowed_tags -
%w(pre h1 h2 h3 h4 h5 h6 img blockquote html head body style)
end
+
+ def batch_authority_count
+ count = @draft_batch_request.public_bodies.count
+
+ tag_attributes = {
+ class: %w[batch-builder__actions__count],
+ data: {
+ message_template_zero: authority_count(count_override: 0),
+ message_template_one: authority_count(count_override: 1),
+ message_template_many: authority_count(count_override: 2)
+ }
+ }
+
+ content_tag :p, authority_count, tag_attributes
+ end
+
+ private
+
+ def authority_count(count_override: nil)
+ limit = AlaveteliConfiguration.pro_batch_authority_limit
+
+ count = count_override || @draft_batch_request.public_bodies.count
+ count_text = '{{count}}' if count_override
+ count_text ||= count
+
+ n_("{{count}} of {{limit}} authorities",
+ "{{count}} of {{limit}} authorities",
+ count, count: count_text, limit: limit)
+ end
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 87b78f91b9..04f6019936 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -51,14 +51,8 @@ def foi_error_messages_for(*params)
error_messages = "".html_safe
objects.each do |object|
- if rails_upgrade?
- object.errors.each do |error|
- error_messages << content_tag(:li, h(error.message))
- end
- else
- object.errors.each do |attr, message|
- error_messages << content_tag(:li, h(message))
- end
+ object.errors.each do |error|
+ error_messages << content_tag(:li, h(error.message))
end
end
@@ -140,9 +134,13 @@ def cache_if_caching_fragments(*args, &block)
end
end
- def render_flash(flash)
- flash = { :plain => flash } if flash.is_a?(String)
- render flash.deep_symbolize_keys
+ def inside_layout(layout = 'application', &block)
+ render inline: capture(&block), layout: "layouts/#{layout}"
+ end
+
+ def render_flash(message)
+ message = { plain: message } if message.is_a?(String)
+ render message.deep_symbolize_keys
end
# We only want to cache request lists that have a reasonable chance of not expiring
diff --git a/app/mailers/outgoing_mailer.rb b/app/mailers/outgoing_mailer.rb
index 1f27813d9f..bf11b55dbf 100644
--- a/app/mailers/outgoing_mailer.rb
+++ b/app/mailers/outgoing_mailer.rb
@@ -47,8 +47,8 @@ def self.name_and_email_for_followup(info_request, incoming_message_followup)
if incoming_message_followup.nil? || !incoming_message_followup.valid_to_reply_to?
return info_request.recipient_name_and_email
else
- # calling safe_mail_from from so censor rules are run
- return MailHandler.address_from_name_and_email(incoming_message_followup.safe_mail_from,
+ # calling safe_from_name from so censor rules are run
+ return MailHandler.address_from_name_and_email(incoming_message_followup.safe_from_name,
incoming_message_followup.from_email)
end
end
@@ -57,8 +57,8 @@ def self.name_for_followup(info_request, incoming_message_followup)
if incoming_message_followup.nil? || !incoming_message_followup.valid_to_reply_to?
return info_request.public_body.name
else
- # calling safe_mail_from from so censor rules are run
- return incoming_message_followup.safe_mail_from || info_request.public_body.name
+ # calling safe_from_name from so censor rules are run
+ return incoming_message_followup.safe_from_name || info_request.public_body.name
end
end
# Used when making list of followup places to remove duplicates
diff --git a/app/mailers/request_mailer.rb b/app/mailers/request_mailer.rb
index 73d52d3540..3e88f50bb7 100644
--- a/app/mailers/request_mailer.rb
+++ b/app/mailers/request_mailer.rb
@@ -354,7 +354,7 @@ def self.alert_new_response_reminders_internal(days_since, type_code)
info_requests = InfoRequest.
where_old_unclassified(days_since).
where(use_notifications: false).
- order('info_requests.id').
+ order(:id).
includes(:user)
info_requests.each do |info_request|
@@ -405,7 +405,7 @@ def self.alert_not_clarified_request
Time.zone.now - 3.days,
false
).
- includes(:user).order("info_requests.id")
+ includes(:user).order(:id)
for info_request in info_requests
alert_event_id = info_request.get_last_public_response_event_id
last_response_message = info_request.get_last_public_response
@@ -468,7 +468,7 @@ def self.alert_comment_on_request
InfoRequest.
includes(:info_request_events => :user_info_request_sent_alerts).
where(conditions).
- order('info_requests.id, info_request_events.created_at').
+ order(:id).merge(InfoRequestEvent.order(:created_at)).
references(:info_request_events)
info_requests.each do |info_request|
diff --git a/app/models/alaveteli_pro/request_filter.rb b/app/models/alaveteli_pro/request_filter.rb
index e82ce8d4eb..7f9f4b3f33 100644
--- a/app/models/alaveteli_pro/request_filter.rb
+++ b/app/models/alaveteli_pro/request_filter.rb
@@ -43,7 +43,8 @@ def results(user)
q: "%#{ search }%")
.references(:request_summary_categories)
request_summaries = filter_results(request_summaries)
- request_summaries.reorder("request_summaries.#{order_value}")
+ request_summaries.
+ merge(RequestSummary.order(order_value))
end
def filter_results(results)
diff --git a/app/models/censor_rule.rb b/app/models/censor_rule.rb
index c653c88b54..bd93a4cd77 100644
--- a/app/models/censor_rule.rb
+++ b/app/models/censor_rule.rb
@@ -23,7 +23,16 @@
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
class CensorRule < ApplicationRecord
+ DEFAULT_CANNED_REPLACEMENTS = [
+ _('[Personally Identifiable Information removed]'),
+ _('[name removed]'),
+ _('[extraneous material removed]'),
+ _('[potentially defamatory material removed]'),
+ _('[extraneous and potentially defamatory material removed]')
+ ].freeze
+
include AdminColumn
+
belongs_to :info_request,
:inverse_of => :censor_rules
belongs_to :user,
@@ -44,6 +53,10 @@ class CensorRule < ApplicationRecord
public_body_id: nil)
}
+ cattr_accessor :canned_replacements,
+ instance_writer: false,
+ default: DEFAULT_CANNED_REPLACEMENTS.dup
+
def apply_to_text(text_to_censor)
return nil if text_to_censor.nil?
text_to_censor.gsub(to_replace('UTF-8'), replacement)
@@ -89,6 +102,10 @@ def censorable_requests
end
end
+ def censorable
+ info_request || user || public_body || nil
+ end
+
private
def single_char_regexp
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 948b262841..8583da961e 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210114052
#
# Table name: comments
#
@@ -7,11 +7,11 @@
# user_id :integer not null
# info_request_id :integer
# body :text not null
-# visible :boolean default("true"), not null
+# visible :boolean default(TRUE), not null
# created_at :datetime not null
# updated_at :datetime not null
# locale :text default(""), not null
-# attention_requested :boolean default("false"), not null
+# attention_requested :boolean default(FALSE), not null
#
# models/comments.rb:
@@ -63,7 +63,7 @@ class Comment < ApplicationRecord
references(:embargoes)
}
- after_save :event_xapian_update
+ after_save :reindex_request_events
default_url_options[:host] = AlaveteliConfiguration.domain
@@ -97,15 +97,24 @@ def body
ret
end
+ def prominence
+ hidden? ? 'hidden' : 'normal'
+ end
+
def hidden?
!visible?
end
- # So when takes changes it updates, or when made invisble it vanishes
- def event_xapian_update
+ def reindex_request_events
info_request_events.find_each(&:xapian_mark_needs_index)
end
+ def event_xapian_update
+ warn 'DEPRECATION: Comment#event_xapian_update will be removed in 0.42. ' \
+ 'It has been replaced with Comment#reindex_request_events'
+ reindex_request_events
+ end
+
# Return body for display as HTML
def get_body_for_html_display
text = body.strip
@@ -187,6 +196,18 @@ def last_reported_at
last_report.try(:created_at)
end
+ def hide(editor:)
+ ActiveRecord::Base.transaction do
+ event_params = { comment_id: id,
+ editor: editor.url_name,
+ old_visible: visible?,
+ visible: false }
+
+ update!(visible: false)
+ info_request.log_event('hide_comment', event_params)
+ end
+ end
+
private
def check_body_has_content
diff --git a/app/models/concerns/taggable.rb b/app/models/concerns/taggable.rb
index 4a7445f5ad..0139429493 100644
--- a/app/models/concerns/taggable.rb
+++ b/app/models/concerns/taggable.rb
@@ -21,7 +21,7 @@ def self.tag_search_sql(tag)
select(1).
where("has_tag_string_tags.model_id = #{quoted_table_name}." \
"#{quoted_primary_key}").
- where("has_tag_string_tags.model = '#{self}'").
+ where("has_tag_string_tags.model_type = '#{self}'").
where(name: name)
scope = scope.where(value: value) if value
scope.to_sql
@@ -29,7 +29,7 @@ def self.tag_search_sql(tag)
private_class_method :tag_search_sql
def self.tags
- HasTagString::HasTagStringTag.where(model_id: all, model: to_s).
+ HasTagString::HasTagStringTag.where(model_id: all, model_type: to_s).
map(&:name_and_value)
end
end
diff --git a/app/models/foi_attachment.rb b/app/models/foi_attachment.rb
index 3f62af9509..2d5f5dfb33 100644
--- a/app/models/foi_attachment.rb
+++ b/app/models/foi_attachment.rb
@@ -29,6 +29,8 @@ class FoiAttachment < ApplicationRecord
belongs_to :incoming_message,
:inverse_of => :foi_attachments
+ has_one_attached :file, service: :attachments
+
validates_presence_of :content_type
validates_presence_of :filename
validates_presence_of :display_size
@@ -42,32 +44,54 @@ class FoiAttachment < ApplicationRecord
BODY_MAX_DELAY = 5
def directory
+ if file.attached?
+ warn <<~DEPRECATION.squish
+ [DEPRECATION] FoiAttachment#directory shouldn't be used when using
+ `ActiveStorage` backed file stores. This method will be removed
+ in 0.42.
+ DEPRECATION
+ return
+ end
+
base_dir = File.expand_path(File.join(File.dirname(__FILE__), "../../cache", "attachments_#{Rails.env}"))
return File.join(base_dir, self.hexdigest[0..2])
end
def filepath
+ if file.attached?
+ warn <<~DEPRECATION.squish
+ [DEPRECATION] FoiAttachment#filepath shouldn't be used when using
+ `ActiveStorage` backed file stores. This method will be removed
+ in 0.42.
+ DEPRECATION
+ return
+ end
+
File.join(self.directory, self.hexdigest)
end
def delete_cached_file!
- begin
- @cached_body = nil
- File.delete(self.filepath)
- rescue
+ @cached_body = nil
+
+ if file.attached?
+ file.purge
+ elsif File.exist?(filepath)
+ File.delete(filepath)
end
end
def body=(d)
self.hexdigest = Digest::MD5.hexdigest(d)
- if !File.exist?(self.directory)
- FileUtils.mkdir_p self.directory
- end
- File.open(self.filepath, "wb") { |file|
- file.write d
- }
- update_display_size!
+
+ ensure_filename!
+ file.attach(
+ io: StringIO.new(d.to_s),
+ filename: filename,
+ content_type: content_type
+ )
+
@cached_body = d.force_encoding("ASCII-8BIT")
+ update_display_size!
end
# raw body, encoded as binary
@@ -76,8 +100,12 @@ def body
tries = 0
delay = 1
begin
- @cached_body = File.open(filepath, "rb" ) { |file| file.read }
- rescue Errno::ENOENT
+ if file.attached?
+ @cached_body = file.download
+ else
+ @cached_body = File.open(filepath, "rb" ) { |file| file.read }
+ end
+ rescue Errno::ENOENT, ActiveStorage::FileNotFoundError
# we've lost our cached attachments for some reason. Reparse them.
if tries > BODY_MAX_TRIES
raise
@@ -89,6 +117,7 @@ def body
delay = BODY_MAX_DELAY if delay > BODY_MAX_DELAY
force = true
self.incoming_message.parse_raw_email!(force)
+ reload
retry
end
end
diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb
index a2d79c606e..f9eefe7920 100644
--- a/app/models/incoming_message.rb
+++ b/app/models/incoming_message.rb
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210120801
#
# Table name: incoming_messages
#
@@ -12,13 +12,14 @@
# cached_main_body_text_folded :text
# cached_main_body_text_unfolded :text
# subject :text
-# mail_from_domain :text
+# from_email_domain :text
# valid_to_reply_to :boolean
# last_parsed :datetime
-# mail_from :text
+# from_name :text
# sent_at :datetime
# prominence :string default("normal"), not null
# prominence_reason :text
+# from_email :text
#
# models/incoming_message.rb:
@@ -38,6 +39,7 @@
class IncomingMessage < ApplicationRecord
include AdminColumn
include MessageProminence
+ include CacheAttributesFromRawEmail
MAX_ATTACHMENT_TEXT_CLIPPED = 1000000 # 1Mb ish
@@ -55,9 +57,10 @@ class IncomingMessage < ApplicationRecord
:class_name => 'OutgoingMessage',
:dependent => :nullify
has_many :foi_attachments,
- -> { order('id') },
- :inverse_of => :incoming_message,
- :dependent => :destroy
+ -> { order(:id) },
+ inverse_of: :incoming_message,
+ dependent: :destroy,
+ autosave: true
# never really has many info_request_events, but could in theory
has_many :info_request_events,
:dependent => :destroy,
@@ -74,16 +77,19 @@ class IncomingMessage < ApplicationRecord
scope :pro, -> { joins(:info_request).merge(InfoRequest.pro) }
scope :unparsed, -> { where(last_parsed: nil) }
- delegate :from_email, to: :raw_email
+ cache_from_raw_email :subject, :sent_at,
+ :from_name, :from_email, :from_email_domain,
+ :valid_to_reply_to
+
delegate :message_id, to: :raw_email
delegate :multipart?, to: :raw_email
delegate :parts, to: :raw_email
delegate :legislation, to: :info_request
- # Given that there are in theory many info request events, a convenience method for
- # getting the response event
+ # Given that there are in theory many info request events, a convenience
+ # method for getting the response event.
def response_event
- self.info_request_events.detect { |e| e.event_type == 'response' }
+ info_request_events.where(event_type: 'response').first
end
def parse_raw_email!(force = nil)
@@ -95,19 +101,14 @@ def parse_raw_email!(force = nil)
end
if (!force.nil? || self.last_parsed.nil?)
ActiveRecord::Base.transaction do
- self.extract_attachments!
+ extract_attachments
self.sent_at = raw_email.date || created_at
self.subject = raw_email.subject
- self.mail_from = raw_email.from_name
- if from_email
- self.mail_from_domain =
- PublicBody.extract_domain_from_email(from_email)
- else
- self.mail_from_domain = ""
- end
+ self.from_name = raw_email.from_name
+ self.from_email = raw_email.from_email || ''
+ self.from_email_domain = raw_email.from_email_domain || ''
self.valid_to_reply_to = raw_email.valid_to_reply_to?
self.last_parsed = Time.zone.now
- self.foi_attachments.reload
self.save!
end
end
@@ -117,66 +118,12 @@ def destroy_email_file
raw_email.destroy_file_representation!
end
- # The cached fields mentioned in the previous comment
-
- # Public: Can this message be replied to?
- # Caches the value set by raw_email.valid_to_reply_to? in #parse_raw_email!
- # #valid_to_reply_to overrides the ActiveRecord provided #valid_to_reply_to
- #
- # Returns a Boolean
- def valid_to_reply_to
- parse_raw_email!
- super
- end
-
alias_method :valid_to_reply_to?, :valid_to_reply_to
- # Public: The date and time the email was sent. Uses the Date header if
- # present in the email, otherwise uses the record's created_at attribute.
- # #sent_at overrides the ActiveRecord provided #sent_at
- #
- # Returns an ActiveSupport::TimeWithZone
- def sent_at
- parse_raw_email!
- super
- end
-
- # Public: The subject of an email.
- # #subject overrides the ActiveRecord provided #subject
- #
- # Examples:
- #
- # # Subject: A response to your FOI request
- # incoming_message.subject
- # # => 'A response to your FOI request'
- #
- # # No subject header
- # incoming_message.subject
- # # => nil
- #
- # Returns a String or nil
- def subject
- parse_raw_email!
- super
- end
-
- # Public: The display name of the email sender.
- # #mail_from overrides the ActiveRecord provided #mail_from
- #
- # Examples:
- #
- # # From: John Doe
- # incoming_message.mail_from
- # # => 'John Doe'
- #
- # # From: john@example.com
- # incoming_message.mail_from
- # # => nil
- #
- # Returns a String or nil
def mail_from
- parse_raw_email!
- super
+ warn %q([DEPRECATION] IncomingMessage#mail_from will be removed in 0.42. It
+ has been replaced by IncomingMessage#from_name).squish
+ from_name
end
# Public: The display name of the email sender with the associated
@@ -186,42 +133,36 @@ def mail_from
#
# # Given a CensorRule that redacts the word 'Person':
#
- # incoming_message.mail_from
+ # incoming_message.from_name
# # => FOI Person
#
- # incoming_message.safe_mail_from
+ # incoming_message.safe_from_name
# # => FOI [REDACTED]
#
# Returns a String
def safe_mail_from
- if mail_from
- info_request.apply_censor_rules_to_text(mail_from)
- end
+ warn %q([DEPRECATION] IncomingMessage#safe_mail_from will be removed in
+ 0.42. It has been replaced by IncomingMessage#safe_from_name).squish
+ safe_from_name
+ end
+
+ def safe_from_name
+ info_request.apply_censor_rules_to_text(from_name) if from_name
end
- # Public: The domain part of the email address in the From header.
- # #mail_from_domain overrides the ActiveRecord provided #mail_from_domain
- #
- # # From: John Doe
- # incoming_message.mail_from_domain
- # # => 'example.com'
- #
- # # No From header
- # incoming_message.mail_from_domain
- # # => ''
- #
- # Returns a String
def mail_from_domain
- parse_raw_email!
- super
+ warn %q([DEPRECATION] IncomingMessage#mail_from_domain will be removed in
+ 0.42. It has been replaced by
+ IncomingMessage#from_email_domain).squish
+ from_email_domain
end
def specific_from_name?
- !safe_mail_from.nil? && safe_mail_from.strip != info_request.public_body.name.strip
+ !safe_from_name.nil? && safe_from_name.strip != info_request.public_body.name.strip
end
def from_public_body?
- safe_mail_from.nil? || (mail_from_domain == info_request.public_body.request_email_domain)
+ safe_from_name.nil? || (from_email_domain == info_request.public_body.request_email_domain)
end
# This method updates the cached column of the InfoRequest that
@@ -517,11 +458,10 @@ def get_main_body_text_part(leaves=[])
end
# Returns attachments that are uuencoded in main body part
- def _uudecode_and_save_attachments(text)
+ def _uudecode_attachments(text, start_part_number)
# Find any uudecoded things buried in it, yeuchly
uus = text.scan(/^begin.+^`\n^end\n/m)
- attachments = []
- uus.each do |uu|
+ uus.map.with_index do |uu, index|
# Decode the string
content = uu.sub(/\Abegin \d+ [^\n]*\n/, '').unpack('u').first
# Make attachment type from it, working out filename and mime type
@@ -534,14 +474,15 @@ def _uudecode_and_save_attachments(text)
content_type = 'application/octet-stream'
end
hexdigest = Digest::MD5.hexdigest(content)
- attachment = foi_attachments.find_or_create_by(:hexdigest => hexdigest)
- attachment.update(:filename => filename,
- :content_type => content_type,
- :body => content)
- attachment.save!
- attachments << attachment
+ attachment = foi_attachments.find_or_initialize_by(hexdigest: hexdigest)
+ attachment.attributes = {
+ filename: filename,
+ content_type: content_type,
+ body: content,
+ url_part_number: start_part_number + index + 1
+ }
+ attachment
end
- attachments
end
def get_attachments_for_display
@@ -556,48 +497,41 @@ def get_attachments_for_display
end
def extract_attachments!
- force = true
+ extract_attachments
+ save!
+ end
+
+ def extract_attachments
_mail = raw_email.mail!
attachment_attributes = MailHandler.get_attachment_attributes(_mail)
- attachments = []
- attachment_attributes.each do |attrs|
- attachment = self.foi_attachments.find_or_create_by(:hexdigest => attrs[:hexdigest])
- attachment.update(attrs)
- attachment.save!
- attachments << attachment
+ attachment_attributes = attachment_attributes.inject({}) do |memo, attrs|
+ memo[attrs[:hexdigest]] = attrs
+ memo
end
- # Reload to refresh newly created foi_attachments
- self.reload
+ attachments = attachment_attributes.map do |hexdigest, attrs|
+ attachment = foi_attachments.find_or_initialize_by(hexdigest: hexdigest)
+ attachment.attributes = attrs
+ attachment
+ end
- # get the main body part from the set of attachments we just created,
- # not from the self.foi_attachments association - some of the total set
- # of self.foi_attachments may now be obsolete. Sometimes (e.g. when
- # parsing mail from Apple Mail) we can end up with less attachments
- # because the hexdigest of an attachment is identical.
+ # Get the main body part from the set of attachments not from the
+ # foi_attachments association - some of the total set of foi_attachments may
+ # now be obsolete. Sometimes (e.g. when parsing mail from Apple Mail) we can
+ # end up with less attachments because the hexdigest of an attachment is
+ # identical.
main_part = get_main_body_text_part(attachments)
- # we don't use get_main_body_text_internal, as we want to avoid charset
- # conversions, since _uudecode_and_save_attachments needs to deal with those.
+
+ # We don't use get_main_body_text_internal, as we want to avoid charset
+ # conversions, since _uudecode_attachments needs to deal with those.
# e.g. for https://secure.mysociety.org/admin/foi/request/show_raw_email/24550
- if !main_part.nil?
- uudecoded_attachments = _uudecode_and_save_attachments(main_part.body)
+ if main_part
c = _mail.count_first_uudecode_count
- for uudecode_attachment in uudecoded_attachments
- c += 1
- uudecode_attachment.url_part_number = c
- uudecode_attachment.save!
- attachments << uudecode_attachment
- end
+ attachments += _uudecode_attachments(main_part.body, c)
end
- attachment_ids = attachments.map { |attachment| attachment.id }
- # now get rid of any attachments we no longer have
- FoiAttachment.
- where(
- ["id NOT IN (?) AND incoming_message_id = ?",
- attachment_ids,
- self.id]
- ).destroy_all
+ # Purge old attachments that have been rebuilt with a new hexdigest
+ (foi_attachments - attachments).each(&:mark_for_destruction)
end
# Returns body text as HTML with quotes flattened, and emails removed.
@@ -738,10 +672,6 @@ def get_present_file_extensions
end
return ret.keys.join(" ")
end
- # Return space separated list of all file extensions known
- def self.get_all_file_extensions
- return AlaveteliFileTypes.all_extensions.join(" ")
- end
def refusals
legislation_references.select(&:refusal?).map(&:parent).uniq(&:to_s)
diff --git a/app/models/incoming_message/cache_attributes_from_raw_email.rb b/app/models/incoming_message/cache_attributes_from_raw_email.rb
new file mode 100644
index 0000000000..cfdc421540
--- /dev/null
+++ b/app/models/incoming_message/cache_attributes_from_raw_email.rb
@@ -0,0 +1,18 @@
+# Cache attributes from the associated RawEmail before calling the attribute's
+# accessor
+module IncomingMessage::CacheAttributesFromRawEmail
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def cache_from_raw_email(*attrs)
+ attrs.each { |attr| cache_attribute_from_raw_email(attr) }
+ end
+
+ def cache_attribute_from_raw_email(attr)
+ define_method(attr) do
+ parse_raw_email!
+ super()
+ end
+ end
+ end
+end
diff --git a/app/models/info_request.rb b/app/models/info_request.rb
index ffdc7cc6b6..9225a1d558 100644
--- a/app/models/info_request.rb
+++ b/app/models/info_request.rb
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210114052
#
# Table name: info_requests
#
@@ -10,7 +10,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# described_state :string not null
-# awaiting_description :boolean default("false"), not null
+# awaiting_description :boolean default(FALSE), not null
# prominence :string default("normal"), not null
# url_title :text not null
# law_used :string default("foi"), not null
@@ -19,19 +19,19 @@
# idhash :string not null
# external_user_name :string
# external_url :string
-# attention_requested :boolean default("false")
-# comments_allowed :boolean default("true"), not null
+# attention_requested :boolean default(FALSE)
+# comments_allowed :boolean default(TRUE), not null
# info_request_batch_id :integer
# last_public_response_at :datetime
-# reject_incoming_at_mta :boolean default("false"), not null
-# rejected_incoming_count :integer default("0")
+# reject_incoming_at_mta :boolean default(FALSE), not null
+# rejected_incoming_count :integer default(0)
# date_initial_request_last_sent_at :date
# date_response_required_by :date
# date_very_overdue_after :date
# last_event_forming_initial_request_id :integer
# use_notifications :boolean
# last_event_time :datetime
-# incoming_messages_count :integer default("0")
+# incoming_messages_count :integer default(0)
# public_token :string
#
@@ -49,6 +49,7 @@ class InfoRequest < ApplicationRecord
include InfoRequest::PublicToken
include InfoRequest::Sluggable
include InfoRequest::TitleValidation
+ include Taggable
@non_admin_columns = %w(title url_title)
@additional_admin_columns = %w(rejected_incoming_count)
@@ -73,22 +74,22 @@ class InfoRequest < ApplicationRecord
:unless => Proc.new { |info_request| info_request.is_batch_request_template? }
has_many :info_request_events,
- -> { order('created_at, id') },
+ -> { order(:created_at, :id) },
:inverse_of => :info_request,
:dependent => :destroy
has_many :outgoing_messages,
- -> { order('created_at') },
+ -> { order(:created_at) },
:inverse_of => :info_request,
:dependent => :destroy
has_many :incoming_messages,
- -> { order('created_at') },
+ -> { order(:created_at) },
:inverse_of => :info_request,
:dependent => :destroy
has_many :user_info_request_sent_alerts,
:inverse_of => :info_request,
:dependent => :destroy
has_many :track_things,
- -> { order('created_at desc') },
+ -> { order(created_at: :desc) },
:inverse_of => :info_request,
:dependent => :destroy
has_many :widget_votes,
@@ -100,11 +101,11 @@ class InfoRequest < ApplicationRecord
inverse_of: :citable,
dependent: :destroy
has_many :comments,
- -> { order('created_at') },
+ -> { order(:created_at) },
:inverse_of => :info_request,
:dependent => :destroy
has_many :censor_rules,
- -> { order('created_at desc') },
+ -> { order(created_at: :desc) },
:inverse_of => :info_request,
:dependent => :destroy
has_many :mail_server_logs,
@@ -129,8 +130,6 @@ class InfoRequest < ApplicationRecord
attr_accessor :is_batch_request_template
attr_reader :followup_bad_reason
- has_tag_string
-
scope :internal, -> { where.not(user_id: nil) }
scope :external, -> { where(user_id: nil) }
@@ -631,7 +630,7 @@ def self.recent_requests
def self.find_in_state(state)
where(:described_state => state).
- order('last_event_time')
+ order(:last_event_time)
end
def self.log_overdue_events
@@ -774,9 +773,7 @@ def user_json_for_api
end
def reindex_request_events
- info_request_events.find_each do |event|
- event.xapian_mark_needs_index
- end
+ info_request_events.find_each(&:xapian_mark_needs_index)
end
# Force reindex when tag string changes
@@ -1208,14 +1205,14 @@ def calculate_date_very_overdue_after
def last_embargo_set_event
info_request_events.
where(:event_type => 'set_embargo').
- reorder('created_at DESC').
+ reorder(created_at: :desc).
first
end
def last_embargo_expire_event
info_request_events.
where(:event_type => 'expire_embargo').
- reorder('created_at DESC').
+ reorder(created_at: :desc).
first
end
@@ -1448,7 +1445,7 @@ def who_can_followup_to(skip_message = nil)
if incoming_message == skip_message
next
end
- incoming_message.safe_mail_from
+ incoming_message.safe_from_name
next if ! incoming_message.is_public?
@@ -1761,12 +1758,10 @@ def receive_mail_from_source?(source)
true
elsif feature_enabled?(:accept_mail_from_anywhere)
true
+ elsif user.features.enabled?(:accept_mail_from_poller)
+ source == :poller
else
- if feature_enabled?(:accept_mail_from_poller, user)
- source == :poller
- else
- source == :mailin
- end
+ source == :mailin
end
end
@@ -1866,7 +1861,8 @@ def set_law_used
def set_use_notifications
if use_notifications.nil?
- self.use_notifications = feature_enabled?(:notifications, user) && \
+ self.use_notifications = user &&
+ user.features.enabled?(:notifications) && \
info_request_batch_id.present?
end
return true
diff --git a/app/models/info_request_batch.rb b/app/models/info_request_batch.rb
index 022986b321..b78a7fc18a 100644
--- a/app/models/info_request_batch.rb
+++ b/app/models/info_request_batch.rb
@@ -36,8 +36,11 @@ class InfoRequestBatch < ApplicationRecord
end
}, :inverse_of => :info_request_batches
+ attr_accessor :ignore_existing_batch
+
validates_presence_of :user
validates_presence_of :body
+ validates_absence_of :existing_batch, unless: -> { ignore_existing_batch }
strip_attributes only: %i[embargo_duration]
@@ -58,18 +61,30 @@ def self.send_batches
end
end
- # When constructing a new batch, use this to check user hasn't double submitted.
- def self.find_existing(user, title, body, public_body_ids)
+ def self.with_body(body)
+ where("regexp_replace(info_request_batches.body, '[[:space:]]', '', 'g') =
+ regexp_replace(?, '[[:space:]]', '', 'g')", body)
+ end
+
+ # When constructing a new batch, use this to check user hasn't double
+ # submitted.
+ def self.find_existing(user, title, body, public_body_ids, id: nil)
conditions = {
- :user_id => user,
- :title => title,
- :body => body,
- :info_request_batches_public_bodies => {
- :public_body_id => public_body_ids
+ user_id: user,
+ title: title,
+ info_request_batches_public_bodies: {
+ public_body_id: public_body_ids
}
}
- includes(:public_bodies).where(conditions).references(:public_bodies).first
+ scope = includes(:public_bodies).
+ where(conditions).
+ with_body(body).
+ references(:public_bodies)
+
+ scope = scope.where.not(id: id) if id
+
+ scope.first
end
# Create a new batch from the supplied draft version
@@ -81,6 +96,10 @@ def self.from_draft(draft)
:embargo_duration => draft.embargo_duration)
end
+ def existing_batch
+ self.class.find_existing(user, title, body, public_body_ids, id: id)
+ end
+
# Create a batch of information requests and sends them to public bodies
def create_batch!
requestable_public_bodies.each do |public_body|
@@ -93,7 +112,7 @@ def create_batch!
# Sleep between requests in production, in case we're sending a huge
# batch which may result in a torrent of auto-replies coming back to
# us and overloading the server.
- uses_poller = feature_enabled?(:accept_mail_from_poller, user)
+ uses_poller = user.features.enabled?(:accept_mail_from_poller)
sleep 60 if Rails.env.production? && !uses_poller
end
reload
@@ -287,4 +306,8 @@ def is_owning_user?(user)
return false unless user
user.id == user_id || user.owns_every_request?
end
+
+ def prominence
+ 'normal'
+ end
end
diff --git a/app/models/mail_server_log/exim_delivery_status/translated_constants.rb b/app/models/mail_server_log/exim_delivery_status/translated_constants.rb
deleted file mode 100644
index 7f537281c8..0000000000
--- a/app/models/mail_server_log/exim_delivery_status/translated_constants.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- SkipSchemaAnnotations
-class MailServerLog::EximDeliveryStatus
- module TranslatedConstants
-
- def self.humanized
- {
- :delivered => _('This message has been delivered.'),
- :failed => _('This message could not be delivered.'),
- :sent => _('This message has been sent.')
- }.freeze
- end
-
- end
-end
diff --git a/app/models/mail_server_log/postfix_delivery_status/translated_constants.rb b/app/models/mail_server_log/postfix_delivery_status/translated_constants.rb
deleted file mode 100644
index 81aef8a23b..0000000000
--- a/app/models/mail_server_log/postfix_delivery_status/translated_constants.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- SkipSchemaAnnotations
-class MailServerLog::PostfixDeliveryStatus
- module TranslatedConstants
-
- def self.humanized
- {
- :delivered => _('This message has been delivered.'),
- :failed => _('This message could not be delivered.'),
- :sent => _('This message has been sent.')
- }.freeze
- end
-
- end
-end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index a2f132d050..dc3cc7d024 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -1,17 +1,17 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210114052
#
# Table name: notifications
#
# id :integer not null, primary key
# info_request_event_id :integer not null
# user_id :integer not null
-# frequency :integer default("0"), not null
+# frequency :integer default("instantly"), not null
# seen_at :datetime
# send_after :datetime not null
# created_at :datetime not null
# updated_at :datetime not null
-# expired :boolean default("false")
+# expired :boolean default(FALSE)
#
class Notification < ApplicationRecord
diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb
index 8c007d7e8b..a31a56d7f0 100644
--- a/app/models/outgoing_message.rb
+++ b/app/models/outgoing_message.rb
@@ -61,6 +61,8 @@ class OutgoingMessage < ApplicationRecord
:inverse_of => :outgoing_message,
:dependent => :destroy
+ delegate :public_body, to: :info_request, private: true, allow_nil: true
+
after_initialize :set_default_letter
# reindex if body text is edited (e.g. by admin interface)
after_update :xapian_reindex_after_update
@@ -141,8 +143,8 @@ def from
# Returns a String
def to
if replying_to_incoming_message?
- # calling safe_mail_from from so censor rules are run
- MailHandler.address_from_name_and_email(incoming_message_followup.safe_mail_from,
+ # calling safe_from_name from so censor rules are run
+ MailHandler.address_from_name_and_email(incoming_message_followup.safe_from_name,
incoming_message_followup.from_email)
else
info_request.recipient_name_and_email
@@ -260,7 +262,7 @@ def sendable?
# Returns an Array
def smtp_message_ids
info_request_events.
- order('created_at ASC').
+ order(:created_at).
map { |event| event.params[:smtp_message_id] }.
compact.
map do |smtp_id|
@@ -342,8 +344,10 @@ def get_text_for_indexing(strip_salutation = true, opts = {})
text = body(opts).strip
end
- # Remove salutation
- text.sub!(/Dear .+,/, "") if strip_salutation
+ if strip_salutation && public_body
+ salutation = self.class.default_salutation(public_body)
+ text.sub!(/#{Regexp.escape(salutation)}\s*/, '')
+ end
# Remove email addresses from display/index etc.
self.remove_privacy_sensitive_things!(text)
@@ -427,7 +431,7 @@ def default_message_replacements
OutgoingMailer.
name_for_followup(info_request, incoming_message_followup)
else
- info_request.try(:public_body).try(:name)
+ public_body&.name
end
opts[:letter] = default_letter if default_letter
@@ -438,7 +442,7 @@ def default_message_replacements
def replying_to_incoming_message?
message_type == 'followup' &&
incoming_message_followup &&
- incoming_message_followup.safe_mail_from &&
+ incoming_message_followup.safe_from_name &&
incoming_message_followup.valid_to_reply_to?
end
diff --git a/app/models/outgoing_message/snippet.rb b/app/models/outgoing_message/snippet.rb
index 82a26492bf..40215a9d8b 100644
--- a/app/models/outgoing_message/snippet.rb
+++ b/app/models/outgoing_message/snippet.rb
@@ -1,14 +1,13 @@
# == Schema Information
-# Schema version: 20210928115500
+# Schema version: 20220210114052
#
# Table name: outgoing_message_snippets
#
-# id :bigint not null, primary key
-# created_at :datetime not null
-# updated_at :datetime not null
-# outgoing_message_snippet_id :bigint not null
-# name :string
-# body :text
+# id :bigint not null, primary key
+# created_at :datetime not null
+# updated_at :datetime not null
+# name :string
+# body :text
#
##
diff --git a/app/models/profile_photo.rb b/app/models/profile_photo.rb
index c8ef69525e..65559da8c7 100644
--- a/app/models/profile_photo.rb
+++ b/app/models/profile_photo.rb
@@ -1,12 +1,12 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210114052
#
# Table name: profile_photos
#
# id :integer not null, primary key
# data :binary not null
# user_id :integer
-# draft :boolean default("false"), not null
+# draft :boolean default(FALSE), not null
# created_at :datetime
# updated_at :datetime
#
diff --git a/app/models/public_body.rb b/app/models/public_body.rb
index 04363b4503..d11e58fd71 100644
--- a/app/models/public_body.rb
+++ b/app/models/public_body.rb
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210114052
#
# Table name: public_bodies
#
@@ -11,14 +11,13 @@
# updated_at :datetime not null
# home_page :text
# api_key :string not null
-# info_requests_count :integer default("0"), not null
+# info_requests_count :integer default(0), not null
# disclosure_log :text
# info_requests_successful_count :integer
# info_requests_not_held_count :integer
# info_requests_overdue_count :integer
# info_requests_visible_classified_count :integer
-# info_requests_visible_count :integer default("0"), not null
-# public_body_id :integer not null
+# info_requests_visible_count :integer default(0), not null
# name :text
# short_name :text
# request_email :text
@@ -60,26 +59,26 @@ class ImportCSVDryRun < StandardError; end
end
has_many :info_requests,
- -> { order('created_at desc') },
+ -> { order(created_at: :desc) },
:inverse_of => :public_body
has_many :track_things,
- -> { order('created_at desc') },
+ -> { order(created_at: :desc) },
:inverse_of => :public_body,
:dependent => :destroy
has_many :censor_rules,
- -> { order('created_at desc') },
+ -> { order(created_at: :desc) },
:inverse_of => :public_body,
:dependent => :destroy
has_many :track_things_sent_emails,
- -> { order('created_at DESC') },
+ -> { order(created_at: :desc) },
:inverse_of => :public_body,
:dependent => :destroy
has_many :public_body_change_requests,
- -> { order('created_at DESC') },
+ -> { order(created_at: :desc) },
:inverse_of => :public_body,
:dependent => :destroy
has_many :draft_info_requests,
- -> { order('created_at DESC') },
+ -> { order(created_at: :desc) },
:inverse_of => :public_body
has_and_belongs_to_many :info_request_batches,
@@ -169,7 +168,7 @@ class Translation
# We could add an `extend_version_class` option pretty trivially by
# following the pattern for the existing `extend` option.
#
- # [1] http://git.io/vIetK
+ # [1] https://github.com/technoweenie/acts_as_versioned/blob/63b1fc8529/lib/acts_as_versioned.rb#L98-L118
class Version
before_save :copy_translated_attributes
@@ -217,6 +216,10 @@ def compare(previous = nil, &block)
end
changes
end
+
+ def editor
+ User.find_by(url_name: last_edit_editor)
+ end
end
# Public: Search for Public Bodies whose name, short_name, request_email or
@@ -236,7 +239,7 @@ def self.search(query, locale = AlaveteliLocalization.locale)
OR lower(has_tag_string_tags.name) like lower('%'||?||'%' )
)
AND has_tag_string_tags.model_id = public_bodies.id
- AND has_tag_string_tags.model = 'PublicBody'
+ AND has_tag_string_tags.model_type = 'PublicBody'
AND (public_body_translations.locale = ?)
SQL
@@ -251,7 +254,7 @@ def self.with_domain(domain)
with_translations(AlaveteliLocalization.locale).
where("lower(public_body_translations.request_email) " \
"like lower('%'||?||'%')", domain).
- order('public_body_translations.name')
+ merge(PublicBody::Translation.order(:name))
end
def set_api_key
@@ -583,14 +586,9 @@ def import_values_from_csv_row(row, line, name, options)
begin
save!
rescue ActiveRecord::RecordInvalid
- if rails_upgrade?
- errors.each do |error|
- options[:errors].push "error: line #{ line }: #{ error.full_message } for authority '#{ name }'"
- end
- else
- errors.full_messages.each do |msg|
- options[:errors].push "error: line #{ line }: #{ msg } for authority '#{ name }'"
- end
+ errors.each do |error|
+ options[:errors].push "error: line #{ line }: " \
+ "#{ error.full_message } for authority '#{ name }'"
end
next
end
@@ -724,7 +722,7 @@ def self.where_clause_for_stats(minimum_requests, total_column)
# exclude any that are tagged with 'test' - we use a
# sub-select to find the IDs of those public bodies.
test_tagged_query = "SELECT model_id FROM has_tag_string_tags" \
- " WHERE model = 'PublicBody' AND name = 'test'"
+ " WHERE model_type = 'PublicBody' AND name = 'test'"
"#{total_column} >= #{minimum_requests} " \
"AND id NOT IN (#{test_tagged_query})"
end
@@ -813,7 +811,7 @@ def self.popular_bodies(locale)
bodies = visible.
where('public_body_translations.locale = ?',
underscore_locale).
- order("info_requests_visible_count desc").
+ order(info_requests_visible_count: :desc).
limit(32).
joins(:translations)
else
@@ -877,7 +875,7 @@ def self.with_query(query, tag)
where("(#{get_public_body_list_translated_condition('current_locale', has_first_letter)}) OR " \
"(#{get_public_body_list_translated_condition('default_locale', has_first_letter)}) ", where_parameters).
where('COALESCE(current_locale.name, default_locale.name) IS NOT NULL').
- order('display_name')
+ order(:display_name)
else
# The simpler case where we're just searching in the current locale:
where_condition = get_public_body_list_translated_condition('public_body_translations', has_first_letter, true)
@@ -889,7 +887,7 @@ def self.with_query(query, tag)
else
where(where_condition, where_parameters).
joins(:translations).
- order('public_body_translations.name')
+ merge(PublicBody::Translation.order(:name))
end
end
end
diff --git a/app/models/public_body_category.rb b/app/models/public_body_category.rb
index 059e0548cc..b162a53e14 100644
--- a/app/models/public_body_category.rb
+++ b/app/models/public_body_category.rb
@@ -1,46 +1,55 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210114052
#
# Table name: public_body_categories
#
-# id :integer not null, primary key
-# category_tag :text not null
-# created_at :datetime
-# updated_at :datetime
-# public_body_category_id :integer not null
-# title :text
-# description :text
+# id :integer not null, primary key
+# category_tag :text not null
+# created_at :datetime
+# updated_at :datetime
+# title :text
+# description :text
#
class PublicBodyCategory < ApplicationRecord
has_many :public_body_category_links,
- :inverse_of => :public_body_category,
- :dependent => :destroy
+ inverse_of: :public_body_category,
+ dependent: :destroy
+
has_many :public_body_headings,
- :through => :public_body_category_links
+ through: :public_body_category_links
+
+ has_many :tags,
+ foreign_key: :name,
+ primary_key: :category_tag,
+ class_name: 'HasTagString::HasTagStringTag'
+
+ has_many :public_bodies,
+ through: :tags,
+ source: :model,
+ source_type: 'PublicBody'
translates :title, :description
- validates_uniqueness_of :category_tag, :message => 'Tag is already taken'
- validates_presence_of :title, :message => "Title can't be blank"
- validates_presence_of :category_tag, :message => "Tag can't be blank"
- validates_presence_of :description, :message => "Description can't be blank"
+ validates_uniqueness_of :category_tag, message: 'Tag is already taken'
+ validates_presence_of :title, message: "Title can't be blank"
+ validates_presence_of :category_tag, message: "Tag can't be blank"
+ validates_presence_of :description, message: "Description can't be blank"
include Translatable
def self.get
- locale = AlaveteliLocalization.locale || default_locale || ""
+ locale = AlaveteliLocalization.locale || default_locale || ''
categories = CategoryCollection.new
+
AlaveteliLocalization.with_locale(locale) do
- headings = PublicBodyHeading.by_display_order
- headings.each do |heading|
+ PublicBodyHeading.by_display_order.each do |heading|
categories << heading.name
+
heading.public_body_categories.each do |category|
- categories << [
- category.category_tag,
- category.title,
- category.description
- ]
+ categories << [category.category_tag,
+ category.title,
+ category.description]
end
end
end
@@ -48,19 +57,23 @@ def self.get
end
def self.without_headings
- sql = %Q| SELECT * FROM public_body_categories pbc
- WHERE pbc.id NOT IN (
- SELECT public_body_category_id AS id
- FROM public_body_category_links
- ) |
- PublicBodyCategory.find_by_sql(sql)
+ PublicBodyCategory.find_by_sql(<<~SQL)
+ SELECT * FROM public_body_categories pbc
+ WHERE pbc.id NOT IN (
+ SELECT public_body_category_id AS id
+ FROM public_body_category_links
+ )
+ SQL
end
end
PublicBodyCategory::Translation.class_eval do
- with_options :if => lambda { |t| !t.default_locale? && t.required_attribute_submitted? } do |required|
- required.validates :title, :presence => { :message => "Title can't be blank" }
- required.validates :description, :presence => { :message => "Description can't be blank" }
+ with_options if: ->(t) {
+ !t.default_locale? && t.required_attribute_submitted?
+ } do |required|
+ required.validates :title, presence: { message: "Title can't be blank" }
+ required.validates :description,
+ presence: { message: "Description can't be blank" }
end
def default_locale?
@@ -72,5 +85,4 @@ def required_attribute_submitted?
!read_attribute(attribute).blank?
end
end
-
end
diff --git a/app/models/public_body_category_link.rb b/app/models/public_body_category_link.rb
index 500f067092..8a6b799bcb 100644
--- a/app/models/public_body_category_link.rb
+++ b/app/models/public_body_category_link.rb
@@ -13,27 +13,53 @@
class PublicBodyCategoryLink < ApplicationRecord
belongs_to :public_body_category,
- :inverse_of => :public_body_category_links
+ inverse_of: :public_body_category_links
+
belongs_to :public_body_heading,
- :inverse_of => :public_body_category_links
+ inverse_of: :public_body_category_links
validates_presence_of :public_body_category
validates_presence_of :public_body_heading
- validates :category_display_order, :numericality => { :only_integer => true,
- :message => 'Display order must be a number' }
- before_validation :on => :create do
- unless self.category_display_order
- self.category_display_order = PublicBodyCategoryLink.next_display_order(public_body_heading_id)
- end
+ validates :category_display_order, numericality: {
+ only_integer: true, message: 'Display order must be a number'
+ }
+
+ before_validation on: :create do
+ self.category_display_order ||=
+ self.class.next_display_order(public_body_heading)
+ end
+
+ scope :for_heading, ->(public_body_heading) do
+ where(public_body_heading: public_body_heading).
+ order(:category_display_order)
end
- def self.next_display_order(heading_id)
- if last = where(:public_body_heading_id => heading_id).order(:category_display_order).last
- last.category_display_order + 1
+ def self.next_display_order(public_body_heading)
+ last_record = for_heading(public_body_heading).last
+
+ if last_record
+ last_record.category_display_order + 1
else
0
end
end
+ def self.by_display_order
+ headings_table = Arel::Table.new(:public_body_headings)
+ links_table = Arel::Table.new(:public_body_category_links)
+
+ PublicBodyCategoryLink.
+ distinct.
+ select(headings_table[:display_order], links_table[Arel.star]).
+ joins(:public_body_heading).
+ merge(PublicBodyHeading.by_display_order).
+ joins(public_body_category: :public_bodies).
+ merge(PublicBody.is_requestable).
+ order(:category_display_order).
+ preload(
+ public_body_heading: :translations,
+ public_body_category: :translations
+ )
+ end
end
diff --git a/app/models/public_body_change_request.rb b/app/models/public_body_change_request.rb
index 378f0c056a..ff652a104a 100644
--- a/app/models/public_body_change_request.rb
+++ b/app/models/public_body_change_request.rb
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210114052
#
# Table name: public_body_change_requests
#
@@ -12,7 +12,7 @@
# public_body_email :string
# source_url :text
# notes :text
-# is_open :boolean default("true"), not null
+# is_open :boolean default(TRUE), not null
# created_at :datetime not null
# updated_at :datetime not null
#
@@ -37,10 +37,10 @@ class PublicBodyChangeRequest < ApplicationRecord
validate :body_email_format, :unless => proc { |change_request| change_request.public_body_email.blank? }
scope :new_body_requests, -> {
- where(public_body_id: nil).order("created_at")
+ where(public_body_id: nil).order(:created_at)
}
scope :body_update_requests, -> {
- where("public_body_id IS NOT NULL").order("created_at")
+ where("public_body_id IS NOT NULL").order(:created_at)
}
singleton_class.undef_method :open # Undefine Kernel.open to avoid warning
diff --git a/app/models/public_body_heading.rb b/app/models/public_body_heading.rb
index 71d9d95b38..2ba476523b 100644
--- a/app/models/public_body_heading.rb
+++ b/app/models/public_body_heading.rb
@@ -1,35 +1,37 @@
# == Schema Information
-# Schema version: 20210114161442
+# Schema version: 20220210114052
#
# Table name: public_body_headings
#
-# id :integer not null, primary key
-# display_order :integer
-# created_at :datetime
-# updated_at :datetime
-# public_body_heading_id :integer not null
-# name :text
+# id :integer not null, primary key
+# display_order :integer
+# created_at :datetime
+# updated_at :datetime
+# name :text
#
class PublicBodyHeading < ApplicationRecord
has_many :public_body_category_links,
- :inverse_of => :public_body_heading,
- :dependent => :destroy
+ inverse_of: :public_body_heading,
+ dependent: :destroy
+
has_many :public_body_categories,
- -> { order('public_body_category_links.category_display_order') },
- :through => :public_body_category_links
+ -> { merge(PublicBodyCategoryLink.order(:category_display_order)) },
+ through: :public_body_category_links
- scope :by_display_order, -> { order('display_order ASC') }
+ scope :by_display_order, -> { order(:display_order) }
translates :name
- validates_uniqueness_of :name, :message => 'Name is already taken'
- validates_presence_of :name, :message => 'Name can\'t be blank'
- validates :display_order, :numericality => { :only_integer => true,
- :message => 'Display order must be a number' }
+ validates_uniqueness_of :name, message: 'Name is already taken'
+ validates_presence_of :name, message: "Name can't be blank"
+
+ validates :display_order, numericality: {
+ only_integer: true, message: 'Display order must be a number'
+ }
- before_validation :on => :create do
- unless self.display_order
+ before_validation on: :create do
+ unless display_order
self.display_order = PublicBodyHeading.next_display_order
end
end
diff --git a/app/models/raw_email.rb b/app/models/raw_email.rb
index b329aa9012..cb14dbbe41 100644
--- a/app/models/raw_email.rb
+++ b/app/models/raw_email.rb
@@ -20,6 +20,8 @@ class RawEmail < ApplicationRecord
has_one :incoming_message,
:inverse_of => :raw_email
+ has_one_attached :file, service: :raw_emails
+
delegate :date, to: :mail
delegate :message_id, to: :mail
delegate :multipart?, to: :mail
@@ -66,6 +68,15 @@ def empty_from_field?
end
def directory
+ if file.attached?
+ warn <<~DEPRECATION.squish
+ [DEPRECATION] RawEmail#directory shouldn't be used when using
+ `ActiveStorage` backed file stores. This method will be removed
+ in 0.42.
+ DEPRECATION
+ return
+ end
+
if request_id.empty?
raise "Failed to find the id number of the associated request: has it been saved?"
end
@@ -79,6 +90,15 @@ def directory
end
def filepath
+ if file.attached?
+ warn <<~DEPRECATION.squish
+ [DEPRECATION] RawEmail#filepath shouldn't be used when using
+ `ActiveStorage` backed file stores. This method will be removed
+ in 0.42.
+ DEPRECATION
+ return
+ end
+
if incoming_message_id.empty?
raise "Failed to find the id number of the associated incoming message: has it been saved?"
end
@@ -95,14 +115,17 @@ def mail!
end
def data=(d)
- FileUtils.mkdir_p(directory) unless File.exist?(directory)
- File.atomic_write(filepath) do |file|
- file.binmode
- file.write(d)
- end
+ @data = d.to_s
+ file.attach(
+ io: StringIO.new(@data),
+ filename: "#{incoming_message_id}.eml",
+ content_type: 'message/rfc822'
+ )
end
def data
+ return @data ||= file.download if file.attached?
+
File.open(filepath, "rb").read
end
@@ -113,7 +136,11 @@ def data_as_text
end
def destroy_file_representation!
- File.delete(filepath) if File.exist?(filepath)
+ if file.attached?
+ file.purge
+ elsif File.exist?(filepath)
+ File.delete(filepath)
+ end
end
def from_name
diff --git a/app/models/request_classification.rb b/app/models/request_classification.rb
index bff373777c..d6e9162109 100644
--- a/app/models/request_classification.rb
+++ b/app/models/request_classification.rb
@@ -23,7 +23,7 @@ class RequestClassification < ApplicationRecord
def self.league_table(size, conditions=nil)
query = select('user_id, count(*) as cnt').
group('user_id').
- order('cnt desc').
+ order(cnt: :desc).
limit(size).
includes(:user)
query = query.where(*conditions) if conditions
diff --git a/app/models/statistics/leaderboard.rb b/app/models/statistics/leaderboard.rb
index 2600626898..974e69274a 100644
--- a/app/models/statistics/leaderboard.rb
+++ b/app/models/statistics/leaderboard.rb
@@ -5,7 +5,7 @@ def all_time_requesters
InfoRequest.is_public.
joins(:user).
group(:user).
- order('count_info_requests_all DESC').
+ order(count_info_requests_all: :desc).
limit(10).
count
end
@@ -16,7 +16,7 @@ def last_28_day_requesters
where('info_requests.created_at >= ?', 28.days.ago).
joins(:user).
group(:user).
- order('count_info_requests_all DESC').
+ order(count_info_requests_all: :desc).
limit(10).
count
end
@@ -25,7 +25,7 @@ def all_time_commenters
commenters = Comment.visible.
joins(:user).
group('comments.user_id').
- order('count_all DESC').
+ order(count_all: :desc).
limit(10).
count
# TODO: Have user objects automatically instantiated like the InfoRequest
@@ -41,7 +41,7 @@ def last_28_day_commenters
where('comments.created_at >= ?', 28.days.ago).
joins(:user).
group('comments.user_id').
- order('count_all DESC').
+ order(count_all: :desc).
limit(10).
count
# TODO: Have user objects automatically instantiated like the InfoRequest
diff --git a/app/models/user.rb b/app/models/user.rb
index ae711e177f..0729af6acf 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,5 +1,5 @@
# == Schema Information
-# Schema version: 20210921094059
+# Schema version: 20220210114052
#
# Table name: users
#
@@ -10,27 +10,27 @@
# salt :string
# created_at :datetime not null
# updated_at :datetime not null
-# email_confirmed :boolean default("false"), not null
+# email_confirmed :boolean default(FALSE), not null
# url_name :text not null
-# last_daily_track_email :datetime default("2000-01-01 00:00:00")
+# last_daily_track_email :datetime default(Sat, 01 Jan 2000 00:00:00.000000000 GMT +00:00)
# ban_text :text default(""), not null
# about_me :text default(""), not null
# locale :string
# email_bounced_at :datetime
# email_bounce_message :text default(""), not null
-# no_limit :boolean default("false"), not null
-# receive_email_alerts :boolean default("true"), not null
-# can_make_batch_requests :boolean default("false"), not null
-# otp_enabled :boolean default("false"), not null
+# no_limit :boolean default(FALSE), not null
+# receive_email_alerts :boolean default(TRUE), not null
+# can_make_batch_requests :boolean default(FALSE), not null
+# otp_enabled :boolean default(FALSE), not null
# otp_secret_key :string
-# otp_counter :integer default("1")
-# confirmed_not_spam :boolean default("false"), not null
-# comments_count :integer default("0"), not null
-# info_requests_count :integer default("0"), not null
-# track_things_count :integer default("0"), not null
-# request_classifications_count :integer default("0"), not null
-# public_body_change_requests_count :integer default("0"), not null
-# info_request_batches_count :integer default("0"), not null
+# otp_counter :integer default(1)
+# confirmed_not_spam :boolean default(FALSE), not null
+# comments_count :integer default(0), not null
+# info_requests_count :integer default(0), not null
+# track_things_count :integer default(0), not null
+# request_classifications_count :integer default(0), not null
+# public_body_change_requests_count :integer default(0), not null
+# info_request_batches_count :integer default(0), not null
# daily_summary_hour :integer
# daily_summary_minute :integer
# closed_at :datetime
@@ -45,123 +45,142 @@ class User < ApplicationRecord
include User::OneTimePassword
include User::Survey
- rolify before_add: :setup_pro_account
- strip_attributes :allow_empty => true
+ CONTENT_LIMIT = {
+ info_requests: AlaveteliConfiguration.max_requests_per_user_per_day,
+ comments: AlaveteliConfiguration.max_requests_per_user_per_day
+ }.freeze
+
+ rolify before_add: :setup_pro_account,
+ after_add: :assign_role_features,
+ after_remove: :assign_role_features
+ strip_attributes allow_empty: true
attr_accessor :no_xapian_reindex
has_many :info_requests,
- -> { order('info_requests.created_at desc') },
- :inverse_of => :user,
- :dependent => :destroy
+ -> { order(created_at: :desc) },
+ inverse_of: :user,
+ dependent: :destroy
has_many :info_request_events,
- -> { reorder('created_at desc') },
- :through => :info_requests
+ -> { reorder(created_at: :desc) },
+ through: :info_requests
has_many :embargoes,
- :inverse_of => :user,
- :through => :info_requests
+ inverse_of: :user,
+ through: :info_requests
has_many :draft_info_requests,
- -> { order('created_at desc') },
- :inverse_of => :user,
- :dependent => :destroy
+ -> { order(created_at: :desc) },
+ inverse_of: :user,
+ dependent: :destroy
has_many :user_info_request_sent_alerts,
- :inverse_of => :user,
- :dependent => :destroy
+ inverse_of: :user,
+ dependent: :destroy
has_many :post_redirects,
- -> { order('created_at desc') },
- :inverse_of => :user,
- :dependent => :destroy
+ -> { order(created_at: :desc) },
+ inverse_of: :user,
+ dependent: :destroy
has_many :track_things,
- -> { order('created_at desc') },
- :inverse_of => :tracking_user,
- :foreign_key => 'tracking_user_id',
- :dependent => :destroy
+ -> { order(created_at: :desc) },
+ inverse_of: :tracking_user,
+ foreign_key: 'tracking_user_id',
+ dependent: :destroy
has_many :citations,
- -> { order('created_at desc') },
+ -> { order(created_at: :desc) },
inverse_of: :user,
dependent: :destroy
has_many :comments,
- -> { order('created_at desc') },
- :inverse_of => :user,
- :dependent => :destroy
+ -> { order(created_at: :desc) },
+ inverse_of: :user,
+ dependent: :destroy
has_many :public_body_change_requests,
- -> { order('created_at desc') },
- :inverse_of => :user,
- :dependent => :destroy
+ -> { order(created_at: :desc) },
+ inverse_of: :user,
+ dependent: :destroy
has_one :profile_photo,
- :inverse_of => :user,
- :dependent => :destroy
+ inverse_of: :user,
+ dependent: :destroy
has_many :censor_rules,
- -> { order('created_at desc') },
- :inverse_of => :user,
- :dependent => :destroy
+ -> { order(created_at: :desc) },
+ inverse_of: :user,
+ dependent: :destroy
has_many :info_request_batches,
- -> { order('created_at desc') },
- :inverse_of => :user,
- :dependent => :destroy
+ -> { order(created_at: :desc) },
+ inverse_of: :user,
+ dependent: :destroy
has_many :draft_info_request_batches,
- -> { order('created_at desc') },
- :inverse_of => :user,
- :dependent => :destroy,
- :class_name => 'AlaveteliPro::DraftInfoRequestBatch'
+ -> { order(created_at: :desc) },
+ inverse_of: :user,
+ dependent: :destroy,
+ class_name: 'AlaveteliPro::DraftInfoRequestBatch'
has_many :request_classifications,
- :inverse_of => :user,
- :dependent => :destroy
+ inverse_of: :user,
+ dependent: :destroy
has_one :pro_account,
- :inverse_of => :user,
- :dependent => :destroy
+ inverse_of: :user,
+ dependent: :destroy
has_many :request_summaries,
- :inverse_of => :user,
- :dependent => :destroy,
- :class_name => 'AlaveteliPro::RequestSummary'
+ inverse_of: :user,
+ dependent: :destroy,
+ class_name: 'AlaveteliPro::RequestSummary'
has_many :notifications,
- :inverse_of => :user,
- :dependent => :destroy
+ inverse_of: :user,
+ dependent: :destroy
has_many :track_things_sent_emails,
- :inverse_of => :user,
- :dependent => :destroy
+ inverse_of: :user,
+ dependent: :destroy
has_many :track_things_sent_emails,
- :dependent => :destroy
+ dependent: :destroy
has_many :announcements,
- :inverse_of => :user
+ inverse_of: :user
has_many :announcement_dismissals,
- :inverse_of => :user,
- :dependent => :destroy
+ inverse_of: :user,
+ dependent: :destroy
has_many :memberships, class_name: 'Project::Membership'
has_many :projects, through: :memberships
+ has_many :sign_ins,
+ class_name: 'User::SignIn',
+ inverse_of: :user,
+ dependent: :destroy
+
scope :active, -> { not_banned.not_closed }
- scope :banned, -> { where.not(ban_text: "") }
- scope :not_banned, -> { where(ban_text: "") }
+ scope :banned, -> { where.not(ban_text: '') }
+ scope :not_banned, -> { where(ban_text: '') }
scope :closed, -> { where.not(closed_at: nil) }
scope :not_closed, -> { where(closed_at: nil) }
- validates_presence_of :email, :message => _("Please enter your email address")
- validates_presence_of :name, :message => _("Please enter your name")
+ validates_presence_of :email, message: _('Please enter your email address')
+ validates_presence_of :name, message: _('Please enter your name')
validates_length_of :about_me,
- :maximum => 500,
- :message => _("Please keep it shorter than 500 characters")
+ maximum: 500,
+ message: _('Please keep it shorter than 500 characters')
- validates :email, :uniqueness => {
- :case_sensitive => false,
- :message => _("This email is already in use") }
+ validates :email,
+ uniqueness: { case_sensitive: false,
+ message: _('This email is already in use') }
validate :email_and_name_are_valid
after_initialize :set_defaults
after_update :reindex_referencing_models, :update_pro_account
- acts_as_xapian :texts => [ :name, :about_me ],
- :values => [
- [ :created_at_numeric, 1, "created_at", :number ] # for sorting
- ],
- :terms => [ [ :variety, 'V', "variety" ] ],
- :if => :indexed_by_search?
+ acts_as_xapian texts: [:name, :about_me],
+ values: [
+ [:created_at_numeric, 1, 'created_at', :number] # for sorting
+ ],
+ terms: [[:variety, 'V', 'variety']],
+ if: :indexed_by_search?
+ def self.search(query)
+ where(<<~SQL, query: query)
+ lower(users.name) LIKE lower('%'||:query||'%') OR
+ lower(users.email) LIKE lower('%'||:query||'%') OR
+ lower(users.about_me) LIKE lower('%'||:query||'%')
+ SQL
+ end
def self.pro
- with_role :pro
+ with_role(:pro)
end
# Return user given login email, password and other form parameters (e.g. name)
@@ -206,50 +225,23 @@ def self.find_user_by_email(email)
# The "internal admin" is a special user for internal use.
def self.internal_admin_user
- user = User.find_by_email(AlaveteliConfiguration::contact_email)
- if user.nil?
- password = PostRedirect.generate_random_token
- user = User.new(
- :name => 'Internal admin user',
- :email => AlaveteliConfiguration.contact_email,
- :password => password,
- :password_confirmation => password
- )
- user.save!
- end
+ user = find_by(email: AlaveteliConfiguration.contact_email)
+ return user if user
- user
- end
-
- def self.owns_every_request?(user)
- warn %q([DEPRECATION] User#owns_every_request? will be removed in 0.41.
- It has been replaced by User#owns_every_request?).squish
- user&.owns_every_request?
- end
-
- def self.view_hidden?(user)
- warn %q([DEPRECATION] User.view_hidden? will be removed in 0.41.
- It has been replaced by User#view_hidden?).squish
- user&.view_hidden?
- end
+ password = PostRedirect.generate_random_token
- def self.view_embargoed?(user)
- warn %q([DEPRECATION] User.view_embargoed? will be removed in 0.41.
- It has been replaced by User#view_embargoed?).squish
- user&.view_embargoed?
- end
-
- def self.view_hidden_and_embargoed?(user)
- warn %q([DEPRECATION] User.view_hidden_and_embargoed? will be removed in
- 0.41. It has been replaced by User#view_hidden_and_embargoed?).
- squish
- user&.view_hidden_and_embargoed?
+ create!(
+ name: 'Internal admin user',
+ email: AlaveteliConfiguration.contact_email,
+ password: password,
+ password_confirmation: password
+ )
end
# Should the user be kept logged into their own account
# if they follow a /c/ redirect link belonging to another user?
def self.stay_logged_in_on_redirect?(user)
- !user.nil? && user.is_admin?
+ user&.is_admin?
end
# Used for default values of last_daily_track_email
@@ -268,10 +260,10 @@ def self.random_time_in_last_day
# This SQL statement is useful for seeing how spread out users are at the moment:
# select extract(hour from last_daily_track_email) as h, count(*) from users group by extract(hour from last_daily_track_email) order by h;
def self.spread_alert_times_across_day
- self.find_each do |user|
- user.last_daily_track_email = User.random_time_in_last_day
- user.save!
+ find_each do |user|
+ user.update!(last_daily_track_email: User.random_time_in_last_day)
end
+
nil # so doesn't print all users on console
end
@@ -280,7 +272,7 @@ def self.record_bounce_for_email(email, message)
return false if user.nil?
user.record_bounce(message) if user.email_bounced_at.nil?
- return true
+ true
end
def self.find_similar_named_users(user)
@@ -312,7 +304,7 @@ def created_at_numeric
end
def variety
- "user"
+ 'user'
end
# requested_by: and commented_by: search queries also need updating after save
@@ -329,12 +321,7 @@ def expire_requests
end
def expire_comments
- comments.find_each do |comment|
- # TODO: Extract to Comment#expire
- comment.info_request_events.find_each do |info_request_event|
- info_request_event.xapian_mark_needs_index
- end
- end
+ comments.find_each(&:reindex_request_events)
end
def locale
@@ -344,7 +331,7 @@ def locale
def name
_name = read_attribute(:name)
if suspended?
- _name = _("{{user_name}} (Account suspended)", :user_name => _name)
+ _name = _('{{user_name}} (Account suspended)', user_name: _name)
end
_name
end
@@ -376,10 +363,9 @@ def name_and_email
# Returns list of requests which the user hasn't described (and last
# changed more than a day ago)
def get_undescribed_requests
- info_requests.where(
- "awaiting_description = ? and #{ InfoRequest.last_event_time_clause } < ?",
- true, 1.day.ago
- )
+ info_requests.
+ where(awaiting_description: true).
+ where("#{ InfoRequest.last_event_time_clause } < ?", 1.day.ago)
end
# Does the user magically gain powers as if they owned every request?
@@ -389,7 +375,10 @@ def owns_every_request?
end
def can_admin_roles
- roles.flat_map { |role| Role.grants_and_revokes(role.name.to_sym) }.compact.uniq
+ roles.
+ flat_map { |role| Role.grants_and_revokes(role.name.to_sym) }.
+ compact.
+ uniq
end
def can_admin_role?(role)
@@ -401,9 +390,12 @@ def admin_page_links?
is_admin?
end
- # Is it public that they are banned?
def banned?
- !ban_text.empty?
+ ban_text.present?
+ end
+
+ def close
+ update(closed_at: Time.zone.now)
end
def closed?
@@ -413,17 +405,21 @@ def closed?
def close_and_anonymise
sha = Digest::SHA1.hexdigest(rand.to_s)
- redact_name! if info_requests.any?
+ transaction do
+ redact_name! if info_requests.any?
- update(
- name: _('[Name Removed]'),
- email: "#{sha}@invalid",
- url_name: sha,
- about_me: '',
- password: MySociety::Util.generate_token,
- receive_email_alerts: false,
- closed_at: Time.zone.now
- )
+ sign_ins.destroy_all
+
+ update(
+ name: _('[Name Removed]'),
+ email: "#{sha}@invalid",
+ url_name: sha,
+ about_me: '',
+ password: MySociety::Util.generate_token,
+ receive_email_alerts: false,
+ closed_at: Time.zone.now
+ )
+ end
end
def active?
@@ -434,27 +430,42 @@ def suspended?
!active?
end
+ def prominence
+ return 'hidden' if banned?
+ return 'backpage' if closed?
+ return 'backpage' unless email_confirmed?
+ 'normal'
+ end
+
# Various ways the user can be banned, and text to describe it if failed
def can_file_requests?
- active? && !exceeded_limit?
+ active? && !exceeded_limit?(:info_requests)
+ end
+
+ def can_make_followup?
+ active?
end
- def exceeded_limit?
- # Some users have no limit
- return false if no_limit
+ def can_make_comments?
+ active? && !exceeded_limit?(:comments)
+ end
- # Batch request users don't have a limit
- return false if can_make_batch_requests?
+ def can_contact_other_users?
+ active?
+ end
- # Has the user issued as many as MAX_REQUESTS_PER_USER_PER_DAY requests in the past 24 hours?
- return false if AlaveteliConfiguration.max_requests_per_user_per_day.blank?
+ def exceeded_limit?(content)
+ return false if no_limit?
+ return false if can_make_batch_requests?
+ return false if content_limit(content).blank?
- recent_requests =
- InfoRequest.
+ # Has the User created too much of the content in the past 24 hours?
+ recent_content =
+ content.to_s.classify.constantize.
where(["user_id = ? AND created_at > now() - '1 day'::interval", id]).
- count
+ count
- recent_requests >= AlaveteliConfiguration.max_requests_per_user_per_day
+ recent_content >= content_limit(content)
end
def next_request_permitted_at
@@ -463,7 +474,7 @@ def next_request_permitted_at
n_most_recent_requests =
InfoRequest.
where(["user_id = ? AND created_at > now() - '1 day'::interval", id]).
- order('created_at DESC').
+ order(created_at: :desc).
limit(AlaveteliConfiguration.max_requests_per_user_per_day)
return nil if n_most_recent_requests.size < AlaveteliConfiguration::max_requests_per_user_per_day
@@ -472,28 +483,16 @@ def next_request_permitted_at
nth_most_recent_request.created_at + 1.day
end
- def can_make_followup?
- active?
- end
-
- def can_make_comments?
- active?
- end
-
- def can_contact_other_users?
- active?
- end
-
def can_fail_html
if banned?
text = ban_text.strip
elsif closed?
text = _('Account closed at user request')
else
- raise "Unknown reason for ban"
+ raise 'Unknown reason for ban'
end
text = CGI.escapeHTML(text)
- text = MySociety::Format.make_clickable(text, :contract => 1)
+ text = MySociety::Format.make_clickable(text, contract: 1)
text = text.gsub(/\n/, ' ')
text.html_safe
end
@@ -518,7 +517,7 @@ def show_profile_photo?
def about_me_already_exists?
return false if about_me.blank?
- self.class.where(:about_me => about_me).where.not(id: id).any?
+ self.class.where(about_me: about_me).where.not(id: id).any?
end
# Return about me text for display as HTML
@@ -526,27 +525,28 @@ def about_me_already_exists?
def get_about_me_for_html_display
text = about_me.strip
text = CGI.escapeHTML(text)
- text = MySociety::Format.make_clickable(text, { :contract => 1, :nofollow => true })
+ text = MySociety::Format.make_clickable(text, contract: 1, nofollow: true)
text = text.gsub(/\n/, ' ')
text.html_safe
end
def json_for_api
{
- :id => id,
- :url_name => url_name,
- :name => name,
- :ban_text => ban_text,
- :about_me => about_me,
+ id: id,
+ url_name: url_name,
+ name: name,
+ ban_text: ban_text,
+ about_me: about_me
# :profile_photo => self.profile_photo # ought to have this, but too hard to get URL out for now
# created_at / updated_at we only show the year on the main page for privacy reasons, so don't put here
}
end
def record_bounce(message)
- self.email_bounced_at = Time.zone.now
- self.email_bounce_message = convert_string_to_utf8(message).string
- save!
+ update!(
+ email_bounced_at: Time.zone.now,
+ email_bounce_message: convert_string_to_utf8(message).string
+ )
end
def confirm(save_record = false)
@@ -598,28 +598,36 @@ def next_daily_summary_time
end
def daily_summary_time
- {
- hour: self.daily_summary_hour,
- min: self.daily_summary_minute
- }
+ { hour: daily_summary_hour,
+ min: daily_summary_minute }
end
# With what frequency does the user want to be notified?
def notification_frequency
- if feature_enabled? :notifications, self
+ if features.enabled?(:notifications)
Notification::DAILY
else
Notification::INSTANTLY
end
end
+ def features
+ # Will return enabled and disabled features. Call #enabled? to see the
+ # current state
+ AlaveteliFeatures.features.with_actor(self)
+ end
+
+ def features=(new_features)
+ features.assign_features(new_features)
+ end
+
# Define an id number for use with the Flipper gem's user-by-user feature
# flagging. We prefix with the class because features can be enabled for
# other types of objects (e.g Roles) in the same way and will be stored in
# the same table. See:
# https://github.com/jnunemaker/flipper/blob/master/docs/Gates.md
def flipper_id
- return "User;#{id}"
+ "User;#{id}"
end
private
@@ -632,18 +640,16 @@ def redact_name!
end
def set_defaults
- if new_record?
- # make alert emails go out at a random time for each new user, so
- # overall they are spread out throughout the day.
- self.last_daily_track_email = User.random_time_in_last_day
- # Make daily summary emails go out at a random time for each new user
- # too, if it's not already set
- if self.daily_summary_hour.nil? && self.daily_summary_minute.nil?
- random_time = User.random_time_in_last_day
- self.daily_summary_hour = random_time.hour
- self.daily_summary_minute = random_time.min
- end
- end
+ return unless new_record?
+
+ # make alert emails go out at a random time for each new user, so
+ # overall they are spread out throughout the day.
+ self.last_daily_track_email = self.class.random_time_in_last_day
+
+ # Make daily summary emails go out at a random time for each new user
+ # too, if it's not already set
+ self.daily_summary_hour ||= self.class.random_time_in_last_day.hour
+ self.daily_summary_minute ||= self.class.random_time_in_last_day.min
end
def email_and_name_are_valid
@@ -655,14 +661,20 @@ def email_and_name_are_valid
end
end
+ def assign_role_features(_role)
+ features.assign_role_features
+ end
+
def setup_pro_account(role)
return unless role == Role.pro_role
pro_account || build_pro_account if feature_enabled?(:pro_pricing)
- AlaveteliPro::Access.grant(self)
end
def update_pro_account
pro_account.update_stripe_customer if pro_account
end
+ def content_limit(content)
+ CONTENT_LIMIT[content]
+ end
end
diff --git a/app/models/user/login_token.rb b/app/models/user/login_token.rb
index d6824dd15a..9d7d62db86 100644
--- a/app/models/user/login_token.rb
+++ b/app/models/user/login_token.rb
@@ -2,6 +2,8 @@
module User::LoginToken
extend ActiveSupport::Concern
+ LOGIN_TOKEN_NAMESPACE = 'b14cba73-a392-4de4-a9ed-06d7f0ced429'
+
included do
before_save :set_login_token
end
@@ -13,9 +15,13 @@ def set_login_token
end
def set_login_token!
- self.login_token = Digest::UUID.uuid_v5("User;#{id}", {
- email: email,
- hashed_password: hashed_password
- }.to_s)
+ self.login_token = Digest::UUID.uuid_v5(
+ LOGIN_TOKEN_NAMESPACE,
+ {
+ user: id,
+ email: email,
+ hashed_password: hashed_password
+ }.to_s
+ )
end
end
diff --git a/app/models/user/sign_in.rb b/app/models/user/sign_in.rb
new file mode 100644
index 0000000000..159741080b
--- /dev/null
+++ b/app/models/user/sign_in.rb
@@ -0,0 +1,54 @@
+# == Schema Information
+# Schema version: 20220225214524
+#
+# Table name: user_sign_ins
+#
+# id :bigint not null, primary key
+# user_id :bigint
+# ip :inet
+# created_at :datetime not null
+# updated_at :datetime not null
+# country :string
+#
+
+# Record medadata about User sign in activity
+class User::SignIn < ApplicationRecord
+ default_scope { order(created_at: :desc) }
+
+ belongs_to :user, inverse_of: :sign_ins
+
+ before_create :create?
+
+ def self.purge
+ where('created_at < ?', retention_days.days.ago).destroy_all
+ end
+
+ def self.search(query)
+ joins(:user).references(:users).where(<<~SQL, query: query)
+ lower(user_sign_ins.ip::text) LIKE lower('%'||:query||'%') OR
+ lower(user_sign_ins.country) LIKE lower('%'||:query||'%') OR
+ lower(users.name) LIKE lower('%'||:query||'%') OR
+ lower(users.email) LIKE lower('%'||:query||'%')
+ SQL
+ end
+
+ def self.retain_signins?
+ retention_days >= 1
+ end
+
+ def self.retention_days
+ AlaveteliConfiguration.user_sign_in_activity_retention_days
+ end
+
+ def other_users
+ User.distinct.joins(:sign_ins).
+ where(user_sign_ins: { ip: ip }).
+ where.not(id: user_id)
+ end
+
+ private
+
+ def create?
+ throw :abort unless self.class.retain_signins?
+ end
+end
diff --git a/app/models/user/transaction_calculator.rb b/app/models/user/transaction_calculator.rb
index 2abffe65ca..98fc229539 100644
--- a/app/models/user/transaction_calculator.rb
+++ b/app/models/user/transaction_calculator.rb
@@ -57,7 +57,7 @@ def total_per_month
user.
send(assoc).
group("DATE_TRUNC('month', created_at)").
- reorder("date_trunc_month_created_at").
+ reorder(:date_trunc_month_created_at).
count
# Add the counts to existing keys, or set new keys if they don't exist
diff --git a/app/models/user/with_activity_query.rb b/app/models/user/with_activity_query.rb
index 8f0f801e4b..09864a7215 100644
--- a/app/models/user/with_activity_query.rb
+++ b/app/models/user/with_activity_query.rb
@@ -8,7 +8,7 @@
# # => User::ActiveRecord_Relation
#
# # ID of most active user:
-# q.call.order('activity DESC').first.id
+# q.call.order(activity: :desc).first.id
# # => 19
#
# # Activity in date range:
diff --git a/app/services/alaveteli_pro/access.rb b/app/services/alaveteli_pro/access.rb
deleted file mode 100644
index fc16e6d708..0000000000
--- a/app/services/alaveteli_pro/access.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-##
-# A service object to grant and revoke users access to Alaveteli Professional
-# features
-#
-# Usage:
-# AlaveteliPro::Access.grant(user)
-#
-# TODO:
-# AlaveteliPro::Access.revoke(user)
-#
-class AlaveteliPro::Access
- include AlaveteliFeatures::Helpers
-
- def self.grant(*args, &block)
- new(*args, &block).grant
- end
-
- attr_reader :user
-
- def initialize(user)
- @user = user
- end
-
- def grant
- # enable the mail poller only if the POP polling is configured
- if AlaveteliConfiguration.production_mailer_retriever_method == 'pop'
- enable_actor(:accept_mail_from_poller, user)
- end
-
- enable_actor(:notifications, user)
- end
-end
diff --git a/app/views/admin/citations/_list.html.erb b/app/views/admin/citations/_list.html.erb
new file mode 100644
index 0000000000..c594b4ad65
--- /dev/null
+++ b/app/views/admin/citations/_list.html.erb
@@ -0,0 +1,20 @@
+
+ <% if citations.any? %>
+ <% citations.each do |citation| %>
+
+
+ <%= link_to citation.source_url, citation.source_url %>
+
+
+
+ <%= both_links(citation.user) %>
+ <%= arrow_right %>
+ <%= both_links(citation.citable) %>,
+ <%= admin_date(citation.created_at, ago_only: true) %>
+
+
+ <% end %>
+ <% else %>
+
None yet.
+ <% end %>
+
diff --git a/app/views/admin/users/_sign_in_table.html.erb b/app/views/admin/users/_sign_in_table.html.erb
new file mode 100644
index 0000000000..7bc3185d39
--- /dev/null
+++ b/app/views/admin/users/_sign_in_table.html.erb
@@ -0,0 +1,48 @@
+
+ <% unless User::SignIn.retain_signins? %>
+
+
Sign In Retention Disabled
+ To enable sign in retention set
+ USER_SIGN_IN_ACTIVITY_RETENTION_DAYS to a value greater than
+ 0 in Alaveteli’s configuration. Any existing records below will
+ soon be purged and no further sign ins will be tracked.
+
+ <% end %>
+
+ <% if sign_ins.any? %>
+
+ <% sign_ins.each do |sign_in| %>
+
+ <%= user_both_links(sign_in.user) %>
+
+
+
+ <%= link_to sign_in.ip, admin_sign_ins_path(query: sign_in.ip) %>
+
+
+
+
+
+ <% if sign_in.country %>
+ <%= link_to admin_sign_ins_path(query: sign_in.country) do %>
+ <%= sign_in.country %>
+ <% end %>
+ <% else %>
+ ??
+ <% end %>
+
+
+
+ <%= admin_date(sign_in.created_at, ago_only: true) %>
+
+
+ <%= sign_in.other_users.size %> others using this
+ IP
+
+
+ <% end %>
+
+ <% elsif User::SignIn.retain_signins? %>
+
None yet.
+ <% end %>
+
diff --git a/app/views/admin/users/sign_ins/index.html.erb b/app/views/admin/users/sign_ins/index.html.erb
new file mode 100644
index 0000000000..8cb4c23a5b
--- /dev/null
+++ b/app/views/admin/users/sign_ins/index.html.erb
@@ -0,0 +1,19 @@
+<%= render 'admin_user/scopes' %>
+
+
+ <%= form_tag admin_sign_ins_path, method: :get, class: 'form form-search span12' do %>
+
+ <%= text_field_tag 'query', params[:query], size: 30, class: 'input-large search-query' %>
+ <%= submit_tag 'Search', class: 'btn' %>
+
+
+
+ (substring search: names, emails, country and IP)
+
+ <% end %>
+
+
+<%= render partial: 'admin/users/sign_in_table',
+ locals: { sign_ins: @sign_ins } %>
+
+<%= will_paginate(@sign_ins, class: 'paginator') %>
diff --git a/app/views/admin_censor_rule/_form.html.erb b/app/views/admin_censor_rule/_form.html.erb
index 1b81c920cb..603dfe4a8f 100644
--- a/app/views/admin_censor_rule/_form.html.erb
+++ b/app/views/admin_censor_rule/_form.html.erb
@@ -1,19 +1,7 @@
<%= foi_error_messages_for :censor_rule %>
- Applies to
- <% unless info_request.nil? %>
- <%= request_both_links(info_request) %>
- <% end %>
- <% unless user.nil? %>
- <%= user_both_links(user) %>
- <% end %>
- <% unless public_body.nil? %>
- <%= public_body_both_links(public_body) %>
- <% end %>
- <% if info_request.nil? && user.nil? && public_body.nil? %>
- everything
- <% end %>
+ Applies to <%= censor_rule_applies_to(@censor_rule) %>
@@ -29,7 +17,7 @@
Text
- <%= text_field 'censor_rule', 'text', :class => "span3" %>
+ <%= text_field 'censor_rule', 'text', class: 'span6' %>
that you want to remove, case sensitive
@@ -39,9 +27,28 @@
Replacement
- <%= text_field 'censor_rule', 'replacement', :class => "span3" %>
+ <% placeholder =
+ if @censor_rule.canned_replacements.any?
+ 'Select or add your own'
+ end
+ %>
+
+ <%= text_field 'censor_rule',
+ 'replacement',
+ class: 'span6',
+ list: 'canned-replacements',
+ autocomplete: 'off',
+ placeholder: placeholder %>
+
+
+ <%= options_for_select(@censor_rule.canned_replacements) %>
+
+
- put it in [square brackets] , e.g. [personal information removed]. applies to text in emails and HTML conversions of binaries; binaries themselves must stay the same length and the replacement is just a bunch of 'x's
+ Put it in [square brackets] , e.g.
+ [Name removed] . Applies to text in emails and HTML conversions of
+ binaries; binaries themselves must stay the same length and the
+ replacement is just a bunch of 'x's
diff --git a/app/views/admin_censor_rule/_show.html.erb b/app/views/admin_censor_rule/_show.html.erb
index 67b6f67e17..98ae703cdf 100644
--- a/app/views/admin_censor_rule/_show.html.erb
+++ b/app/views/admin_censor_rule/_show.html.erb
@@ -6,15 +6,22 @@
<% CensorRule.content_columns.each do |column| %>
<%= column.name.humanize %>
<% end %>
+
Applies to
Actions
<% censor_rules.each do |censor_rule| %>
<%=h censor_rule.id %>
+
<% CensorRule.content_columns.map { |c| c.name }.each do |column| %>
<%=h censor_rule.send(column) %>
<% end %>
+
+
+ <%= censor_rule_applicable_class(censor_rule) %>
+
+
<%= link_to "Edit", edit_admin_censor_rule_path(censor_rule) %>
diff --git a/app/views/admin_comment/edit.html.erb b/app/views/admin_comment/edit.html.erb
index 9cb1e4f3df..61ffe7e13b 100644
--- a/app/views/admin_comment/edit.html.erb
+++ b/app/views/admin_comment/edit.html.erb
@@ -2,6 +2,26 @@
<%= foi_error_messages_for 'comment' %>
+
+
+
+
+
+
+ By <%= both_links(@comment.user) %>
+
+
+
+
+
+ On <%= both_links(@comment.info_request) %>
+
+
+
+
+
+
+
<%= form_tag admin_comment_path(@comment), :method => 'put' do %>
Body of annotation
@@ -22,11 +42,6 @@
<%= submit_tag 'Save', :accesskey => 's', :class => 'btn btn-success' %>
<% end %>
-
- <%= link_to 'Show request', admin_request_path(@comment.info_request) %> |
- <%= link_to 'List all requests', admin_requests_path %>
-
-
Events
diff --git a/app/views/admin_comment/index.html.erb b/app/views/admin_comment/index.html.erb
index 5ed06aa746..f422dd8d56 100644
--- a/app/views/admin_comment/index.html.erb
+++ b/app/views/admin_comment/index.html.erb
@@ -1,43 +1,15 @@
<%= @title %>
-<%= form_tag({}, :method => :get, :class => 'form form-search') do %>
- <%= text_field_tag 'query', params[:query], { :size => 30, :class => 'input-large search-query' } %>
- <%= submit_tag 'Search', :class => 'btn' %> (substring search, body)
-<% end %>
-
-
-<% @comments.each do |comment| %>
-
-
-
- <%= chevron_right %>
-
- <%= link_to "#{ h(truncate(comment.body, :length => 100)) }",
- edit_admin_comment_path(comment) %>
-
-
- created <%= admin_date(comment.created_at, ago: false) %>
-
-
-
-
-
-
- <% comment.for_admin_column do |name, value, type|%>
-
- <%= h name %>
-
- <%= admin_value(value) %>
-
-
- <% end %>
-
-
-
+<%= form_tag({}, method: :get, class: 'form form-search') do %>
+
+ <%= text_field_tag 'query', params[:query], size: 30, class: 'input-large search-query' %>
+ <%= submit_tag 'Search', class: 'btn' %>
+
+
(substring search, body)
<% end %>
-
-<%= will_paginate(@comments, :class => "paginator") %>
+<%= render partial: 'admin_request/some_annotations' ,
+ locals: { comments: @comments } %>
+
+<%= will_paginate(@comments, class: 'paginator') %>
diff --git a/app/views/admin_general/_admin_navbar.html.erb b/app/views/admin_general/_admin_navbar.html.erb
index 4732713a87..4b9c0f39c0 100644
--- a/app/views/admin_general/_admin_navbar.html.erb
+++ b/app/views/admin_general/_admin_navbar.html.erb
@@ -1,10 +1,17 @@
-
+
<%= link_to 'Alaveteli', frontpage_path, :class => "brand" %>
+ <% if session[:user_circumstance] == 'login_as' %>
+
+ <%= link_to "Logged in as #{current_user.name}", user_path(current_user) %>
+ <%= link_to 'Revert back to admin', admin_users_sessions_path, method: :delete %>
+
+ <% else %>
+
Summary
+ <% end %>
+
<%= link_to 'Log out', signout_path %>
diff --git a/app/views/admin_general/_change_request_summary.html.erb b/app/views/admin_general/_change_request_summary.html.erb
index d7fca5995b..072f7936da 100644
--- a/app/views/admin_general/_change_request_summary.html.erb
+++ b/app/views/admin_general/_change_request_summary.html.erb
@@ -34,7 +34,7 @@
Source
- <%= @change_request.source_url %>
+ <%= link_to nil, @change_request.source_url %>
@@ -47,6 +47,15 @@
+
+
+ Notes
+
+
+ <%= @change_request.notes %>
+
+
+
Requested on
diff --git a/app/views/admin_general/_to_do_list.html.erb b/app/views/admin_general/_to_do_list.html.erb
index 673408fb53..b011c49705 100644
--- a/app/views/admin_general/_to_do_list.html.erb
+++ b/app/views/admin_general/_to_do_list.html.erb
@@ -25,7 +25,7 @@
<% end %>
<% end %>
<% elsif item.is_a? InfoRequest %>
- <%= request_both_links(item) %>
+ <%= both_links(item) %>
<% if InfoRequest.requires_admin_states.include?(item.described_state) %>
<% end %>
<% elsif item.is_a? Comment %>
- <%= comment_both_links(item) %>
+ <%= both_links(item) %>
User message
@@ -69,7 +69,7 @@
<% elsif item.is_a? PublicBody %>
- <%= public_body_both_links(item) %>
+ <%= both_links(item) %>
<% end %>
diff --git a/app/views/admin_general/stats.html.erb b/app/views/admin_general/stats.html.erb
index ee6feec9fb..1ba4964897 100644
--- a/app/views/admin_general/stats.html.erb
+++ b/app/views/admin_general/stats.html.erb
@@ -22,7 +22,7 @@
State of requests (includes backpaged)
<% for state, count in @request_by_state %>
-
+
<%=count%>
@@ -49,7 +49,7 @@
Tracks by type
<% for state, count in @tracks_by_type %>
-
+
<%=count%>
diff --git a/app/views/admin_general/timeline.html.erb b/app/views/admin_general/timeline.html.erb
index 48fd62e54f..0f45796b54 100644
--- a/app/views/admin_general/timeline.html.erb
+++ b/app/views/admin_general/timeline.html.erb
@@ -33,7 +33,10 @@
<% end %>
-
<%= simple_date(event_at) %>
+
+ <%= simple_date(event_at) %>
+ <%= event_at.strftime('%A') %>
+
<% else %>
@@ -49,7 +52,7 @@
<% if event.info_request.embargo && cannot?(:admin, AlaveteliPro::Embargo) %>
An embargoed request
<% else %>
- <%= request_both_links(event.info_request) %>
+ <%= both_links(event.info_request) %>
<% end %>
<% if lookup_context.template_exists?(event.event_type, ['admin_general'], true) %>
@@ -59,7 +62,7 @@
<% end %>
<% else %>
- <%= public_body_both_links(event.public_body) %>
+ <%= both_links(event.public_body) %>
<% if event.previous %>
was updated by administrator <%= event.last_edit_editor %> .
Changed fields:
diff --git a/app/views/admin_incoming_message/_actions.html.erb b/app/views/admin_incoming_message/_actions.html.erb
index 2d7deb14aa..0478d2d780 100644
--- a/app/views/admin_incoming_message/_actions.html.erb
+++ b/app/views/admin_incoming_message/_actions.html.erb
@@ -1,5 +1,20 @@
Actions
+
+<% if incoming_message.response_event.described_state != 'waiting_clarification' %>
+ <%= form_tag admin_info_request_event_path(incoming_message.response_event), method: :put, class: 'form form-inline' do %>
+
+
+ Mark as clarification request. This resets the timer.
+
+
+
+
+ <%= submit_tag 'Was clarification request', class: 'btn' %>
+
+ <% end %>
+<% end %>
+
<%= form_tag redeliver_admin_incoming_message_path(incoming_message), :class => "form form-inline" do %>
Redeliver message to one or more other requests
diff --git a/app/views/admin_incoming_message/_intro.html.erb b/app/views/admin_incoming_message/_intro.html.erb
index 1d5585f111..07eecb93b1 100644
--- a/app/views/admin_incoming_message/_intro.html.erb
+++ b/app/views/admin_incoming_message/_intro.html.erb
@@ -1,3 +1,3 @@
<% @title = "Incoming message #{incoming_message.id} of FOI request '#{incoming_message.info_request.title}'" %>
Incoming message <%= incoming_message.id %>
-
FOI request: <%= request_both_links(incoming_message.info_request) %>
+
FOI request: <%= both_links(incoming_message.info_request) %>
diff --git a/app/views/admin_incoming_message/bulk_destroy.html.erb b/app/views/admin_incoming_message/bulk_destroy.html.erb
index cf757f4c3a..fc558cb8c8 100644
--- a/app/views/admin_incoming_message/bulk_destroy.html.erb
+++ b/app/views/admin_incoming_message/bulk_destroy.html.erb
@@ -1,7 +1,7 @@
Confirm incoming message deletion
<% @incoming_messages.each do |incoming_message| %>
-
<%= h(incoming_message.mail_from) %>
+
<%= h(incoming_message.from_name) %>
at <%= admin_date(incoming_message.sent_at) %>
<% if !incoming_message.cached_main_body_text_folded.nil? %>
diff --git a/app/views/admin_public_body/_form.html.erb b/app/views/admin_public_body/_form.html.erb
index 3e440840a2..b521372cae 100644
--- a/app/views/admin_public_body/_form.html.erb
+++ b/app/views/admin_public_body/_form.html.erb
@@ -1,16 +1,8 @@
<% if @public_body.errors.any? %>
- <% if rails_upgrade? %>
- <% @public_body.errors.each do |error| %>
- <% unless error.attribute.to_s.starts_with?('translation') %>
- <%= error.message %>
- <% end %>
- <% end %>
- <% else %>
- <% @public_body.errors.each do |attr, message| %>
- <% unless attr.to_s.starts_with?('translation') %>
- <%= message %>
- <% end %>
+ <% @public_body.errors.each do |error| %>
+ <% unless error.attribute.to_s.starts_with?('translation') %>
+ <%= error.message %>
<% end %>
<% end %>
@@ -20,14 +12,8 @@
<% if translation.errors.any? %>
<%= locale_name(translation.locale.to_s) || translation.locale.to_s %>
- <% if rails_upgrade? %>
- <% translation.errors.each do |error| %>
- <%= error.message %>
- <% end %>
- <% else %>
- <% translation.errors.each do |attr, message| %>
- <%= message %>
- <% end %>
+ <% translation.errors.each do |error| %>
+ <%= error.message %>
<% end %>
<% end %>
@@ -71,7 +57,7 @@
Special tags:
not_apply
: if FOI and EIR no longer apply to authority and prevents further requests from being sent
- foi_no
: if FOI does not apply but the authority is still willing to accept requests
+ foi_no
: FOI does not apply but we list the authority to campaign for it to be subject to FOI
eir_only
if EIR but not FOI applies to authority
defunct
if the authority no longer exists
charity:NUMBER
if a registered charity
diff --git a/app/views/admin_public_body/_one_list.html.erb b/app/views/admin_public_body/_one_list.html.erb
index d700356651..cb4e16e3dd 100644
--- a/app/views/admin_public_body/_one_list.html.erb
+++ b/app/views/admin_public_body/_one_list.html.erb
@@ -28,14 +28,24 @@
<% end %>
-<%= form_tag(mass_tag_add_admin_bodies_path, :method => "post", :class => "form form-inline" ) do %>
-
- <%= text_field_tag 'new_tag', params[:new_tag], { :size => 15, :id => "mass_add_tag_new_tag_" + table_name } %>
- <%= hidden_field_tag(:query, params[:query], { :id => "mass_add_tag_query_" + table_name } ) %>
- <%= hidden_field_tag(:page, params[:page], { :id => "mass_add_page_" + table_name } ) %>
- <%= hidden_field_tag(:table_name, table_name, { :id => "mass_add_tag_table_name_" + table_name } ) %>
- <%= submit_tag "Add tag to all", :class => "btn btn-primary" %>
- (in table just above)
-
-<% end %>
+
+ <%= form_tag(mass_tag_admin_bodies_path, method: "post", class: "form form-inline" ) do %>
+ <%= text_field_tag 'tag', params[:tag], { size: 15, id: "mass_tag_tag_" + table_name } %>
+ <%= hidden_field_tag(:query, params[:query], { id: "mass_tag_query_" + table_name } ) %>
+ <%= hidden_field_tag(:page, params[:page], { id: "mass_page_" + table_name } ) %>
+ <%= hidden_field_tag(:table_name, table_name, { id: "mass_tag_table_name_" + table_name } ) %>
+ <%= submit_tag "Add tag to all", class: "btn btn-primary" %>
+ <% end %>
+
+ <% if table_name == 'exact' %>
+ <%= form_tag(mass_tag_admin_bodies_path, method: "delete", class: "form form-inline" ) do %>
+ <%= hidden_field_tag 'tag', params[:query], { id: "mass_tag_tag_" + table_name } %>
+ <%= hidden_field_tag(:query, params[:query], { id: "mass_tag_query_" + table_name } ) %>
+ <%= hidden_field_tag(:page, params[:page], { id: "mass_page_" + table_name } ) %>
+ <%= hidden_field_tag(:table_name, table_name, { id: "mass_tag_table_name_" + table_name } ) %>
+ <%= submit_tag "Remove '#{params[:query].html_safe}' tag from all", class: "btn btn-primary" %>
+ <% end %>
+ <% end %>
+ (in table just above)
+
diff --git a/app/views/admin_public_body/edit.html.erb b/app/views/admin_public_body/edit.html.erb
index d242e43ad3..9f82a34adb 100644
--- a/app/views/admin_public_body/edit.html.erb
+++ b/app/views/admin_public_body/edit.html.erb
@@ -36,6 +36,11 @@
+ <% if @change_request %>
+
Change Request
+ <%= render partial: 'admin_general/change_request_summary' %>
+ <% end %>
+
<%= render :partial => 'tag_help' %>
diff --git a/app/views/admin_public_body/import_csv.html.erb b/app/views/admin_public_body/import_csv.html.erb
index 57ab0b78e0..a003e00dbd 100644
--- a/app/views/admin_public_body/import_csv.html.erb
+++ b/app/views/admin_public_body/import_csv.html.erb
@@ -2,11 +2,12 @@
<%=@title%>
-<% if not @notes.empty? %>
-
<%=@notes %>
+<% if @errors.present? %>
+
<%= @errors %>
<% end %>
-<% if not @errors.empty? %>
-
<%=@errors %>
+
+<% if @notes.present? %>
+
<%= @notes %>
<% end %>
<%= form_tag 'import_csv', :multipart => true do %>
diff --git a/app/views/admin_public_body/index.html.erb b/app/views/admin_public_body/index.html.erb
index a2ae4367ec..b25e192bcd 100644
--- a/app/views/admin_public_body/index.html.erb
+++ b/app/views/admin_public_body/index.html.erb
@@ -21,7 +21,7 @@
<% if !@query.nil? %>
<%= link_to 'Show all', admin_bodies_path, :class => "btn" %>
<% end %>
- (substring search in names and emails; exact match of tags)
+
(substring search in names and emails; exact match of tags)
<% end %>
diff --git a/app/views/admin_public_body/missing_scheme.html.erb b/app/views/admin_public_body/missing_scheme.html.erb
deleted file mode 100644
index 90d37ff653..0000000000
--- a/app/views/admin_public_body/missing_scheme.html.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-<% @title = 'Popular authorities without Publication Scheme' %>
-
-
<%=@title%>
-
<%= @stats["entered"] %> of <%= @stats["total"] %> entered
-
-
- <% for public_body in @public_bodies %>
- <%= public_body_both_links(public_body) %>
- <% end %>
-
-
diff --git a/app/views/admin_public_body/new.html.erb b/app/views/admin_public_body/new.html.erb
index f5dfbc3fb7..30a0911a9d 100644
--- a/app/views/admin_public_body/new.html.erb
+++ b/app/views/admin_public_body/new.html.erb
@@ -20,6 +20,11 @@
+ <% if @change_request %>
+
Change Request
+ <%= render partial: 'admin_general/change_request_summary' %>
+ <% end %>
+
<% if @public_body.ordered_translations.many? %>
diff --git a/app/views/admin_public_body/show.html.erb b/app/views/admin_public_body/show.html.erb
index bc441bb352..95503bb9de 100644
--- a/app/views/admin_public_body/show.html.erb
+++ b/app/views/admin_public_body/show.html.erb
@@ -1,6 +1,6 @@
<% @title = "Public authority – #{ @public_body.name }" %>
-
<%=@title%>
+
<%= @title %>
@@ -10,6 +10,7 @@
<%=name%>
+
<% if ['home_page', 'publication_scheme', 'disclosure_log'].include? column_name %>
<% if value %>
@@ -27,10 +28,12 @@
<% end %>
<% end %>
+
Calculated home page
+
<% unless @public_body.calculated_home_page.nil? %>
<%= link_to(h(@public_body.calculated_home_page), @public_body.calculated_home_page) %>
@@ -39,10 +42,12 @@
<% end %>
+
Tags
+
<%= render_tags @public_body.tags,
search_target: list_public_bodies_path %>
@@ -50,35 +55,52 @@
+
<%= link_to 'Edit', edit_admin_body_path(@public_body), class: 'btn btn-primary' %>
+
<% unless @public_body.url_name.nil? %>
<%= link_to 'Public page', public_body_path(@public_body), class: 'btn' %>
<% else %>
Public page not available
<% end %>
+
+
History
- <% @versions.each_with_index do |version, i| %>
+
+<% @versions.each_with_index do |version, i| %>
<%= "Version #{ version.version }" %>
+
- <%= version.updated_at.to_s(:db) %>
+ <% if rails_upgrade? %>
+ <%= version.updated_at.to_fs(:db) %>
+ <% else %>
+ <%= version.updated_at.to_s(:db) %>
+ <% end %>
(<%= time_ago_in_words(version.updated_at) %> ago)
- <% if i == @versions.length - 1 %>
-
-
“<%= h(version.last_edit_comment) %>”
+
+
+
+ “<%= h(version.last_edit_comment) %>”
+
+ <% if version.editor %>
+ – <%= link_to version.editor.name, admin_user_path(version.editor) %>
+ <% else %>
+ – <%= version.last_edit_editor %>
+ <% end %>
+
+
+ <% if i == @versions.length - 1 %>
This is the first version.
-
- <% else %>
-
-
“<%= h(version.last_edit_comment) %>”
+ <% else %>
<% version.compare(@versions[i+1]) do |change| %>
@@ -88,20 +110,25 @@
<% end %>
-
- <% end %>
+ <% end %>
+
<% end %>
+
+
Requests
+
<%= render :partial => 'admin_request/some_requests', :locals => { :info_requests => @info_requests } %>
Track things
+
<%= render :partial => 'admin_track/some_tracks', :locals => { :track_things => @public_body.track_things, :include_destroy => true } %>
Censor rules
+
<%= render :partial => 'admin_censor_rule/show', :locals => { :censor_rules => @public_body.censor_rules, :public_body => @public_body } %>
diff --git a/app/views/admin_public_body_categories/_category_list_item.html.erb b/app/views/admin_public_body_categories/_category_list_item.html.erb
index 0d860e6ff4..d810589d96 100644
--- a/app/views/admin_public_body_categories/_category_list_item.html.erb
+++ b/app/views/admin_public_body_categories/_category_list_item.html.erb
@@ -1,3 +1,7 @@
data-id="categories_<%= category.id %>"<% end %>>
+ <% if heading %>
+
+ <% end %>
+
<%= link_to(category.title, edit_admin_category_path(category), :title => "view full details") %>
diff --git a/app/views/admin_public_body_categories/_form.html.erb b/app/views/admin_public_body_categories/_form.html.erb
index 681ce06e89..9c3b9bdcda 100644
--- a/app/views/admin_public_body_categories/_form.html.erb
+++ b/app/views/admin_public_body_categories/_form.html.erb
@@ -1,16 +1,8 @@
<% if @public_body_category.errors.any? %>
- <% if rails_upgrade? %>
- <% @public_body_category.errors.each do |error| %>
- <% unless error.attribute.to_s.starts_with?('translation') %>
- <%= error.message %>
- <% end %>
- <% end %>
- <% else %>
- <% @public_body_category.errors.each do |attr, message| %>
- <% unless attr.to_s.starts_with?('translation') %>
- <%= message %>
- <% end %>
+ <% @public_body_category.errors.each do |error| %>
+ <% unless error.attribute.to_s.starts_with?('translation') %>
+ <%= error.message %>
<% end %>
<% end %>
@@ -20,14 +12,8 @@
<% if translation.errors.any? %>
<%= locale_name(translation.locale.to_s) || translation.locale.to_s %>
- <% if rails_upgrade? %>
- <% translation.errors.each do |error| %>
- <%= error.message %>
- <% end %>
- <% else %>
- <% translation.errors.each do |attr, message| %>
- <%= message %>
- <% end %>
+ <% translation.errors.each do |error| %>
+ <%= error.message %>
<% end %>
<% end %>
diff --git a/app/views/admin_public_body_categories/_heading_list.html.erb b/app/views/admin_public_body_categories/_heading_list.html.erb
index 48b712d9a6..5c82445bf8 100644
--- a/app/views/admin_public_body_categories/_heading_list.html.erb
+++ b/app/views/admin_public_body_categories/_heading_list.html.erb
@@ -3,6 +3,8 @@
+
+
<%= heading.public_body_categories.size %>
<%= chevron_right %>
diff --git a/app/views/admin_public_body_headings/_form.html.erb b/app/views/admin_public_body_headings/_form.html.erb
index 9a314dce82..b5b45b01b4 100644
--- a/app/views/admin_public_body_headings/_form.html.erb
+++ b/app/views/admin_public_body_headings/_form.html.erb
@@ -1,16 +1,8 @@
<% if @public_body_heading.errors.any? %>
- <% if rails_upgrade? %>
- <% @public_body_heading.errors.each do |error| %>
- <% unless error.attribute.to_s.starts_with?('translation') %>
- <%= error.message %>
- <% end %>
- <% end %>
- <% else %>
- <% @public_body_heading.errors.each do |attr, message| %>
- <% unless attr.to_s.starts_with?('translation') %>
- <%= message %>
- <% end %>
+ <% @public_body_heading.errors.each do |error| %>
+ <% unless error.attribute.to_s.starts_with?('translation') %>
+ <%= error.message %>
<% end %>
<% end %>
@@ -20,14 +12,8 @@
<% if translation.errors.any? %>
<%= locale_name(translation.locale.to_s) || translation.locale.to_s %>
- <% if rails_upgrade? %>
- <% translation.errors.each do |error| %>
- <%= error.message %>
- <% end %>
- <% else %>
- <% translation.errors.each do |attr, message| %>
- <%= message %>
- <% end %>
+ <% translation.errors.each do |error| %>
+ <%= error.message %>
<% end %>
<% end %>
diff --git a/app/views/admin_raw_email/show.html.erb b/app/views/admin_raw_email/show.html.erb
index 39e3b41f9f..c0591f7c41 100644
--- a/app/views/admin_raw_email/show.html.erb
+++ b/app/views/admin_raw_email/show.html.erb
@@ -8,7 +8,7 @@
Guessed authority:
<% @public_bodies.each do |public_body| %>
- <%= public_body_both_links(public_body) %>
+ <%= both_links(public_body) %>
<% end %>
(based on From: email domain)
@@ -22,7 +22,7 @@
- <%= request_both_links(guess.info_request) %>
+ <%= both_links(guess.info_request) %>
diff --git a/app/views/admin_request/_some_annotations.html.erb b/app/views/admin_request/_some_annotations.html.erb
index 233e2fd552..2b4caeb2df 100644
--- a/app/views/admin_request/_some_annotations.html.erb
+++ b/app/views/admin_request/_some_annotations.html.erb
@@ -3,33 +3,40 @@