diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e99baefce..5e176f7325 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: MYSQL_PASSWORD: password MYSQL_DATABASE: alchemy_cms_dummy_test MYSQL_ROOT_PASSWORD: password - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - uses: actions/checkout@v1 - name: Set up Ruby @@ -60,15 +60,15 @@ jobs: if: matrix.database == 'postgresql' run: | mkdir -p /home/runner/apt/cache - sudo apt-get update -qq - sudo apt-get install -qq --fix-missing libpq-dev -o dir::cache::archives="/home/runner/apt/cache" + sudo apt update -qq + sudo apt install -qq --fix-missing libpq-dev -o dir::cache::archives="/home/runner/apt/cache" sudo chown -R runner /home/runner/apt/cache - name: Install MySQL headers if: matrix.database == 'mysql' run: | mkdir -p /home/runner/apt/cache - sudo apt-get update -qq - sudo apt-get install -qq --fix-missing libmysqlclient-dev -o dir::cache::archives="/home/runner/apt/cache" + sudo apt update -qq + sudo apt install -qq --fix-missing libmysqlclient-dev -o dir::cache::archives="/home/runner/apt/cache" sudo chown -R runner /home/runner/apt/cache - name: Install bundler run: | @@ -85,11 +85,19 @@ jobs: timeout-minutes: 10 run: | bundle install --jobs 4 --retry 3 --path vendor/bundle + - name: Restore node modules cache + id: yarn-cache + uses: actions/cache@preview + with: + path: spec/dummy/node_modules + key: ${{ runner.os }}-yarn-dummy-${{ hashFiles('./package.json') }} + restore-keys: | + ${{ runner.os }}-yarn-dummy- - name: Prepare database run: | bundle exec rake alchemy:spec:prepare - name: Run tests & publish code coverage - uses: paambaati/codeclimate-action@v2.5.5 + uses: paambaati/codeclimate-action@v2.5.7 env: CC_TEST_REPORTER_ID: bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22 with: @@ -99,3 +107,28 @@ jobs: with: name: Screenshots path: spec/dummy/tmp/screenshots + Jest: + runs-on: ubuntu-latest + env: + NODE_ENV: test + steps: + - uses: actions/checkout@v1 + - name: Restore node modules cache + uses: actions/cache@preview + with: + path: node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('./package.json') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Install yarn + run: yarn install + - name: Run jest + run: yarn jest + - name: Run jest & publish code coverage + uses: paambaati/codeclimate-action@v2.5.7 + env: + CC_TEST_REPORTER_ID: bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22 + with: + coverageLocations: + ./coverage/lcov.info:lcov + coverageCommand: yarn jest --collectCoverage --coverageDirectory=coverage diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 75f22b4762..262e51d5a9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,5 +13,5 @@ jobs: - uses: actions/stale@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue has not seen any activity in a long time. If the issue descriped still exists in recent versions of Alchemy, please open a new issue or preferably open a PR with a fix. Thanks for reporting.' + stale-issue-message: 'This issue has not seen any activity in a long time. If the issue described still exists in recent versions of Alchemy, please open a new issue or preferably open a PR with a fix. Thanks for reporting.' stale-pr-message: 'This pull request has not seen any activiy in a long time. Probably because of missing tests or a necessary rebase. Please open a new PR to latest master if you want to continue working on this. Thanks for the contribution.' diff --git a/.gitignore b/.gitignore index 3a28ffe484..a49a90c1cb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,22 @@ pkg tmp log .sass-cache -spec/dummy/uploads/ +spec/dummy/.browserslistrc +spec/dummy/app/assets/stylesheets/alchemy/ +spec/dummy/app/javascript/ +spec/dummy/babel.config.js +spec/dummy/bin/webpack +spec/dummy/bin/webpack-dev-server +spec/dummy/config/alchemy/config.yml +spec/dummy/config/webpack/ +spec/dummy/config/webpacker.yml spec/dummy/db/*.sqlite3* -spec/dummy/public/assets +spec/dummy/package.json +spec/dummy/postcss.config.js +spec/dummy/public/assets/ +spec/dummy/public/packs/ +spec/dummy/public/packs-test/ +spec/dummy/uploads/ .rvmrc /coverage/ *.gem @@ -22,3 +35,8 @@ spec/dummy/public/assets .ruby-version .env .rspec +node_modules +yarn-error.log +yarn-debug.log* +.yarn-integrity +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..348cb46f7b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "trailingComma": "none", + "vueIndentScriptAndStyle": true, + "arrowParens": "always" +} diff --git a/.rubocop.yml b/.rubocop.yml index 076dc5c8a0..05dd2cc324 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,7 @@ # Relaxed.Ruby.Style AllCops: - TargetRubyVersion: 2.3 + TargetRubyVersion: 2.4 Exclude: - 'bin/rspec' - 'vendor/**/*' @@ -9,6 +9,7 @@ AllCops: - 'spec/dummy/config/**/*' - 'alchemy_cms.gemspec' - 'Rakefile' + - 'node_modules/**/*' # Really, rubocop? Bundler/OrderedGems: @@ -220,8 +221,19 @@ Style/SpecialGlobalVars: StyleGuide: http://relaxed.ruby.style/#stylespecialglobalvars Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes + +Style/TrailingCommaInArguments: Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylestringliterals + +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: consistent_comma Style/WhileUntilModifier: Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index f9002419dc..0e62bef2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,74 @@ ## 5.0.0 (unreleased) +- Language Factory: Create default language in host app's locale [#1884](https://github.com/AlchemyCMS/alchemy_cms/pull/1884) ([mamhoff](https://github.com/mamhoff)) +- Respect filter and tagging params in picture archive size buttons [#1880](https://github.com/AlchemyCMS/alchemy_cms/pull/1880) ([tvdeyen](https://github.com/tvdeyen)) +- Extract picture thumbnail sizes in a constant [#1879](https://github.com/AlchemyCMS/alchemy_cms/pull/1879) ([tvdeyen](https://github.com/tvdeyen)) +- Configurable Image Preprocessor [#1878](https://github.com/AlchemyCMS/alchemy_cms/pull/1878) ([tvdeyen](https://github.com/tvdeyen)) +- Configure edit page preview per site [#1877](https://github.com/AlchemyCMS/alchemy_cms/pull/1877) ([tvdeyen](https://github.com/tvdeyen)) +- Fix Page tree sorting after root page removal [#1876](https://github.com/AlchemyCMS/alchemy_cms/pull/1876) ([tvdeyen](https://github.com/tvdeyen)) +- 5.0 Upgrader fixes [#1874](https://github.com/AlchemyCMS/alchemy_cms/pull/1874) ([tvdeyen](https://github.com/tvdeyen)) +- Remove url_nesting config [#1872](https://github.com/AlchemyCMS/alchemy_cms/pull/1872) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade sassc to version 2.4.0 [#1871](https://github.com/AlchemyCMS/alchemy_cms/pull/1871) ([depfu](https://github.com/apps/depfu)) +- fix GitHub Actions spelling [#1869](https://github.com/AlchemyCMS/alchemy_cms/pull/1869) ([alexanderadam](https://github.com/alexanderadam)) +- Remove Page#visible [#1868](https://github.com/AlchemyCMS/alchemy_cms/pull/1868) ([tvdeyen](https://github.com/tvdeyen)) +- 4.6 backports for master [#1867](https://github.com/AlchemyCMS/alchemy_cms/pull/1867) ([tvdeyen](https://github.com/tvdeyen)) +- Use apt update instead of apt-get in GH action [#1866](https://github.com/AlchemyCMS/alchemy_cms/pull/1866) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade rubocop to version 0.85.0 [#1863](https://github.com/AlchemyCMS/alchemy_cms/pull/1863) ([depfu](https://github.com/apps/depfu)) +- Remove active_record_5_1? method [#1854](https://github.com/AlchemyCMS/alchemy_cms/pull/1854) ([tvdeyen](https://github.com/tvdeyen)) +- Use Alchemy npm package instead of hacking webpacker [#1853](https://github.com/AlchemyCMS/alchemy_cms/pull/1853) ([tvdeyen](https://github.com/tvdeyen)) +- Fix node select ES5 syntax [#1851](https://github.com/AlchemyCMS/alchemy_cms/pull/1851) ([tvdeyen](https://github.com/tvdeyen)) +- Run yarn:install after installing webpacker in install generator [#1850](https://github.com/AlchemyCMS/alchemy_cms/pull/1850) ([mamhoff](https://github.com/mamhoff)) +- Remove male sign after emoji [#1849](https://github.com/AlchemyCMS/alchemy_cms/pull/1849) ([mamhoff](https://github.com/mamhoff)) +- Do not use ES6 Syntax in Node Selector [#1846](https://github.com/AlchemyCMS/alchemy_cms/pull/1846) ([mamhoff](https://github.com/mamhoff)) +- [ruby] Upgrade rubocop to version 0.84.0 [#1845](https://github.com/AlchemyCMS/alchemy_cms/pull/1845) ([depfu](https://github.com/apps/depfu)) +- Always create nested urls [#1844](https://github.com/AlchemyCMS/alchemy_cms/pull/1844) ([tvdeyen](https://github.com/tvdeyen)) +- Fix: Add indifferent access to default options in encoded_image [#1840](https://github.com/AlchemyCMS/alchemy_cms/pull/1840) ([mickenorlen](https://github.com/mickenorlen)) +- Set proper nested set scope on page [#1837](https://github.com/AlchemyCMS/alchemy_cms/pull/1837) ([tvdeyen](https://github.com/tvdeyen)) +- Install Webpacker in install generator [#1835](https://github.com/AlchemyCMS/alchemy_cms/pull/1835) ([mamhoff](https://github.com/mamhoff)) +- Fix deleting an EssenceNode from a content [#1834](https://github.com/AlchemyCMS/alchemy_cms/pull/1834) ([mamhoff](https://github.com/mamhoff)) +- Use Rails standards for deleting pages from EssencePage [#1833](https://github.com/AlchemyCMS/alchemy_cms/pull/1833) ([mamhoff](https://github.com/mamhoff)) +- Scope has one site [#1832](https://github.com/AlchemyCMS/alchemy_cms/pull/1832) ([mamhoff](https://github.com/mamhoff)) +- Render nodes [#1831](https://github.com/AlchemyCMS/alchemy_cms/pull/1831) ([mamhoff](https://github.com/mamhoff)) +- Add errors when node cant be deleted [#1828](https://github.com/AlchemyCMS/alchemy_cms/pull/1828) ([mamhoff](https://github.com/mamhoff)) +- Add error flash to resource controller [#1827](https://github.com/AlchemyCMS/alchemy_cms/pull/1827) ([mamhoff](https://github.com/mamhoff)) +- Fix Association between Nodes and EssenceNodes [#1826](https://github.com/AlchemyCMS/alchemy_cms/pull/1826) ([mamhoff](https://github.com/mamhoff)) +- Translated root menus [#1825](https://github.com/AlchemyCMS/alchemy_cms/pull/1825) ([mamhoff](https://github.com/mamhoff)) +- Use rails root in install generator [#1822](https://github.com/AlchemyCMS/alchemy_cms/pull/1822) ([tvdeyen](https://github.com/tvdeyen)) +- Add a quick Node select [#1821](https://github.com/AlchemyCMS/alchemy_cms/pull/1821) ([mamhoff](https://github.com/mamhoff)) +- Add has_one association for root page [#1820](https://github.com/AlchemyCMS/alchemy_cms/pull/1820) ([mamhoff](https://github.com/mamhoff)) +- [js] Upgrade babel-jest to version 26.0.1 [#1819](https://github.com/AlchemyCMS/alchemy_cms/pull/1819) ([depfu](https://github.com/apps/depfu)) +- Make page language mandatory [#1818](https://github.com/AlchemyCMS/alchemy_cms/pull/1818) ([tvdeyen](https://github.com/tvdeyen)) +- Remove root page [#1817](https://github.com/AlchemyCMS/alchemy_cms/pull/1817) ([tvdeyen](https://github.com/tvdeyen)) +- Fix page unlock page icon replacement [#1816](https://github.com/AlchemyCMS/alchemy_cms/pull/1816) ([tvdeyen](https://github.com/tvdeyen)) +- Invoke rake task in upgrader instead of system call [#1815](https://github.com/AlchemyCMS/alchemy_cms/pull/1815) ([tvdeyen](https://github.com/tvdeyen)) +- Remove old 4.4 upgrader class [#1814](https://github.com/AlchemyCMS/alchemy_cms/pull/1814) ([tvdeyen](https://github.com/tvdeyen)) +- Remove Page.ancestors_for [#1813](https://github.com/AlchemyCMS/alchemy_cms/pull/1813) ([tvdeyen](https://github.com/tvdeyen)) +- Remove layout root pages [#1812](https://github.com/AlchemyCMS/alchemy_cms/pull/1812) ([tvdeyen](https://github.com/tvdeyen)) +- Use timestamps in migration [#1811](https://github.com/AlchemyCMS/alchemy_cms/pull/1811) ([tvdeyen](https://github.com/tvdeyen)) +- Remove legacy element serializer [#1810](https://github.com/AlchemyCMS/alchemy_cms/pull/1810) ([tvdeyen](https://github.com/tvdeyen)) +- Remove timestamps from essences and contents [#1809](https://github.com/AlchemyCMS/alchemy_cms/pull/1809) ([tvdeyen](https://github.com/tvdeyen)) +- Remove stamper from contents [#1808](https://github.com/AlchemyCMS/alchemy_cms/pull/1808) ([tvdeyen](https://github.com/tvdeyen)) +- Remove Site ID from nodes [#1807](https://github.com/AlchemyCMS/alchemy_cms/pull/1807) ([mamhoff](https://github.com/mamhoff)) +- Add Alchemy::Language.has_many :nodes [#1806](https://github.com/AlchemyCMS/alchemy_cms/pull/1806) ([mamhoff](https://github.com/mamhoff)) +- Drop Rails 5.0 and 5.1 support [#1805](https://github.com/AlchemyCMS/alchemy_cms/pull/1805) ([tvdeyen](https://github.com/tvdeyen)) +- Remove enforce_ssl [#1804](https://github.com/AlchemyCMS/alchemy_cms/pull/1804) ([tvdeyen](https://github.com/tvdeyen)) +- Make the preview url configurable [#1803](https://github.com/AlchemyCMS/alchemy_cms/pull/1803) ([tvdeyen](https://github.com/tvdeyen)) +- Remove stamper from essences [#1802](https://github.com/AlchemyCMS/alchemy_cms/pull/1802) ([tvdeyen](https://github.com/tvdeyen)) +- Use Rufo to format all files in a consistent way [#1799](https://github.com/AlchemyCMS/alchemy_cms/pull/1799) ([tvdeyen](https://github.com/tvdeyen)) +- Remove acts_as_list from Content [#1798](https://github.com/AlchemyCMS/alchemy_cms/pull/1798) ([tvdeyen](https://github.com/tvdeyen)) +- Add EssenceNode [#1792](https://github.com/AlchemyCMS/alchemy_cms/pull/1792) ([mamhoff](https://github.com/mamhoff)) +- Use 2.5.7 of code climate coverage reporter GH action [#1790](https://github.com/AlchemyCMS/alchemy_cms/pull/1790) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade sassc to version 2.3.0 [#1787](https://github.com/AlchemyCMS/alchemy_cms/pull/1787) ([depfu](https://github.com/apps/depfu)) +- [ruby] Upgrade rubocop to version 0.82.0 [#1785](https://github.com/AlchemyCMS/alchemy_cms/pull/1785) ([depfu](https://github.com/apps/depfu)) +- Fix regular icons [#1784](https://github.com/AlchemyCMS/alchemy_cms/pull/1784) ([tvdeyen](https://github.com/tvdeyen)) +- Convert NodeTree into ES6 [#1782](https://github.com/AlchemyCMS/alchemy_cms/pull/1782) ([tvdeyen](https://github.com/tvdeyen)) +- Add Webpacker [#1775](https://github.com/AlchemyCMS/alchemy_cms/pull/1775) ([tvdeyen](https://github.com/tvdeyen)) +- Multi language menus [#1774](https://github.com/AlchemyCMS/alchemy_cms/pull/1774) ([rmparr](https://github.com/rmparr)) +- On Boarding Flow [#1770](https://github.com/AlchemyCMS/alchemy_cms/pull/1770) ([tvdeyen](https://github.com/tvdeyen)) +- Fix bug in language from session w/o site [#1769](https://github.com/AlchemyCMS/alchemy_cms/pull/1769) ([tvdeyen](https://github.com/tvdeyen)) +- Fix fontawesome in production [#1765](https://github.com/AlchemyCMS/alchemy_cms/pull/1765) ([mickenorlen](https://github.com/mickenorlen)) +- Remove implicit Site and Language creation [#1763](https://github.com/AlchemyCMS/alchemy_cms/pull/1763) ([mamhoff](https://github.com/mamhoff)) +- Add content editor data attributes based on name/id and css_classes presenter method [#1761](https://github.com/AlchemyCMS/alchemy_cms/pull/1761) ([mickenorlen](https://github.com/mickenorlen)) - Add alchemy.test to development domains [#1760](https://github.com/AlchemyCMS/alchemy_cms/pull/1760) ([tvdeyen](https://github.com/tvdeyen)) - Update Fontawesome [#1759](https://github.com/AlchemyCMS/alchemy_cms/pull/1759) ([tvdeyen](https://github.com/tvdeyen)) - Fix test coverage reporting [#1757](https://github.com/AlchemyCMS/alchemy_cms/pull/1757) ([tvdeyen](https://github.com/tvdeyen)) @@ -24,6 +93,27 @@ - Add ContentEditor decorator [#1645](https://github.com/AlchemyCMS/alchemy_cms/pull/1645) ([tvdeyen](https://github.com/tvdeyen)) - Remove local options from essence editors [#1638](https://github.com/AlchemyCMS/alchemy_cms/pull/1638) ([tvdeyen](https://github.com/tvdeyen)) +## 4.6.1 (2020-06-04) + +- Fix 4.6 upgrader + +## 4.6.0 (2020-06-04) + +- Use apt update instead of apt-get in GH action [#1865](https://github.com/AlchemyCMS/alchemy_cms/pull/1865) ([tvdeyen](https://github.com/tvdeyen)) +- Use depth for page tree serializer root_or_leaf [#1864](https://github.com/AlchemyCMS/alchemy_cms/pull/1864) ([tvdeyen](https://github.com/tvdeyen)) +- Fix sitemap wrapper height [#1861](https://github.com/AlchemyCMS/alchemy_cms/pull/1861) ([tvdeyen](https://github.com/tvdeyen)) +- Do not return the root page with API responses. [#1860](https://github.com/AlchemyCMS/alchemy_cms/pull/1860) ([tvdeyen](https://github.com/tvdeyen)) +- Introduce page.url_path and use it for alchemyPageSelect [#1859](https://github.com/AlchemyCMS/alchemy_cms/pull/1859) ([tvdeyen](https://github.com/tvdeyen)) +- Update Urlname translation [#1857](https://github.com/AlchemyCMS/alchemy_cms/pull/1857) ([tvdeyen](https://github.com/tvdeyen)) +- Show url name in Page tree [#1856](https://github.com/AlchemyCMS/alchemy_cms/pull/1856) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate Page#visible attribute [#1855](https://github.com/AlchemyCMS/alchemy_cms/pull/1855) ([tvdeyen](https://github.com/tvdeyen)) +- 4.6: Re-add `auto_logout_time` configuration option [#1852](https://github.com/AlchemyCMS/alchemy_cms/pull/1852) ([mamhoff](https://github.com/mamhoff)) +- Backport ContentEditor to 4.6, deprecate removed methods on `Alchemy::Content` [#1847](https://github.com/AlchemyCMS/alchemy_cms/pull/1847) ([mamhoff](https://github.com/mamhoff)) +- Deprecate auto_logout_time (4.6) [#1843](https://github.com/AlchemyCMS/alchemy_cms/pull/1843) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate require_ssl (4.6) [#1842](https://github.com/AlchemyCMS/alchemy_cms/pull/1842) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate url_nesting configuration (4.6) [#1841](https://github.com/AlchemyCMS/alchemy_cms/pull/1841) ([tvdeyen](https://github.com/tvdeyen)) +- Allow page visible toggle (4.6) [#1838](https://github.com/AlchemyCMS/alchemy_cms/pull/1838) ([tvdeyen](https://github.com/tvdeyen)) + ## 4.5.0 (2020-03-30) - Sortable menus [#1758](https://github.com/AlchemyCMS/alchemy_cms/pull/1758) ([mamhoff](https://github.com/mamhoff)) diff --git a/Gemfile b/Gemfile index e43124be57..b1f343681e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,33 +1,34 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" gemspec -rails_version = ENV.fetch('RAILS_VERSION', 6.0).to_f -gem 'rails', "~> #{rails_version}.0" +rails_version = ENV.fetch("RAILS_VERSION", 6.0).to_f +gem "rails", "~> #{rails_version}.0" -if ENV['DB'].nil? || ENV['DB'] == 'sqlite' - gem 'sqlite3', rails_version > 5.0 ? '~> 1.4.1' : '~> 1.3.6' +if ENV["DB"].nil? || ENV["DB"] == "sqlite" + gem "sqlite3", "~> 1.4.1" end -gem 'mysql2', '~> 0.5.1' if ENV['DB'] == 'mysql' -gem 'pg', '~> 1.0' if ENV['DB'] == 'postgresql' +gem "mysql2", "~> 0.5.1" if ENV["DB"] == "mysql" +gem "pg", "~> 1.0" if ENV["DB"] == "postgresql" group :development, :test do - if ENV['GITHUB_ACTIONS'] - gem 'sassc', '~> 2.1.0' # https://github.com/sass/sassc-ruby/issues/146 + if ENV["GITHUB_ACTIONS"] + gem "sassc", "~> 2.4.0" # https://github.com/sass/sassc-ruby/issues/146 else - gem 'launchy' - gem 'annotate' - gem 'bumpy' - gem 'yard' - gem 'redcarpet' - gem 'pry-byebug' - gem 'rubocop', '~> 0.80.1', require: false - gem 'listen' - gem 'localeapp', '~> 3.0', require: false - gem 'dotenv', '~> 2.2' - gem 'github_fast_changelog', require: false - gem 'active_record_query_trace', require: false - gem 'rack-mini-profiler', require: false + gem "launchy" + gem "annotate" + gem "bumpy" + gem "yard" + gem "redcarpet" + gem "pry-byebug" + gem "rubocop", "~> 0.85.0", require: false + gem "listen" + gem "localeapp", "~> 3.0", require: false + gem "dotenv", "~> 2.2" + gem "github_fast_changelog", require: false + gem "active_record_query_trace", require: false + gem "rack-mini-profiler", require: false + gem "rufo", require: false end end diff --git a/README.md b/README.md index f559b8597b..a972eb19cf 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,9 @@ or visit the existing demo at https://alchemy-demo.herokuapp.com ## 🚂 Rails Version -**This version of AlchemyCMS runs with all versions of Rails 5 and Rails 6** +**This version of AlchemyCMS runs with Rails 5.2 and Rails 6.0** +* For a Rails 5.0 or 5.1 compatible version use the [`4.5-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/4.5-stable). * For a Rails 4.2 compatible version use the [`3.6-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.6-stable). * For a Rails 4.0/4.1 compatible version use the [`3.1-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.1-stable). * For a Rails 3.2 compatible version use the [`2.8-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/2.8-stable). diff --git a/Rakefile b/Rakefile index f3c0b67156..494e284beb 100644 --- a/Rakefile +++ b/Rakefile @@ -45,6 +45,7 @@ namespace :alchemy do bin/rake db:create && \ bin/rake db:environment:set && \ bin/rake db:migrate:reset && \ + bin/rails g alchemy:install --skip --skip-demo-files && \ cd - BASH result || fail diff --git a/alchemy_cms.gemspec b/alchemy_cms.gemspec index 7d442a1b3c..74dec0ef7e 100644 --- a/alchemy_cms.gemspec +++ b/alchemy_cms.gemspec @@ -8,10 +8,10 @@ Gem::Specification.new do |gem| gem.version = Alchemy::VERSION gem.platform = Gem::Platform::RUBY gem.authors = ['Thomas von Deyen', 'Robin Boening', 'Marc Schettke', 'Hendrik Mans', 'Carsten Fregin', 'Martin Meyerhoff'] - gem.email = ['alchemy@magiclabs.de'] + gem.email = ['hello@alchemy-cms.com'] gem.homepage = 'https://alchemy-cms.com' - gem.summary = 'A powerful, userfriendly and flexible CMS for Rails 5' - gem.description = 'Alchemy is a powerful, userfriendly and flexible Rails 5 CMS.' + gem.summary = 'A powerful, userfriendly and flexible CMS for Rails' + gem.description = 'Alchemy is a powerful, userfriendly and flexible Rails CMS.' gem.requirements << 'ImageMagick (libmagick), v6.6 or greater.' gem.required_ruby_version = '>= 2.3.0' gem.license = 'BSD New' @@ -32,7 +32,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'kaminari', ['~> 1.1'] gem.add_runtime_dependency 'originator', ['~> 3.1'] gem.add_runtime_dependency 'non-stupid-digest-assets', ['~> 1.0.8'] - gem.add_runtime_dependency 'rails', ['>= 5.0.0', '< 6.1'] + gem.add_runtime_dependency 'rails', ['>= 5.2.0', '< 6.1'] gem.add_runtime_dependency 'ransack', ['>= 1.8', '< 3.0'] gem.add_runtime_dependency 'request_store', ['~> 1.2'] gem.add_runtime_dependency 'responders', ['>= 2.0', '< 4.0'] @@ -41,10 +41,11 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'simple_form', ['>= 4.0', '< 6'] gem.add_runtime_dependency 'sprockets', ['>= 3.0', '< 5'] gem.add_runtime_dependency 'turbolinks', ['>= 2.5'] + gem.add_runtime_dependency 'webpacker', ['>= 4.0', '< 6'] gem.add_development_dependency 'capybara', ['~> 3.0'] gem.add_development_dependency 'capybara-screenshot', ['~> 1.0'] - gem.add_development_dependency 'factory_bot_rails', ['~> 5.0'] + gem.add_development_dependency 'factory_bot_rails', ['~> 6.0'] gem.add_development_dependency 'puma', ['~> 4.0'] gem.add_development_dependency 'rails-controller-testing', ['~> 1.0'] gem.add_development_dependency 'rspec-activemodel-mocks', ['~> 1.0'] diff --git a/app/assets/javascripts/alchemy/admin.js b/app/assets/javascripts/alchemy/admin.js index 7b728dfd24..32c2d118e3 100644 --- a/app/assets/javascripts/alchemy/admin.js +++ b/app/assets/javascripts/alchemy/admin.js @@ -15,10 +15,8 @@ //= require requestAnimationFrame //= require select2 //= require handlebars -//= require sortable/Sortable.min //= require alchemy/templates //= require alchemy/alchemy.base -//= require alchemy/alchemy.utils //= require alchemy/alchemy.autocomplete //= require alchemy/alchemy.browser //= require alchemy/alchemy.buttons @@ -34,14 +32,12 @@ //= require alchemy/alchemy.growler //= require alchemy/alchemy.gui //= require alchemy/alchemy.hotkeys -//= require alchemy/alchemy.i18n //= require alchemy/alchemy.image_cropper //= require alchemy/alchemy.image_overlay //= require alchemy/alchemy.string_extension //= require alchemy/alchemy.link_dialog //= require alchemy/alchemy.list_filter //= require alchemy/alchemy.initializer -//= require alchemy/alchemy.node_tree //= require alchemy/alchemy.page_sorter //= require alchemy/alchemy.uploader //= require alchemy/alchemy.preview_window @@ -49,6 +45,6 @@ //= require alchemy/alchemy.spinner //= require alchemy/alchemy.tinymce //= require alchemy/alchemy.tooltips -//= require alchemy/alchemy.translations //= require alchemy/alchemy.trash_window //= require alchemy/page_select +//= require alchemy/node_select diff --git a/app/assets/javascripts/alchemy/alchemy.base.js.coffee b/app/assets/javascripts/alchemy/alchemy.base.js.coffee index db7ea0bbe8..20b7de18a8 100644 --- a/app/assets/javascripts/alchemy/alchemy.base.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.base.js.coffee @@ -62,9 +62,10 @@ $.extend Alchemy, removePicture: (selector) -> $form_field = $(selector) $element = $form_field.closest(".element-editor") + $content = $form_field.closest(".content_editor") if $form_field[0] $form_field.val "" - $element.find(".thumbnail_background").html('') + $content.find(".thumbnail_background").html('') Alchemy.setElementDirty $element false diff --git a/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee b/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee index 1d86ff16f1..80ae7b18be 100644 --- a/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee @@ -179,8 +179,6 @@ Alchemy.ElementEditors = if data.message == 'Alchemy.focusElementEditor' $element = $("#element_#{data.element_id}") Alchemy.ElementEditors.focusElement($element) - else - console.warn 'Unknown message received!', data onClickBody: (e) -> element = $(e.target).parents('.element-editor')[0] diff --git a/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee b/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee deleted file mode 100644 index 5f08c82c07..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee +++ /dev/null @@ -1,32 +0,0 @@ -#= require alchemy/alchemy.translations - -window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' - -Alchemy.I18n = - - KEY_SEPARATOR: /\./ - - # Translates given string - # - translate: (key, replacement) -> - if !Alchemy.locale? - throw 'Alchemy.locale is not set! Please set Alchemy.locale to a locale string in order to translate something.' - translations = Alchemy.translations[Alchemy.locale] - if translations - if @KEY_SEPARATOR.test(key) - keys = key.split(@KEY_SEPARATOR) - translation = translations[keys[0]][keys[1]] || key - else - translation = translations[key] || key - if replacement - translation.replace(/%\{.+\}/, replacement) - else - translation - else - console.warn "Translations for locale #{Alchemy.locale} not found!" - key - -# Global utility method for translating a given string -# -Alchemy.t = (key, replacement) -> - Alchemy.I18n.translate(key, replacement) diff --git a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee index 46bf1bad57..cbc7e463b3 100644 --- a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee @@ -76,16 +76,16 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog meta = data.meta results: data.pages.map (page) -> - id: "/#{page.urlname}" + id: page.url_path name: page.name - urlname: page.urlname + url_path: page.url_path page_id: page.id more: meta.page * meta.per_page < meta.total_count initSelection: ($element, callback) => urlname = $element.val() $.get Alchemy.routes.api_pages_path, q: - urlname_eq: urlname.replace(/^\//, '') + urlname_eq: urlname.replace(/^\/([a-z]{2}(-[A-Z]{2})?\/)?/, '') page: 1 per_page: 1, (data) => @@ -93,9 +93,9 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog if page @initElementSelect(page.id) callback - id: "/#{page.urlname}" + id: page.url_path name: page.name - urlname: page.name + url_path: page.url_path page_id: page.id formatSelection: (page) -> page.name diff --git a/app/assets/javascripts/alchemy/alchemy.node_tree.js b/app/assets/javascripts/alchemy/alchemy.node_tree.js deleted file mode 100644 index 9213d8bb70..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.node_tree.js +++ /dev/null @@ -1,66 +0,0 @@ -Alchemy.NodeTree = { - onFinishDragging: function (evt) { - var url = Alchemy.routes.move_api_node_path(evt.item.dataset.id) - var data = { - target_parent_id: evt.to.dataset.nodeId, - new_position: evt.newIndex - }; - var ajax = Alchemy.ajax('PATCH', url, data) - - ajax.then(function(response) { - Alchemy.growl('Successfully moved menu item.') - Alchemy.NodeTree.displayNodeFolders() - }).catch(function() { - Alchemy.growl(error.message || error); - }) - }, - - displayNodeFolders: function () { - document.querySelectorAll('li.menu-item').forEach(function (el) { - var leftIconArea = el.querySelector('.nodes_tree-left_images') - var list = el.querySelector('ul') - var node = { folded: el.dataset.folded === 'true', id: el.dataset.id } - - if (list.children.length > 0 || node.folded ) { - leftIconArea.innerHTML = HandlebarsTemplates.node_folder({ node: node }) - } else { - leftIconArea.innerHTML = ' ' - } - }); - }, - - handleNodeFolders: function() { - Alchemy.on('click', '.nodes_tree', '.node_folder', function(evt) { - var nodeId = this.dataset.nodeId - var menu_item = this.closest('li.menu-item') - var url = Alchemy.routes.toggle_folded_api_node_path(nodeId) - var list = menu_item.querySelector('.children') - var ajax = Alchemy.ajax('PATCH', url) - - ajax.then(function() { - list.classList.toggle('folded') - menu_item.dataset.folded = menu_item.dataset.folded == 'true' ? 'false' : 'true' - Alchemy.NodeTree.displayNodeFolders(); - }).catch(function(error){ - Alchemy.growl(error.message || error); - }); - }); - }, - - init: function() { - this.handleNodeFolders() - this.displayNodeFolders() - - document.querySelectorAll('.nodes_tree ul.children').forEach(function (el) { - new Sortable(el, { - group: 'nodes', - animation: 150, - fallbackOnBody: true, - swapThreshold: 0.65, - handle: '.node_name', - invertSwap: true, - onEnd: Alchemy.NodeTree.onFinishDragging - }); - }); - } -} diff --git a/app/assets/javascripts/alchemy/alchemy.page_sorter.js b/app/assets/javascripts/alchemy/alchemy.page_sorter.js index df57ce4be1..9c4039c97d 100644 --- a/app/assets/javascripts/alchemy/alchemy.page_sorter.js +++ b/app/assets/javascripts/alchemy/alchemy.page_sorter.js @@ -1,24 +1,24 @@ -Alchemy.PageSorter = function() { - var $sortables = $('ul#sitemap').find('ul.level_1_children'); +Alchemy.PageSorter = function () { + var $sortables = $("ul#sitemap").find("ul.level_0_children") $sortables.nestedSortable({ - disableNesting: 'no-nest', + disableNesting: "no-nest", forcePlaceholderSize: true, - handle: '.handle', - items: 'li', - listType: 'ul', + handle: ".handle", + items: "li", + listType: "ul", opacity: 0.5, - placeholder: 'placeholder', + placeholder: "placeholder", tabSize: 16, - tolerance: 'pointer', - toleranceElement: '> div' - }); + tolerance: "pointer", + toleranceElement: "> div" + }) - $('#save_page_order').click(function(e) { - e.preventDefault(); - Alchemy.Buttons.disable(this); + $("#save_page_order").click(function (e) { + e.preventDefault() + Alchemy.Buttons.disable(this) $.post(Alchemy.routes.order_admin_pages_path, { - set: JSON.stringify($sortables.nestedSortable('toHierarchy')) - }); - }); -}; + set: JSON.stringify($sortables.nestedSortable("toHierarchy")) + }) + }) +} diff --git a/app/assets/javascripts/alchemy/alchemy.translations.js.coffee b/app/assets/javascripts/alchemy/alchemy.translations.js.coffee deleted file mode 100644 index 57e2f84d49..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +++ /dev/null @@ -1,29 +0,0 @@ -window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' - -# Holds translations for javascripts -# -Alchemy.translations = - en: - allowed_chars: 'of %{count} chars' - cancel: 'Cancel' - cancelled: 'Cancelled' - click_to_edit: 'click to edit' - complete: 'Complete' - element_dirty_notice: 'This element has unsaved changes. Do you really want to fold it?' - help: 'Help' - ok: 'Ok' - page_dirty_notice: 'You have unsaved changes on this page. They will be lost if you continue.' - page_found: 'Page found' - pages_found: 'Pages found' - url_validation_failed: 'The url has no valid format.' - warning: 'Warning!' - 'File is too large': 'File is too large' - 'File is too small': 'File is too small' - 'File type not allowed': 'File type not allowed' - 'Maximum number of files exceeded': 'Maximum number of files exceeded.' - 'Uploaded bytes exceed file size': 'Uploaded bytes exceed file size' - formats: - datetime: "Y-m-d H:i" - date: "Y-m-d" - time: "H:i" - time_24hr: false diff --git a/app/assets/javascripts/alchemy/alchemy.utils.js b/app/assets/javascripts/alchemy/alchemy.utils.js deleted file mode 100644 index 5c35fa08e0..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.utils.js +++ /dev/null @@ -1,45 +0,0 @@ -Alchemy.on = function (eventName, baseSelector, targetSelector, callback) { - var baseNode = document.querySelector(baseSelector) - baseNode.addEventListener(eventName, function (evt) { - var targets = Array.from(baseNode.querySelectorAll(targetSelector)) - var currentNode = evt.target - while (currentNode !== baseNode) { - if (targets.includes(currentNode)) { - callback.call(currentNode, evt) - return - } - currentNode = currentNode.parentElement - } - }); -} - -Alchemy.ajax = function(method, url, data) { - var xhr = new XMLHttpRequest() - var token = document.querySelector('meta[name="csrf-token"]').attributes.content.textContent - var promise = new Promise(function (resolve, reject) { - xhr.onload = function() { - try { - resolve({ - data: JSON.parse(xhr.responseText), - status: xhr.status - }) - } catch (error) { - reject(new Error(JSON.parse(xhr.responseText).error)) - } - }; - xhr.onerror = function() { - reject(new Error(xhr.statusText)) - } - }); - xhr.open(method, url); - xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader('X-CSRF-Token', token) - if (data) { - xhr.send(JSON.stringify(data)) - } else { - xhr.send() - } - - return promise -} diff --git a/app/assets/javascripts/alchemy/node_select.js b/app/assets/javascripts/alchemy/node_select.js new file mode 100644 index 0000000000..a358703905 --- /dev/null +++ b/app/assets/javascripts/alchemy/node_select.js @@ -0,0 +1,39 @@ +$.fn.alchemyNodeSelect = function (options) { + var renderNodeTemplate = function (node) { + return HandlebarsTemplates.node({ node: node }) + } + var queryParamsFromTerm = function (term) { + return { + filter: Object.assign( + { name_or_page_name_cont: term }, + options.query_params + ) + } + } + var resultsFromResponse = function (response) { + var meta = response.meta + var data = response.data + var more = meta.page * meta.per_page < meta.total_count + return { results: data, more: more } + } + + return this.select2({ + placeholder: options.placeholder, + allowClear: true, + minimumInputLength: 3, + initSelection: function (_$el, callback) { + if (options.initialSelection) { + callback(options.initialSelection) + } + }, + ajax: { + url: options.url, + datatype: "json", + quietMillis: 300, + data: queryParamsFromTerm, + results: resultsFromResponse + }, + formatSelection: renderNodeTemplate, + formatResult: renderNodeTemplate + }) +} diff --git a/app/assets/javascripts/alchemy/templates/index.js b/app/assets/javascripts/alchemy/templates/index.js index 0405d22de6..acbaf8b3db 100644 --- a/app/assets/javascripts/alchemy/templates/index.js +++ b/app/assets/javascripts/alchemy/templates/index.js @@ -1,3 +1,4 @@ //= require alchemy/templates/spinner //= require alchemy/templates/page //= require alchemy/templates/node_folder +//= require alchemy/templates/node diff --git a/app/assets/javascripts/alchemy/templates/node.hbs b/app/assets/javascripts/alchemy/templates/node.hbs new file mode 100644 index 0000000000..50d487d912 --- /dev/null +++ b/app/assets/javascripts/alchemy/templates/node.hbs @@ -0,0 +1,16 @@ +
#{Alchemy.t(:content_validations_headline)}
".html_safe end end @@ -81,7 +81,7 @@ def order Element.where(id: element_id).update_all( page_id: params[:page_id], parent_element_id: params[:parent_element_id], - position: idx + 1 + position: idx + 1, ) end @parent_element.try!(:touch) @@ -100,20 +100,20 @@ def element_includes [ { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, :tags, { all_nested_elements: [ { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags - ] - } + :tags, + ], + }, ] end @@ -123,20 +123,20 @@ def load_element def element_from_clipboard @element_from_clipboard ||= begin - @clipboard = get_clipboard('elements') - @clipboard.detect { |item| item['id'].to_i == params[:paste_from_clipboard].to_i } - end + @clipboard = get_clipboard("elements") + @clipboard.detect { |item| item["id"].to_i == params[:paste_from_clipboard].to_i } + end end def paste_element_from_clipboard - @source_element = Element.find(element_from_clipboard['id']) + @source_element = Element.find(element_from_clipboard["id"]) element = Element.copy(@source_element, { parent_element_id: create_element_params[:parent_element_id], - page_id: @page.id} - ) - if element_from_clipboard['action'] == 'cut' + page_id: @page.id, + }) + if element_from_clipboard["action"] == "cut" @cut_element_id = @source_element.id - @clipboard.delete_if { |item| item['id'] == @source_element.id.to_s } + @clipboard.delete_if { |item| item["id"] == @source_element.id.to_s } @source_element.destroy end element diff --git a/app/controllers/alchemy/admin/essence_pictures_controller.rb b/app/controllers/alchemy/admin/essence_pictures_controller.rb index 4bc85b2c9e..9656635ec5 100644 --- a/app/controllers/alchemy/admin/essence_pictures_controller.rb +++ b/app/controllers/alchemy/admin/essence_pictures_controller.rb @@ -9,9 +9,9 @@ class EssencePicturesController < Alchemy::Admin::BaseController before_action :load_essence_picture, only: [:edit, :crop, :update] before_action :load_content, only: [:edit, :update, :assign] - helper 'alchemy/admin/contents' - helper 'alchemy/admin/essences' - helper 'alchemy/url' + helper "alchemy/admin/contents" + helper "alchemy/admin/essences" + helper "alchemy/url" def edit end diff --git a/app/controllers/alchemy/admin/languages_controller.rb b/app/controllers/alchemy/admin/languages_controller.rb index b3f97d30b9..402ee0e395 100644 --- a/app/controllers/alchemy/admin/languages_controller.rb +++ b/app/controllers/alchemy/admin/languages_controller.rb @@ -14,14 +14,14 @@ def index def new @language = Language.new( site: @current_site, - page_layout: Config.get(:default_language)['page_layout'] + page_layout: Config.get(:default_language)["page_layout"], ) end def create @language = Alchemy::Language.new(resource_params) if @language.save - flash[:notice] = Alchemy.t('Language successfully created') + flash[:notice] = Alchemy.t("Language successfully created") redirect_to alchemy.admin_pages_path(language_id: @language) else render :new @@ -30,7 +30,7 @@ def create def destroy if @language.destroy - flash[:notice] = Alchemy.t('Language successfully removed') + flash[:notice] = Alchemy.t("Language successfully removed") else flash[:warning] = @language.errors.full_messages.to_sentence end @@ -47,7 +47,7 @@ def switch def load_current_site @current_site = Alchemy::Site.current if @current_site.nil? - flash[:warning] = Alchemy.t('Please create a site first.') + flash[:warning] = Alchemy.t("Please create a site first.") redirect_to admin_sites_path end end diff --git a/app/controllers/alchemy/admin/layoutpages_controller.rb b/app/controllers/alchemy/admin/layoutpages_controller.rb index 23a2e42765..2279f19e42 100644 --- a/app/controllers/alchemy/admin/layoutpages_controller.rb +++ b/app/controllers/alchemy/admin/layoutpages_controller.rb @@ -10,7 +10,7 @@ class LayoutpagesController < Alchemy::Admin::BaseController helper Alchemy::Admin::PagesHelper def index - @layout_root = Page.find_or_create_layout_root_for(@current_language.id) + @layout_pages = Page.layoutpages.where(language: @current_language) @languages = Language.on_current_site end diff --git a/app/controllers/alchemy/admin/nodes_controller.rb b/app/controllers/alchemy/admin/nodes_controller.rb index 98ca0bb1f4..e0db2ef6c0 100644 --- a/app/controllers/alchemy/admin/nodes_controller.rb +++ b/app/controllers/alchemy/admin/nodes_controller.rb @@ -11,9 +11,8 @@ def index def new @node = Node.new( - site: Alchemy::Site.current, parent_id: params[:parent_id], - language: @current_language + language: @current_language, ) end @@ -21,7 +20,7 @@ def new def resource_params params.require(:node).permit( - :site_id, + :menu_type, :parent_id, :language_id, :page_id, @@ -29,7 +28,7 @@ def resource_params :url, :title, :nofollow, - :external + :external, ) end end diff --git a/app/controllers/alchemy/admin/pages_controller.rb b/app/controllers/alchemy/admin/pages_controller.rb index 36716c5f8d..d5ce82c902 100644 --- a/app/controllers/alchemy/admin/pages_controller.rb +++ b/app/controllers/alchemy/admin/pages_controller.rb @@ -5,7 +5,7 @@ module Admin class PagesController < Alchemy::Admin::BaseController include OnPageLayout::CallbacksRunner - helper 'alchemy/pages' + helper "alchemy/pages" before_action :load_page, except: [:index, :flush, :new, :order, :create, :copy_language_tree, :link, :sort] @@ -48,7 +48,7 @@ def show Page.current_preview = @page # Setting the locale to pages language, so the page content has it's correct translations. ::I18n.locale = @page.language.locale - render(layout: Alchemy::Config.get(:admin_page_preview_layout) || 'application') + render(layout: Alchemy::Config.get(:admin_page_preview_layout) || "application") end def info @@ -56,9 +56,9 @@ def info end def new - @page ||= Page.new(layoutpage: params[:layoutpage] == 'true', parent_id: params[:parent_id]) + @page ||= Page.new(layoutpage: params[:layoutpage] == "true", parent_id: params[:parent_id]) @page_layouts = PageLayout.layouts_for_select(@current_language.id, @page.layoutpage?) - @clipboard = get_clipboard('pages') + @clipboard = get_clipboard("pages") @clipboard_items = Page.all_from_clipboard_for_select(@clipboard, @current_language.id, @page.layoutpage?) end @@ -80,11 +80,12 @@ def create def edit # fetching page via before filter if page_is_locked? - flash[:warning] = Alchemy.t('This page is locked', name: @page.locker_name) + flash[:warning] = Alchemy.t("This page is locked", name: @page.locker_name) redirect_to admin_pages_path elsif page_needs_lock? @page.lock_to!(current_alchemy_user) end + @preview_url = Alchemy::Admin::PREVIEW_URL.url_for(@page) @layoutpage = @page.layoutpage? end @@ -102,7 +103,7 @@ def update @old_page_layout = @page.page_layout if @page.update(page_params) @notice = Alchemy.t("Page saved", name: @page.name) - @while_page_edit = request.referer.include?('edit') + @while_page_edit = request.referer.include?("edit") unless @while_page_edit @tree = serialized_page_tree @@ -118,17 +119,17 @@ def destroy flash[:notice] = Alchemy.t("Page deleted", name: @page.name) # Remove page from clipboard - clipboard = get_clipboard('pages') - clipboard.delete_if { |item| item['id'] == @page.id.to_s } + clipboard = get_clipboard("pages") + clipboard.delete_if { |item| item["id"] == @page.id.to_s } end respond_to do |format| format.js do @redirect_url = if @page.layoutpage? - alchemy.admin_layoutpages_path - else - alchemy.admin_pages_path - end + alchemy.admin_layoutpages_path + else + alchemy.admin_pages_path + end render :redirect end @@ -139,7 +140,6 @@ def link @attachments = Attachment.all.collect { |f| [f.name, download_attachment_path(id: f.id, name: f.urlname)] } - @url_prefix = prefix_locale? ? "#{@current_language.code}/" : "" end def fold @@ -169,7 +169,7 @@ def visit redirect_to show_page_url( urlname: @page.urlname, locale: prefix_locale? ? @page.language_code : nil, - host: @page.site.host == "*" ? request.host : @page.site.host + host: @page.site.host == "*" ? request.host : @page.site.host, ) end @@ -221,13 +221,11 @@ def flush private def copy_of_language_root - page_copy = Page.copy( + Page.copy( language_root_to_copy_from, language_id: params[:languages][:new_lang_id], - language_code: @current_language.code + language_code: @current_language.code, ) - page_copy.move_to_child_of Page.root - page_copy end def language_root_to_copy_from @@ -257,14 +255,14 @@ def language_root_to_copy_from def visit_nodes(nodes, my_left, parent, depth, tree, url, restricted) nodes.each do |item| my_right = my_left + 1 - my_restricted = item['restricted'] || restricted + my_restricted = item["restricted"] || restricted urls = process_url(url, item) - if item['children'] - my_right, tree = visit_nodes(item['children'], my_left + 1, item['id'], depth + 1, tree, urls[:children_path], my_restricted) + if item["children"] + my_right, tree = visit_nodes(item["children"], my_left + 1, item["id"], depth + 1, tree, urls[:children_path], my_restricted) end - tree[item['id']] = TreeNode.new(my_left, my_right, parent, depth, urls[:my_urlname], my_restricted) + tree[item["id"]] = TreeNode.new(my_left, my_right, parent, depth, urls[:my_urlname], my_restricted) my_left = my_right + 1 end @@ -293,24 +291,14 @@ def create_tree(items, rootpage) # This function will add a node's own slug into their ancestor's path # in order to create the full URL of a node # - # NOTE: Invisible pages are not part of the full path of their children - # # @param [String] # The node's ancestors path # @param [Hash] # A children node # def process_url(ancestors_path, item) - default_urlname = (ancestors_path.blank? ? "" : "#{ancestors_path}/") + item['slug'].to_s - - pair = {my_urlname: default_urlname, children_path: default_urlname} - - if item['visible'] == false - # children ignore an ancestor in their path if invisible - pair[:children_path] = ancestors_path - end - - pair + default_urlname = (ancestors_path.blank? ? "" : "#{ancestors_path}/") + item["slug"].to_s + { my_urlname: default_urlname, children_path: default_urlname } end def load_page @@ -318,10 +306,10 @@ def load_page end def pages_from_raw_request - request.raw_post.split('&').map do |i| - parts = i.split('=') + request.raw_post.split("&").map do |i| + parts = i.split("=") { - parts[0].gsub(/[^0-9]/, '') => parts[1] + parts[0].gsub(/[^0-9]/, "") => parts[1], } end end @@ -362,7 +350,7 @@ def page_needs_lock? def paste_from_clipboard if params[:paste_from_clipboard] source = Page.find(params[:paste_from_clipboard]) - parent = Page.find_by(id: params[:page][:parent_id]) || Page.root + parent = Page.find_by(id: params[:page][:parent_id]) Page.copy_and_paste(source, parent, params[:page][:name]) end end @@ -374,7 +362,7 @@ def set_root_page def serialized_page_tree PageTreeSerializer.new(@page, ability: current_ability, user: current_alchemy_user, - full: params[:full] == 'true') + full: params[:full] == "true") end end end diff --git a/app/controllers/alchemy/admin/pictures_controller.rb b/app/controllers/alchemy/admin/pictures_controller.rb index 75354555ed..7869a3ee42 100644 --- a/app/controllers/alchemy/admin/pictures_controller.rb +++ b/app/controllers/alchemy/admin/pictures_controller.rb @@ -6,7 +6,7 @@ class PicturesController < Alchemy::Admin::ResourcesController include UploaderResponses include ArchiveOverlay - helper 'alchemy/admin/tags' + helper "alchemy/admin/tags" before_action :load_resource, only: [:show, :edit, :update, :destroy, :info] @@ -14,12 +14,12 @@ class PicturesController < Alchemy::Admin::ResourcesController authorize_resource class: Alchemy::Picture def index - @size = params[:size].present? ? params[:size] : 'medium' + @size = params[:size].present? ? params[:size] : "medium" @query = Picture.ransack(search_filter_params[:q]) @pictures = Picture.search_by( search_filter_params, @query, - items_per_page + items_per_page, ) if in_overlay? @@ -31,7 +31,7 @@ def show @previous = @picture.previous(params) @next = @picture.next(params) @assignments = @picture.essence_pictures.joins(content: {element: :page}) - render action: 'show' + render action: "show" end def create @@ -46,19 +46,19 @@ def create def edit_multiple @pictures = Picture.where(id: params[:picture_ids]) - @tags = @pictures.collect(&:tag_list).flatten.uniq.join(', ') + @tags = @pictures.collect(&:tag_list).flatten.uniq.join(", ") end def update if @picture.update(picture_params) @message = { body: Alchemy.t(:picture_updated_successfully, name: @picture.name), - type: 'notice' + type: "notice", } else @message = { body: Alchemy.t(:picture_update_failed), - type: 'error' + type: "error", } end render :update @@ -89,7 +89,7 @@ def delete_multiple if not_deletable.any? flash[:warn] = Alchemy.t( "These pictures could not be deleted, because they were in use", - names: not_deletable.to_sentence + names: not_deletable.to_sentence, ) else flash[:notice] = Alchemy.t("Pictures deleted successfully", names: names.to_sentence) @@ -116,8 +116,8 @@ def destroy def items_per_page if in_overlay? case params[:size] - when 'small' then 25 - when 'large' then 4 + when "small" then 25 + when "large" then 4 else 9 end @@ -137,8 +137,8 @@ def items_per_page_options def pictures_per_page_for_size(size) case size - when 'small' then 60 - when 'large' then 12 + when "small" then 60 + when "large" then 12 else 20 end @@ -154,8 +154,8 @@ def search_filter_params :size, :element_id, :swap, - :content_id - ] + :content_id, + ], ) end diff --git a/app/controllers/alchemy/admin/resources_controller.rb b/app/controllers/alchemy/admin/resources_controller.rb index e6079942c8..4bbf962c89 100644 --- a/app/controllers/alchemy/admin/resources_controller.rb +++ b/app/controllers/alchemy/admin/resources_controller.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'csv' -require 'alchemy/resource' -require 'alchemy/resources_helper' +require "csv" +require "alchemy/resource" +require "alchemy/resources_helper" module Alchemy module Admin @@ -53,7 +53,7 @@ def new end def show - render action: 'edit' + render action: "edit" end def edit; end @@ -64,7 +64,7 @@ def create render_errors_or_redirect( resource_instance_variable, resources_path(resource_instance_variable.class, search_filter_params), - flash_notice_for_resource_action + flash_notice_for_resource_action, ) end @@ -73,14 +73,17 @@ def update render_errors_or_redirect( resource_instance_variable, resources_path(resource_instance_variable.class, search_filter_params), - flash_notice_for_resource_action + flash_notice_for_resource_action, ) end def destroy resource_instance_variable.destroy + if resource_instance_variable.errors.any? + flash[:error] = resource_instance_variable.errors.full_messages.join(", ") + end flash_notice_for_resource_action - do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: 'index')) + do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index")) end def resource_handler @@ -106,11 +109,11 @@ def flash_notice_for_resource_action(action = params[:action]) end def is_alchemy_module? - !alchemy_module.nil? && !alchemy_module['engine_name'].nil? + !alchemy_module.nil? && !alchemy_module["engine_name"].nil? end def alchemy_module - @alchemy_module ||= module_definition_for(controller: params[:controller], action: 'index') + @alchemy_module ||= module_definition_for(controller: params[:controller], action: "index") end def load_resource @@ -147,12 +150,12 @@ def common_search_filter_includes [ {q: [ resource_handler.search_field_name, - :s + :s, ]}, :tagged_with, :filter, :page, - :per_page + :per_page, ].freeze end @@ -166,8 +169,8 @@ def items_per_page_options end def default_sort_order - name = resource_handler.attributes.detect { |attr| attr[:name] == 'name' } - name ? 'name asc' : "#{resource_handler.attributes.first[:name]} asc" + name = resource_handler.attributes.detect { |attr| attr[:name] == "name" } + name ? "name asc" : "#{resource_handler.attributes.first[:name]} asc" end end end diff --git a/app/controllers/alchemy/admin/sites_controller.rb b/app/controllers/alchemy/admin/sites_controller.rb index 3fd772b664..be79b0dd29 100644 --- a/app/controllers/alchemy/admin/sites_controller.rb +++ b/app/controllers/alchemy/admin/sites_controller.rb @@ -6,7 +6,7 @@ class SitesController < ResourcesController def create @site = Alchemy::Site.new(resource_params) if @site.save - flash[:notice] = Alchemy.t('Please create a default language for this site.') + flash[:notice] = Alchemy.t("Please create a default language for this site.") redirect_to alchemy.admin_languages_path(site_id: @site) else render :new @@ -15,7 +15,7 @@ def create def destroy if @site.destroy - flash[:notice] = Alchemy.t('Site successfully removed') + flash[:notice] = Alchemy.t("Site successfully removed") else flash[:warning] = @site.errors.full_messages.to_sentence end diff --git a/app/controllers/alchemy/admin/tags_controller.rb b/app/controllers/alchemy/admin/tags_controller.rb index d418b7aca9..857e4f4778 100644 --- a/app/controllers/alchemy/admin/tags_controller.rb +++ b/app/controllers/alchemy/admin/tags_controller.rb @@ -21,7 +21,7 @@ def new def create @tag = Gutentag::Tag.create(tag_params) - render_errors_or_redirect @tag, admin_tags_path, Alchemy.t('New Tag Created') + render_errors_or_redirect @tag, admin_tags_path, Alchemy.t("New Tag Created") end def edit @@ -32,7 +32,7 @@ def update if tag_params[:merge_to] @new_tag = Gutentag::Tag.find(tag_params[:merge_to]) Tag.replace(@tag, @new_tag) - operation_text = Alchemy.t('Replaced Tag') % {old_tag: @tag.name, new_tag: @new_tag.name} + operation_text = Alchemy.t("Replaced Tag") % {old_tag: @tag.name, new_tag: @new_tag.name} @tag.destroy else @tag.update(tag_params) @@ -67,7 +67,7 @@ def tag_params def tags_from_term(term) return [] if term.blank? - Gutentag::Tag.where(['LOWER(name) LIKE ?', "#{term.downcase}%"]) + Gutentag::Tag.where(["LOWER(name) LIKE ?", "#{term.downcase}%"]) end def json_for_autocomplete(items, attribute) diff --git a/app/controllers/alchemy/admin/trash_controller.rb b/app/controllers/alchemy/admin/trash_controller.rb index 8956f70b79..98c3dce514 100644 --- a/app/controllers/alchemy/admin/trash_controller.rb +++ b/app/controllers/alchemy/admin/trash_controller.rb @@ -25,16 +25,16 @@ def element_includes [ { contents: { - essence: :ingredient_association + essence: :ingredient_association, }, all_nested_elements: [ { contents: { - essence: :ingredient_association - } - } - ] - } + essence: :ingredient_association, + }, + }, + ], + }, ] end end diff --git a/app/controllers/alchemy/api/base_controller.rb b/app/controllers/alchemy/api/base_controller.rb index b3e21c42f5..c79f5badcd 100644 --- a/app/controllers/alchemy/api/base_controller.rb +++ b/app/controllers/alchemy/api/base_controller.rb @@ -11,11 +11,11 @@ class Api::BaseController < Alchemy::BaseController private def render_not_authorized - render json: {error: 'Not authorized'}, status: 403 + render json: {error: "Not authorized"}, status: 403 end def render_not_found - render json: {error: 'Record not found'}, status: 404 + render json: {error: "Record not found"}, status: 404 end end end diff --git a/app/controllers/alchemy/api/contents_controller.rb b/app/controllers/alchemy/api/contents_controller.rb index 13162e08ca..c864de5fe2 100644 --- a/app/controllers/alchemy/api/contents_controller.rb +++ b/app/controllers/alchemy/api/contents_controller.rb @@ -18,7 +18,7 @@ def index end @contents = @contents.includes(*content_includes) - render json: @contents, adapter: :json, root: 'contents' + render json: @contents, adapter: :json, root: "contents" end # Returns a json object for content @@ -36,7 +36,7 @@ def show elsif params[:element_id] && params[:name] @content = Content.where( element_id: params[:element_id], - name: params[:name] + name: params[:name], ).includes(*content_includes).first || raise(ActiveRecord::RecordNotFound) end authorize! :show, @content @@ -48,8 +48,8 @@ def show def content_includes [ { - essence: :ingredient_association - } + essence: :ingredient_association, + }, ] end end diff --git a/app/controllers/alchemy/api/elements_controller.rb b/app/controllers/alchemy/api/elements_controller.rb index 45367a8104..1ad9583065 100644 --- a/app/controllers/alchemy/api/elements_controller.rb +++ b/app/controllers/alchemy/api/elements_controller.rb @@ -22,7 +22,7 @@ def index end @elements = @elements.includes(*element_includes) - render json: @elements, adapter: :json, root: 'elements' + render json: @elements, adapter: :json, root: "elements" end # Returns a json object for element @@ -41,18 +41,18 @@ def element_includes nested_elements: [ { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags - ] + :tags, + ], }, { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags + :tags, ] end end diff --git a/app/controllers/alchemy/api/nodes_controller.rb b/app/controllers/alchemy/api/nodes_controller.rb index 3e61059fb5..766f85ce7a 100644 --- a/app/controllers/alchemy/api/nodes_controller.rb +++ b/app/controllers/alchemy/api/nodes_controller.rb @@ -2,9 +2,21 @@ module Alchemy class Api::NodesController < Api::BaseController - before_action :load_node + before_action :load_node, except: :index before_action :authorize_access, only: [:move, :toggle_folded] + def index + @nodes = Node.all + @nodes = @nodes.includes(:parent) + @nodes = @nodes.ransack(params[:filter]).result + + if params[:page] + @nodes = @nodes.page(params[:page]).per(params[:per_page]) + end + + render json: @nodes, adapter: :json, root: "data", meta: meta_data, include: params[:include] + end + def move target_parent_node = Node.find(params[:target_parent_id]) @node.move_to_child_with_index(target_parent_node, params[:new_position]) @@ -25,5 +37,29 @@ def load_node def authorize_access authorize! :update, @node end + + def meta_data + { + total_count: total_count_value, + per_page: per_page_value, + page: page_value, + } + end + + def total_count_value + params[:page] ? @nodes.total_count : @nodes.size + end + + def per_page_value + if params[:page] + (params[:per_page] || Kaminari.config.default_per_page).to_i + else + @nodes.size + end + end + + def page_value + params[:page] ? params[:page].to_i : 1 + end end end diff --git a/app/controllers/alchemy/api/pages_controller.rb b/app/controllers/alchemy/api/pages_controller.rb index 0946d6a39e..1b24ddfebe 100644 --- a/app/controllers/alchemy/api/pages_controller.rb +++ b/app/controllers/alchemy/api/pages_controller.rb @@ -20,7 +20,7 @@ def index @pages = @pages.page(params[:page]).per(params[:per_page]) end - render json: @pages, adapter: :json, root: 'pages', meta: meta_data + render json: @pages, adapter: :json, root: "pages", meta: meta_data end # Returns all pages as nested json object for tree views @@ -64,7 +64,7 @@ def load_page_by_urlname Language.current.pages.where( urlname: params[:urlname], - language_code: params[:locale] || Language.current.code + language_code: params[:locale] || Language.current.code, ).includes(page_includes).first end @@ -72,7 +72,7 @@ def meta_data { total_count: total_count_value, per_page: per_page_value, - page: page_value + page: page_value, } end @@ -96,25 +96,26 @@ def page_includes [ :tags, { + language: :site, elements: [ { nested_elements: [ { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags - ] + :tags, + ], }, { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags - ] - } + :tags, + ], + }, ] end end diff --git a/app/controllers/alchemy/attachments_controller.rb b/app/controllers/alchemy/attachments_controller.rb index 3327d67849..8841ad8a35 100644 --- a/app/controllers/alchemy/attachments_controller.rb +++ b/app/controllers/alchemy/attachments_controller.rb @@ -7,24 +7,24 @@ class AttachmentsController < BaseController # sends file inline. i.e. for viewing pdfs/movies in browser def show - response.headers['Content-Length'] = @attachment.file.size.to_s + response.headers["Content-Length"] = @attachment.file.size.to_s send_file( @attachment.file.path, { filename: @attachment.file_name, type: @attachment.file_mime_type, - disposition: 'inline' - } + disposition: "inline", + }, ) end # sends file as attachment. aka download def download - response.headers['Content-Length'] = @attachment.file.size.to_s + response.headers["Content-Length"] = @attachment.file.size.to_s send_file( @attachment.file.path, { filename: @attachment.file_name, - type: @attachment.file_mime_type + type: @attachment.file_mime_type, } ) end diff --git a/app/controllers/alchemy/base_controller.rb b/app/controllers/alchemy/base_controller.rb index dad72d6c72..ea201d954d 100644 --- a/app/controllers/alchemy/base_controller.rb +++ b/app/controllers/alchemy/base_controller.rb @@ -8,14 +8,13 @@ class BaseController < ApplicationController include Alchemy::AbilityHelper include Alchemy::ControllerActions include Alchemy::Modules - include Alchemy::SSLProtection protect_from_forgery before_action :mailer_set_url_options before_action :set_locale - helper 'alchemy/admin/form' + helper "alchemy/admin/form" rescue_from CanCan::AccessDenied do |exception| permission_denied(exception) @@ -27,6 +26,7 @@ class BaseController < ApplicationController # def set_locale return unless Language.current + ::I18n.locale = Language.current&.locale end @@ -61,11 +61,11 @@ def permission_denied(exception = nil) end def handle_redirect_for_user - flash[:warning] = Alchemy.t('You are not authorized') + flash[:warning] = Alchemy.t("You are not authorized") if can?(:index, :alchemy_admin_dashboard) redirect_or_render_notice else - redirect_to('/') + redirect_to("/") end end @@ -76,8 +76,8 @@ def redirect_or_render_notice render plain: flash.discard(:warning), status: 403 end format.html do - render partial: 'alchemy/admin/partials/flash', - locals: {message: flash[:warning], flash_type: 'warning'} + render partial: "alchemy/admin/partials/flash", + locals: { message: flash[:warning], flash_type: "warning" } end end else @@ -86,7 +86,7 @@ def redirect_or_render_notice end def handle_redirect_for_guest - flash[:info] = Alchemy.t('Please log in') + flash[:info] = Alchemy.t("Please log in") if request.xhr? render :permission_denied else @@ -99,7 +99,7 @@ def handle_redirect_for_guest def exception_logger(error) Rails.logger.error("\n#{error.class} #{error.message} in #{error.backtrace.first}") Rails.logger.error(error.backtrace[1..50].each { |line| - line.gsub(/#{Rails.root}/, '') + line.gsub(/#{Rails.root}/, "") }.join("\n")) end end diff --git a/app/controllers/alchemy/messages_controller.rb b/app/controllers/alchemy/messages_controller.rb index 1503f71b6e..0148f29d1c 100644 --- a/app/controllers/alchemy/messages_controller.rb +++ b/app/controllers/alchemy/messages_controller.rb @@ -39,18 +39,18 @@ module Alchemy class MessagesController < Alchemy::BaseController before_action :get_page, except: :create - helper 'alchemy/pages' + helper "alchemy/pages" def index #:nodoc: redirect_to show_page_path( urlname: @page.urlname, - locale: prefix_locale? ? @page.language_code : nil + locale: prefix_locale? ? @page.language_code : nil, ) end def new #:nodoc: @message = Message.new - render template: 'alchemy/pages/show' + render template: "alchemy/pages/show" end def create #:nodoc: @@ -67,7 +67,7 @@ def create #:nodoc: MessagesMailer.contact_form_mail(@message, mail_to, mail_from, subject).deliver redirect_to_success_page else - render template: 'alchemy/pages/show' + render template: "alchemy/pages/show" end end @@ -78,29 +78,29 @@ def mailer_config end def mail_to - @element.ingredient(:mail_to) || mailer_config['mail_to'] + @element.ingredient(:mail_to) || mailer_config["mail_to"] end def mail_from - @element.ingredient(:mail_from) || mailer_config['mail_from'] + @element.ingredient(:mail_from) || mailer_config["mail_from"] end def subject - @element.ingredient(:subject) || mailer_config['subject'] + @element.ingredient(:subject) || mailer_config["subject"] end def redirect_to_success_page - flash[:notice] = Alchemy.t(:success, scope: 'contactform.messages') + flash[:notice] = Alchemy.t(:success, scope: "contactform.messages") if success_page urlname = success_page_urlname - elsif mailer_config['forward_to_page'] && mailer_config['mail_success_page'] - urlname = Page.find_by(urlname: mailer_config['mail_success_page']).urlname + elsif mailer_config["forward_to_page"] && mailer_config["mail_success_page"] + urlname = Page.find_by(urlname: mailer_config["mail_success_page"]).urlname else urlname = Language.current_root_page.urlname end redirect_to show_page_path( urlname: urlname, - locale: prefix_locale? ? Language.current.code : nil + locale: prefix_locale? ? Language.current.code : nil, ) end @@ -118,16 +118,16 @@ def success_page_urlname end def get_page - @page = Language.current.pages.find_by(page_layout: mailer_config['page_layout_name']) + @page = Language.current.pages.find_by(page_layout: mailer_config["page_layout_name"]) if @page.blank? - raise "Page for page_layout #{mailer_config['page_layout_name']} not found" + raise "Page for page_layout #{mailer_config["page_layout_name"]} not found" end @root_page = @page.get_language_root end def message_params - params.require(:message).permit(*mailer_config['fields']) + params.require(:message).permit(*mailer_config["fields"]) end end end diff --git a/app/controllers/alchemy/pages_controller.rb b/app/controllers/alchemy/pages_controller.rb index 87c153d2e7..674ff91ed9 100644 --- a/app/controllers/alchemy/pages_controller.rb +++ b/app/controllers/alchemy/pages_controller.rb @@ -3,10 +3,10 @@ module Alchemy class PagesController < Alchemy::BaseController SHOW_PAGE_PARAMS_KEYS = [ - 'action', - 'controller', - 'urlname', - 'locale' + "action", + "controller", + "urlname", + "locale", ] include OnPageLayout::CallbacksRunner @@ -78,7 +78,7 @@ def show def sitemap @pages = Page.sitemap respond_to do |format| - format.xml { render layout: 'alchemy/sitemap' } + format.xml { render layout: "alchemy/sitemap" } end end @@ -95,7 +95,7 @@ def sitemap # def load_index_page @page ||= Language.current_root_page - render template: 'alchemy/welcome', layout: false if signup_required? + render template: "alchemy/welcome", layout: false if signup_required? end # == Loads page by urlname @@ -112,7 +112,7 @@ def load_page @page ||= Language.current.pages.contentpages.find_by( urlname: params[:urlname], - language_code: params[:locale] || Language.current.code + language_code: params[:locale] || Language.current.code, ) end @@ -150,7 +150,7 @@ def render_page if @page.contains_feed? render action: :show, layout: false, handlers: [:builder] else - render xml: {error: 'Not found'}, status: 404 + render xml: { error: "Not found" }, status: 404 end end end @@ -193,9 +193,9 @@ def page_etag # def render_fresh_page? must_not_cache? || stale?(etag: page_etag, - last_modified: @page.published_at, - public: !@page.restricted, - template: 'pages/show') + last_modified: @page.published_at, + public: !@page.restricted, + template: "pages/show") end # don't cache pages if we have flash message to display or the page has caching disabled diff --git a/app/controllers/concerns/alchemy/admin/archive_overlay.rb b/app/controllers/concerns/alchemy/admin/archive_overlay.rb index 782228a730..6346d646d6 100644 --- a/app/controllers/concerns/alchemy/admin/archive_overlay.rb +++ b/app/controllers/concerns/alchemy/admin/archive_overlay.rb @@ -12,8 +12,8 @@ def archive_overlay @content = Content.find_by(id: params[:content_id]) respond_to do |format| - format.html { render partial: 'archive_overlay' } - format.js { render action: 'archive_overlay' } + format.html { render partial: "archive_overlay" } + format.js { render action: "archive_overlay" } end end end diff --git a/app/controllers/concerns/alchemy/admin/current_language.rb b/app/controllers/concerns/alchemy/admin/current_language.rb index 6a6a864e1a..d2bf50a6e7 100644 --- a/app/controllers/concerns/alchemy/admin/current_language.rb +++ b/app/controllers/concerns/alchemy/admin/current_language.rb @@ -14,7 +14,7 @@ module CurrentLanguage def load_current_language @current_language = Alchemy::Language.current if @current_language.nil? - flash[:warning] = Alchemy.t('Please create a language first.') + flash[:warning] = Alchemy.t("Please create a language first.") redirect_to admin_languages_path end end diff --git a/app/controllers/concerns/alchemy/admin/uploader_responses.rb b/app/controllers/concerns/alchemy/admin/uploader_responses.rb index 4662ea384a..6a2f21f2e6 100644 --- a/app/controllers/concerns/alchemy/admin/uploader_responses.rb +++ b/app/controllers/concerns/alchemy/admin/uploader_responses.rb @@ -8,12 +8,12 @@ module UploaderResponses def successful_uploader_response(file:, status: :created) message = Alchemy.t(:upload_success, scope: [:uploader, file.class.model_name.i18n_key], - name: file.name + name: file.name, ) { json: uploader_response(file: file, message: message), - status: status + status: status, } end @@ -21,12 +21,12 @@ def failed_uploader_response(file:) message = Alchemy.t(:upload_failure, scope: [:uploader, file.class.model_name.i18n_key], error: file.errors[:file].join, - name: file.name + name: file.name, ) { json: uploader_response(file: file, message: message), - status: :unprocessable_entity + status: :unprocessable_entity, } end @@ -35,7 +35,7 @@ def failed_uploader_response(file:) def uploader_response(file:, message:) { files: [file.to_jq_upload], - growl_message: message + growl_message: message, } end end diff --git a/app/controllers/concerns/alchemy/legacy_page_redirects.rb b/app/controllers/concerns/alchemy/legacy_page_redirects.rb index e00780d26d..aa18739edf 100644 --- a/app/controllers/concerns/alchemy/legacy_page_redirects.rb +++ b/app/controllers/concerns/alchemy/legacy_page_redirects.rb @@ -36,18 +36,18 @@ def legacy_page_redirect_url alchemy.show_page_path( locale: prefix_locale? ? page.language_code : nil, - urlname: page.urlname + urlname: page.urlname, ) end def legacy_urls # /slug/tree => slug/tree - urlname = (request.fullpath[1..-1] if request.fullpath[0] == '/') || request.fullpath + urlname = (request.fullpath[1..-1] if request.fullpath[0] == "/") || request.fullpath LegacyPageUrl.joins(:page).where( urlname: urlname, Page.table_name => { - language_id: Language.current.id - } + language_id: Language.current.id, + }, ) end diff --git a/app/controllers/concerns/alchemy/page_redirects.rb b/app/controllers/concerns/alchemy/page_redirects.rb index 751f42c3e9..b39d766670 100644 --- a/app/controllers/concerns/alchemy/page_redirects.rb +++ b/app/controllers/concerns/alchemy/page_redirects.rb @@ -51,7 +51,7 @@ def public_child_redirect_url def page_redirect_url(options = {}) options = { locale: prefix_locale? ? @page.language_code : nil, - urlname: @page.urlname + urlname: @page.urlname, }.merge(options) alchemy.show_page_path additional_params.merge(options) diff --git a/app/controllers/concerns/alchemy/site_redirects.rb b/app/controllers/concerns/alchemy/site_redirects.rb index e0696dd7fb..059bd7a5d3 100644 --- a/app/controllers/concerns/alchemy/site_redirects.rb +++ b/app/controllers/concerns/alchemy/site_redirects.rb @@ -17,7 +17,7 @@ def enforce_primary_host_for_site def needs_redirect_to_primary_host? current_alchemy_site&.redirect_to_primary_host? && - current_alchemy_site.host != '*' && + current_alchemy_site.host != "*" && current_alchemy_site.host != request.host end end diff --git a/app/decorators/alchemy/content_editor.rb b/app/decorators/alchemy/content_editor.rb index 2e5055aee5..4d7bcf6317 100644 --- a/app/decorators/alchemy/content_editor.rb +++ b/app/decorators/alchemy/content_editor.rb @@ -10,15 +10,15 @@ def to_partial_path def css_classes [ - 'content_editor', - essence_partial_name + "content_editor", + essence_partial_name, ].compact end def data_attributes { content_id: id, - content_name: name + content_name: name, } end @@ -36,11 +36,11 @@ def data_attributes # # <%= text_field_tag content_editor.form_field_name(:link), content_editor.ingredient %> # - def form_field_name(essence_column = 'ingredient') + def form_field_name(essence_column = "ingredient") "contents[#{id}][#{essence_column}]" end - def form_field_id(essence_column = 'ingredient') + def form_field_id(essence_column = "ingredient") "contents_#{id}_#{essence_column}" end diff --git a/app/decorators/alchemy/element_editor.rb b/app/decorators/alchemy/element_editor.rb index ba2b1ce838..29c9478079 100644 --- a/app/decorators/alchemy/element_editor.rb +++ b/app/decorators/alchemy/element_editor.rb @@ -11,14 +11,14 @@ def to_partial_path # CSS classes for the element editor partial. def css_classes [ - 'element-editor', - content_definitions.present? ? 'with-contents' : 'without-contents', - nestable_elements.any? ? 'nestable' : 'not-nestable', - taggable? ? 'taggable' : 'not-taggable', - folded ? 'folded' : 'expanded', - compact? ? 'compact' : nil, - fixed? ? 'is-fixed' : 'not-fixed' - ].join(' ') + "element-editor", + content_definitions.present? ? "with-contents" : "without-contents", + nestable_elements.any? ? "nestable" : "not-nestable", + taggable? ? "taggable" : "not-taggable", + folded ? "folded" : "expanded", + compact? ? "compact" : nil, + fixed? ? "is-fixed" : "not-fixed", + ].join(" ") end # Tells us, if we should show the element footer and form inputs. diff --git a/app/helpers/alchemy/admin/attachments_helper.rb b/app/helpers/alchemy/admin/attachments_helper.rb index 22bb255b39..8bc876a275 100644 --- a/app/helpers/alchemy/admin/attachments_helper.rb +++ b/app/helpers/alchemy/admin/attachments_helper.rb @@ -6,17 +6,17 @@ module AttachmentsHelper include Alchemy::Admin::BaseHelper def mime_to_human(mime) - Alchemy.t(mime, scope: 'mime_types', default: Alchemy.t(:document)) + Alchemy.t(mime, scope: "mime_types", default: Alchemy.t(:document)) end def attachment_preview_size(attachment) case attachment.icon_css_class - when 'image' then '600x475' - when 'audio' then '600x190' - when 'video' then '600x485' - when 'pdf' then '600x500' + when "image" then "600x475" + when "audio" then "600x190" + when "video" then "600x485" + when "pdf" then "600x500" else - '600x145' + "600x145" end end end diff --git a/app/helpers/alchemy/admin/base_helper.rb b/app/helpers/alchemy/admin/base_helper.rb index ca7afd44c0..617390178f 100644 --- a/app/helpers/alchemy/admin/base_helper.rb +++ b/app/helpers/alchemy/admin/base_helper.rb @@ -23,7 +23,7 @@ module BaseHelper def current_alchemy_user_name name = current_alchemy_user.try(:alchemy_display_name) if name.present? - content_tag :span, "#{Alchemy.t('Logged in as')} #{name}", class: 'current-user-name' + content_tag :span, "#{Alchemy.t("Logged in as")} #{name}", class: "current-user-name" end end @@ -55,7 +55,7 @@ def link_to_dialog(content, url, options = {}, html_options = {}) default_options = {modal: true} options = default_options.merge(options) link_to content, url, - html_options.merge('data-alchemy-dialog' => options.to_json) + html_options.merge("data-alchemy-dialog" => options.to_json) end # Used for translations selector in Alchemy cockpit user settings. @@ -99,13 +99,13 @@ def sites_for_select # def js_filter_field(items, options = {}) options = { - class: 'js_filter_field', - data: {'alchemy-list-filter' => items} + class: "js_filter_field", + data: {"alchemy-list-filter" => items}, }.merge(options) - content_tag(:div, class: 'js_filter_field_box') do + content_tag(:div, class: "js_filter_field_box") do concat text_field_tag(nil, nil, options) concat render_icon(:search) - concat link_to(render_icon(:times, size: 'xs'), '', class: 'js_filter_field_clear', title: Alchemy.t(:click_to_show_all)) + concat link_to(render_icon(:times, size: "xs"), "", class: "js_filter_field_clear", title: Alchemy.t(:click_to_show_all)) end end @@ -136,12 +136,12 @@ def js_filter_field(items, options = {}) def link_to_confirm_dialog(link_string = "", message = "", url = "", html_options = {}) link_to(link_string, url, html_options.merge( - 'data-alchemy-confirm-delete' => { + "data-alchemy-confirm-delete" => { title: Alchemy.t(:please_confirm), message: message, ok_label: Alchemy.t("Yes"), - cancel_label: Alchemy.t("No") - }.to_json + cancel_label: Alchemy.t("No"), + }.to_json, ) ) end @@ -170,10 +170,10 @@ def button_with_confirm(value = "", url = "", options = {}, html_options = {}) message: Alchemy.t(:confirm_to_proceed), ok_label: Alchemy.t("Yes"), title: Alchemy.t(:please_confirm), - cancel_label: Alchemy.t("No") + cancel_label: Alchemy.t("No"), }.merge(options) - form_tag url, {method: html_options.delete(:method), class: 'button-with-confirm'} do - button_tag value, html_options.merge('data-alchemy-confirm' => options.to_json) + form_tag url, {method: html_options.delete(:method), class: "button-with-confirm"} do + button_tag value, html_options.merge("data-alchemy-confirm" => options.to_json) end end @@ -188,18 +188,18 @@ def button_with_confirm(value = "", url = "", options = {}, html_options = {}) # def delete_button(url, options = {}, html_options = {}) options = { - title: Alchemy.t('Delete'), - message: Alchemy.t('Are you sure?'), - icon: :minus + title: Alchemy.t("Delete"), + message: Alchemy.t("Are you sure?"), + icon: :minus, }.merge(options) button_with_confirm( render_icon(options[:icon]), url, { - message: options[:message] + message: options[:message], }, { - method: 'delete', + method: "delete", title: options[:title], - class: "icon_button #{html_options.delete(:class)}".strip + class: "icon_button #{html_options.delete(:class)}".strip, }.merge(html_options) ) end @@ -259,11 +259,11 @@ def toolbar_button(options = {}) active: false, link_options: {}, dialog_options: {}, - loading_indicator: false + loading_indicator: false, }.merge(options.symbolize_keys) button = render( - 'alchemy/admin/partials/toolbar_button', - options: options + "alchemy/admin/partials/toolbar_button", + options: options, ) if options[:skip_permission_check] || can?(*permission_from_options(options)) button @@ -302,13 +302,13 @@ def toolbar_button(options = {}) def toolbar(options = {}) defaults = { buttons: [], - search: true + search: true, } options = defaults.merge(options) content_for(:toolbar) do content = <<-CONTENT.strip_heredoc #{options[:buttons].map { |button_options| toolbar_button(button_options) }.join} - #{render('alchemy/admin/partials/search_form', url: options[:search_url]) if options[:search]} + #{render("alchemy/admin/partials/search_form", url: options[:search_url]) if options[:search]} CONTENT content.html_safe end @@ -360,7 +360,7 @@ def new_asset_path_with_session_information(asset_type) # The value the input displays. If you pass a String its parsed with +Time.parse+ # def alchemy_datepicker(object, method, html_options = {}) - type = html_options.delete(:type) || 'date' + type = html_options.delete(:type) || "date" date = html_options.delete(:value) || object.send(method.to_sym).presence date = Time.zone.parse(date) if date.is_a?(String) value = date ? date.iso8601 : nil @@ -374,9 +374,9 @@ def alchemy_datepicker(object, method, html_options = {}) def render_hint_for(element) return unless element.has_hint? - content_tag :span, class: 'hint-with-icon' do - render_icon('question-circle') + - content_tag(:span, element.hint.html_safe, class: 'hint-bubble') + content_tag :span, class: "hint-with-icon" do + render_icon("question-circle") + + content_tag(:span, element.hint.html_safe, class: "hint-bubble") end end @@ -386,7 +386,7 @@ def alchemy_body_class controller_name, action_name, content_for(:main_menu_style), - content_for(:alchemy_body_class) + content_for(:alchemy_body_class), ].compact end @@ -405,7 +405,7 @@ def clipboard_select_tag_options(items) # Returns the regular expression used for external url validation in link dialog. def link_url_regexp - Alchemy::Config.get(:format_matchers)['link_url'] || /^(mailto:|\/|[a-z]+:\/\/)/ + Alchemy::Config.get(:format_matchers)["link_url"] || /^(mailto:|\/|[a-z]+:\/\/)/ end # Renders a hint with tooltip @@ -418,9 +418,9 @@ def link_url_regexp # @param icon: 'exclamation-triangle' [String] - Icon name # # @return [String] - def hint_with_tooltip(text, icon: 'exclamation-triangle') - content_tag :span, class: 'hint-with-icon' do - render_icon(icon) + content_tag(:span, text, class: 'hint-bubble') + def hint_with_tooltip(text, icon: "exclamation-triangle") + content_tag :span, class: "hint-with-icon" do + render_icon(icon) + content_tag(:span, text, class: "hint-bubble") end end @@ -428,7 +428,7 @@ def hint_with_tooltip(text, icon: 'exclamation-triangle') # that explains the user that the page layout is missing def page_layout_missing_warning hint_with_tooltip( - Alchemy.t(:page_definition_missing) + Alchemy.t(:page_definition_missing), ) end @@ -443,10 +443,10 @@ def permission_from_options(options) end def permission_array_from_url(options) - action_controller = options[:url].gsub(/\A\//, '').split('/') + action_controller = options[:url].gsub(/\A\//, "").split("/") [ action_controller.last.to_sym, - action_controller[0..action_controller.length - 2].join('_').to_sym + action_controller[0..action_controller.length - 2].join("_").to_sym, ] end end diff --git a/app/helpers/alchemy/admin/contents_helper.rb b/app/helpers/alchemy/admin/contents_helper.rb index 609d6980a7..18b4fcf214 100644 --- a/app/helpers/alchemy/admin/contents_helper.rb +++ b/app/helpers/alchemy/admin/contents_helper.rb @@ -13,7 +13,7 @@ module ContentsHelper # def render_content_name(content) if content.blank? - warning('Content is nil') + warning("Content is nil") return end @@ -23,7 +23,7 @@ def render_content_name(content) warning("Content #{content.name} is missing its definition") icon = hint_with_tooltip( - Alchemy.t(:content_definition_missing) + Alchemy.t(:content_definition_missing), ) content_name = "#{icon} #{content_name}".html_safe @@ -39,7 +39,7 @@ def render_content_name(content) # Renders the label and a remove link for a content. def content_label(content) content_tag :label, for: content.form_field_id do - [render_hint_for(content), render_content_name(content)].compact.join(' ').html_safe + [render_hint_for(content), render_content_name(content)].compact.join(" ").html_safe end end end diff --git a/app/helpers/alchemy/admin/elements_helper.rb b/app/helpers/alchemy/admin/elements_helper.rb index 07b01eb8ca..56d0aeec66 100644 --- a/app/helpers/alchemy/admin/elements_helper.rb +++ b/app/helpers/alchemy/admin/elements_helper.rb @@ -16,8 +16,8 @@ def elements_for_select(elements) elements.collect do |e| [ - Element.display_name_for(e['name']), - e['name'] + Element.display_name_for(e["name"]), + e["name"], ] end end diff --git a/app/helpers/alchemy/admin/essences_helper.rb b/app/helpers/alchemy/admin/essences_helper.rb index 9f67587dca..00fefb240a 100644 --- a/app/helpers/alchemy/admin/essences_helper.rb +++ b/app/helpers/alchemy/admin/essences_helper.rb @@ -13,17 +13,17 @@ def essence_picture_thumbnail(content) image_tag( essence.thumbnail_url, alt: picture.name, - class: 'img_paddingtop', - title: Alchemy.t(:image_name) + ": #{picture.name}" + class: "img_paddingtop", + title: Alchemy.t(:image_name) + ": #{picture.name}", ) end # Size value for edit picture dialog def edit_picture_dialog_size(content) if content.settings[:caption_as_textarea] - content.settings[:sizes] ? '380x320' : '380x300' + content.settings[:sizes] ? "380x320" : "380x300" else - content.settings[:sizes] ? '380x290' : '380x255' + content.settings[:sizes] ? "380x290" : "380x255" end end end diff --git a/app/helpers/alchemy/admin/form_helper.rb b/app/helpers/alchemy/admin/form_helper.rb index 2e0dc0a9cd..1a132029a2 100644 --- a/app/helpers/alchemy/admin/form_helper.rb +++ b/app/helpers/alchemy/admin/form_helper.rb @@ -19,7 +19,7 @@ def alchemy_form_for(object, *args, &block) options[:remote] = request.xhr? options[:html] = { id: options.delete(:id), - class: ["alchemy", options.delete(:class)].compact.join(' ') + class: ["alchemy", options.delete(:class)].compact.join(" "), } simple_form_for(object, *(args << options), &block) end diff --git a/app/helpers/alchemy/admin/navigation_helper.rb b/app/helpers/alchemy/admin/navigation_helper.rb index 846e08a628..63654d9cc3 100644 --- a/app/helpers/alchemy/admin/navigation_helper.rb +++ b/app/helpers/alchemy/admin/navigation_helper.rb @@ -12,9 +12,9 @@ module NavigationHelper # def alchemy_main_navigation_entry(alchemy_module) render( - 'alchemy/admin/partials/main_navigation_entry', + "alchemy/admin/partials/main_navigation_entry", alchemy_module: alchemy_module, - navigation: alchemy_module['navigation'] + navigation: alchemy_module["navigation"], ) end @@ -36,8 +36,8 @@ def alchemy_main_navigation_entry(alchemy_module) # def navigate_module(navigation) [ - navigation['action'].to_sym, - navigation['controller'].to_s.gsub(/\A\//, '').gsub(/\//, '_').to_sym + navigation["action"].to_sym, + navigation["controller"].to_s.gsub(/\A\//, "").gsub(/\//, "_").to_sym, ] end @@ -45,9 +45,9 @@ def navigate_module(navigation) # def main_navigation_css_classes(navigation) [ - 'main_navi_entry', - admin_mainnavi_active?(navigation) ? 'active' : nil, - navigation.key?('sub_navigation') ? 'has_sub_navigation' : nil + "main_navi_entry", + admin_mainnavi_active?(navigation) ? "active" : nil, + navigation.key?("sub_navigation") ? "has_sub_navigation" : nil, ].compact end @@ -74,8 +74,8 @@ def entry_active?(entry) # def url_for_module(alchemy_module) route_from_engine_or_main_app( - alchemy_module['engine_name'], - url_options_for_module(alchemy_module) + alchemy_module["engine_name"], + url_options_for_module(alchemy_module), ) end @@ -93,8 +93,8 @@ def url_for_module_sub_navigation(navigation) return if alchemy_module.nil? route_from_engine_or_main_app( - alchemy_module['engine_name'], - url_options_for_navigation_entry(navigation) + alchemy_module["engine_name"], + url_options_for_navigation_entry(navigation), ) end @@ -106,13 +106,13 @@ def sorted_alchemy_modules sorted = [] not_sorted = [] alchemy_modules.map do |m| - if m['position'].blank? + if m["position"].blank? not_sorted << m else sorted << m end end - sorted.sort_by { |m| m['position'] } + not_sorted + sorted.sort_by { |m| m["position"] } + not_sorted end private @@ -138,7 +138,7 @@ def route_from_engine_or_main_app(engine_name, url_options) # A Alchemy module definition # def url_options_for_module(alchemy_module) - url_options_for_navigation_entry(alchemy_module['navigation'] || {}) + url_options_for_navigation_entry(alchemy_module["navigation"] || {}) end # Returns a url options hash for given navigation entry. @@ -148,26 +148,26 @@ def url_options_for_module(alchemy_module) # def url_options_for_navigation_entry(entry) { - controller: entry['controller'], - action: entry['action'], + controller: entry["controller"], + action: entry["action"], only_path: true, - params: entry['params'] + params: entry["params"], }.delete_if { |_k, v| v.nil? } end # Retrieves the current Alchemy module from controller and index action. # def current_alchemy_module - module_definition_for(controller: params[:controller], action: 'index') + module_definition_for(controller: params[:controller], action: "index") end # Returns true if the current controller and action is in a modules navigation definition. # def admin_mainnavi_active?(navigation) # Has the given navigation entry a active sub navigation? - has_active_entry?(navigation['sub_navigation'] || []) || + has_active_entry?(navigation["sub_navigation"] || []) || # Has the given navigation entry a active nested navigation? - has_active_entry?(navigation['nested'] || []) || + has_active_entry?(navigation["nested"] || []) || # Is the navigation entry active? entry_active?(navigation || {}) end @@ -175,7 +175,7 @@ def admin_mainnavi_active?(navigation) # Returns true if the given entry's controller is current controller # def is_entry_controller_active?(entry) - entry['controller'].gsub(/\A\//, '') == params[:controller] + entry["controller"].gsub(/\A\//, "") == params[:controller] end # Returns true if the given entry's action is current controllers action @@ -183,8 +183,8 @@ def is_entry_controller_active?(entry) # Also checks if given entry has a +nested_actions+ key, if so it checks if one of them is current controller's action # def is_entry_action_active?(entry) - entry['action'] == params[:action] || - entry.fetch('nested_actions', []).include?(params[:action]) + entry["action"] == params[:action] || + entry.fetch("nested_actions", []).include?(params[:action]) end # Returns true if an entry of given entries is active. diff --git a/app/helpers/alchemy/admin/pages_helper.rb b/app/helpers/alchemy/admin/pages_helper.rb index f46664bff3..af2037bf3e 100644 --- a/app/helpers/alchemy/admin/pages_helper.rb +++ b/app/helpers/alchemy/admin/pages_helper.rb @@ -9,13 +9,13 @@ module PagesHelper # def preview_sizes_for_select options_for_select([ - 'auto', - [Alchemy.t('240', scope: 'preview_sizes'), 240], - [Alchemy.t('320', scope: 'preview_sizes'), 320], - [Alchemy.t('480', scope: 'preview_sizes'), 480], - [Alchemy.t('768', scope: 'preview_sizes'), 768], - [Alchemy.t('1024', scope: 'preview_sizes'), 1024], - [Alchemy.t('1280', scope: 'preview_sizes'), 1280] + "auto", + [Alchemy.t("240", scope: "preview_sizes"), 240], + [Alchemy.t("320", scope: "preview_sizes"), 320], + [Alchemy.t("480", scope: "preview_sizes"), 480], + [Alchemy.t("768", scope: "preview_sizes"), 768], + [Alchemy.t("1024", scope: "preview_sizes"), 1024], + [Alchemy.t("1280", scope: "preview_sizes"), 1280], ]) end @@ -27,8 +27,8 @@ def page_layout_label(page) if page.persisted? && page.definition.blank? [ page_layout_missing_warning, - Alchemy.t(:page_type) - ].join(' ').html_safe + Alchemy.t(:page_type), + ].join(" ").html_safe else Alchemy.t(:page_type) end @@ -39,10 +39,10 @@ def page_status_checkbox(page, attribute) if page.attribute_fixed?(attribute) checkbox = check_box(:page, attribute, disabled: true) - hint = content_tag(:span, class: 'hint-bubble') do + hint = content_tag(:span, class: "hint-bubble") do Alchemy.t(:attribute_fixed, attribute: attribute) end - content = content_tag(:span, class: 'with-hint') do + content = content_tag(:span, class: "with-hint") do "#{checkbox}\n#{label}\n#{hint}".html_safe end else @@ -50,7 +50,7 @@ def page_status_checkbox(page, attribute) content = "#{checkbox}\n#{label}".html_safe end - content_tag(:label, class: 'checkbox') { content } + content_tag(:label, class: "checkbox") { content } end end end diff --git a/app/helpers/alchemy/admin/pictures_helper.rb b/app/helpers/alchemy/admin/pictures_helper.rb index 0cf9ab0ec4..d2c5f47cc4 100644 --- a/app/helpers/alchemy/admin/pictures_helper.rb +++ b/app/helpers/alchemy/admin/pictures_helper.rb @@ -4,12 +4,10 @@ module Alchemy module Admin module PicturesHelper def preview_size(size) - case size - when 'small' then '80x60' - when 'large' then '240x180' - else - '160x120' - end + Alchemy::Picture::THUMBNAIL_SIZES.fetch( + size, + Alchemy::Picture::THUMBNAIL_SIZES[:medium] + ) end end end diff --git a/app/helpers/alchemy/admin/tags_helper.rb b/app/helpers/alchemy/admin/tags_helper.rb index c9e7a9709a..05212f15f0 100644 --- a/app/helpers/alchemy/admin/tags_helper.rb +++ b/app/helpers/alchemy/admin/tags_helper.rb @@ -12,18 +12,18 @@ module TagsHelper # A HTML string containing tags # def render_tag_list(class_name) - raise ArgumentError, 'Please provide a String as class_name' if class_name.nil? + raise ArgumentError, "Please provide a String as class_name" if class_name.nil? sorted_tags_from(class_name: class_name).map do |tag| - content_tag('li', name: tag.name, class: filtered_by_tag?(tag) ? 'active' : nil) do + content_tag("li", name: tag.name, class: filtered_by_tag?(tag) ? "active" : nil) do link_to( "#{tag.name} (#{tag.taggings_count})", url_for( search_filter_params.except(:page, :tagged_with).merge( - tagged_with: tags_for_filter(current: tag).presence - ) + tagged_with: tags_for_filter(current: tag).presence, + ), ), - remote: request.xhr? + remote: request.xhr?, ) end end.join.html_safe @@ -44,13 +44,13 @@ def tags_for_filter(current:) tags_from_params - Array(current.name) else tags_from_params.push(current.name) - end.uniq.join(',') + end.uniq.join(",") end # Returns tags from params # @returns [Array] def tags_from_params - search_filter_params[:tagged_with].to_s.split(',') + search_filter_params[:tagged_with].to_s.split(",") end def sorted_tags_from(class_name:) diff --git a/app/helpers/alchemy/base_helper.rb b/app/helpers/alchemy/base_helper.rb index 55c60f98c3..76b9826326 100644 --- a/app/helpers/alchemy/base_helper.rb +++ b/app/helpers/alchemy/base_helper.rb @@ -27,16 +27,16 @@ def warning(message, text = nil) # # @return [String] def render_icon(icon_class, options = {}) - options = {style: 'solid'}.merge(options) + options = {style: "solid"}.merge(options) classes = [ "icon fa-fw", "fa-#{icon_class}", "fa#{options[:style].first}", options[:size] ? "fa-#{options[:size]}" : nil, options[:transform] ? "fa-#{options[:transform]}" : nil, - options[:class] + options[:class], ].compact - content_tag('i', nil, class: classes) + content_tag("i", nil, class: classes) end # Returns a div with an icon and the passed content @@ -64,7 +64,7 @@ def render_message(type = :info, msg = nil, &blk) # @param [Symbol] style The style of this flash. Valid values are +:notice+ (default), +:warn+ and +:error+ # def render_flash_notice(notice, style = :notice) - render('alchemy/admin/partials/flash', flash_type: style, message: notice) + render("alchemy/admin/partials/flash", flash_type: style, message: notice) end # Checks if the given argument is a String or a Page object. @@ -93,9 +93,9 @@ def page_or_find(page) # @return [String] The FontAwesome icon name def message_icon_class(message_type) case message_type.to_s - when 'warning', 'warn', 'alert' then 'exclamation' - when 'notice' then 'check' - when 'error' then 'bug' + when "warning", "warn", "alert" then "exclamation" + when "notice" then "check" + when "error" then "bug" else message_type end diff --git a/app/helpers/alchemy/elements_block_helper.rb b/app/helpers/alchemy/elements_block_helper.rb index 91aafeed07..6d5e0459c8 100644 --- a/app/helpers/alchemy/elements_block_helper.rb +++ b/app/helpers/alchemy/elements_block_helper.rb @@ -33,7 +33,7 @@ def render(name, options = {}, html_options = {}) helpers.render(content, { content: content, options: options, - html_options: html_options + html_options: html_options, }) end @@ -107,7 +107,7 @@ def element_view_for(element, options = {}) tag: :div, id: element_dom_id(element), class: element.name, - tags_formatter: ->(tags) { tags.join(" ") } + tags_formatter: ->(tags) { tags.join(" ") }, }.merge(options) # capture inner template block diff --git a/app/helpers/alchemy/elements_helper.rb b/app/helpers/alchemy/elements_helper.rb index 981ee81503..0d3e31e247 100644 --- a/app/helpers/alchemy/elements_helper.rb +++ b/app/helpers/alchemy/elements_helper.rb @@ -91,7 +91,7 @@ module ElementsHelper def render_elements(options = {}) options = { from_page: @page, - render_format: 'html' + render_format: "html", }.update(options) finder = options[:finder] || Alchemy::ElementsFinder.new(options) @@ -150,8 +150,8 @@ def render_elements(options = {}) # def render_element(element, options = {}, counter = 1) if element.nil? - warning('Element is nil') - render "alchemy/elements/view_not_found", {name: 'nil'} + warning("Element is nil") + render "alchemy/elements/view_not_found", {name: "nil"} return end @@ -160,7 +160,7 @@ def render_element(element, options = {}, counter = 1) render element, { element: element, counter: counter, - options: options + options: options, }.merge(options.delete(:locals) || {}) rescue ActionView::MissingTemplate => e warning(%( @@ -179,19 +179,14 @@ def element_dom_id(element) # Renders the HTML tag attributes required for preview mode. def element_preview_code(element) - if respond_to?(:tag_options) - tag_options(element_preview_code_attributes(element)) - else - # Rails 5.1 uses TagBuilder - tag_builder.tag_options(element_preview_code_attributes(element)) - end + tag_builder.tag_options(element_preview_code_attributes(element)) end # Returns a hash containing the HTML tag attributes required for preview mode. def element_preview_code_attributes(element) return {} unless element.present? && @preview_mode && element.page == @page - { 'data-alchemy-element' => element.id } + { "data-alchemy-element" => element.id } end # Returns the element's tags information as a string. Parameters and options @@ -203,12 +198,7 @@ def element_preview_code_attributes(element) # HTML tag attributes containing the element's tag information. # def element_tags(element, options = {}) - if respond_to?(:tag_options) - tag_options(element_tags_attributes(element, options)) - else - # Rails 5.1 uses TagBuilder - tag_builder.tag_options(element_tags_attributes(element, options)) - end + tag_builder.tag_options(element_tags_attributes(element, options)) end # Returns the element's tags information as an attribute hash. @@ -224,12 +214,12 @@ def element_tags(element, options = {}) # def element_tags_attributes(element, options = {}) options = { - formatter: lambda { |tags| tags.join(' ') } + formatter: lambda { |tags| tags.join(" ") }, }.merge(options) return {} if !element.taggable? || element.tag_list.blank? - { 'data-element-tags' => options[:formatter].call(element.tag_list) } + { "data-element-tags" => options[:formatter].call(element.tag_list) } end end end diff --git a/app/helpers/alchemy/pages_helper.rb b/app/helpers/alchemy/pages_helper.rb index b9af4d9c5c..c98ee22d4c 100644 --- a/app/helpers/alchemy/pages_helper.rb +++ b/app/helpers/alchemy/pages_helper.rb @@ -25,19 +25,19 @@ def picture_essence_caption(content) # def language_links(options = {}) options = { - linkname: 'name', + linkname: "name", show_title: true, - spacer: '', - reverse: false + spacer: "", + reverse: false, }.merge(options) - languages = Language.on_current_site.published.with_root_page.order("name #{options[:reverse] ? 'DESC' : 'ASC'}") + languages = Language.on_current_site.published.with_root_page.order("name #{options[:reverse] ? "DESC" : "ASC"}") return nil if languages.count < 2 render( partial: "alchemy/language_links/language", collection: languages, spacer_template: "alchemy/language_links/spacer", - locals: {languages: languages, options: options} + locals: { languages: languages, options: options }, ) end @@ -51,7 +51,7 @@ def render_page_layout render @page, page: @page rescue ActionView::MissingTemplate warning("PageLayout: '#{@page.page_layout}' not found. Rendering standard page_layout.") - render 'alchemy/page_layouts/standard', page: @page + render "alchemy/page_layouts/standard", page: @page end # Renders a partial for current site @@ -78,26 +78,22 @@ def render_site_layout # Menu partials are placed in the `app/views/alchemy/menus` folder # Use the `rails g alchemy:menus` generator to create the partials # - # @param [String] - Name of the menu + # @param [String] - Type of the menu # @param [Hash] - A set of options available in your menu partials - def render_menu(name, options = {}) + def render_menu(menu_type, options = {}) root_node = Alchemy::Node.roots.find_by( - name: name, - language: Alchemy::Language.current + menu_type: menu_type, + language: Alchemy::Language.current, ) if root_node.nil? - warning("Menu with name #{name} not found!") + warning("Menu with type #{menu_type} not found!") return end - options = { - node_partial_name: "#{root_node.view_folder_name}/node" - }.merge(options) - - render(root_node.to_partial_path, menu: root_node, node: root_node, options: options) + render("alchemy/menus/#{menu_type}/wrapper", menu: root_node, options: options) rescue ActionView::MissingTemplate => e warning <<~WARN - Menu partial not found for #{name}. + Menu partial not found for #{menu_type}. #{e} WARN end @@ -124,11 +120,11 @@ def render_breadcrumb(options = {}) page: @page, restricted_only: false, reverse: false, - link_active_page: false + link_active_page: false, }.merge(options) - pages = Page. - ancestors_for(options[:page]). + pages = options[:page]. + self_and_ancestors.contentpages. accessible_by(current_ability, :see) if options.delete(:restricted_only) @@ -136,7 +132,7 @@ def render_breadcrumb(options = {}) end if options.delete(:reverse) - pages = pages.reorder('lft DESC') + pages = pages.reorder("lft DESC") end if options[:without].present? @@ -144,7 +140,7 @@ def render_breadcrumb(options = {}) pages = pages.where.not(id: without.try(:collect, &:id) || without.id) end - render 'alchemy/breadcrumb/wrapper', pages: pages, options: options + render "alchemy/breadcrumb/wrapper", pages: pages, options: options end # Returns current page title @@ -160,7 +156,7 @@ def page_title(options = {}) options = { prefix: "", suffix: "", - separator: "" + separator: "", }.update(options) title_parts = [options[:prefix]] if response.status == 200 @@ -181,7 +177,7 @@ def meta_keywords end def meta_robots - "#{@page.robot_index? ? '' : 'no'}index, #{@page.robot_follow? ? '' : 'no'}follow" + "#{@page.robot_index? ? "" : "no"}index, #{@page.robot_follow? ? "" : "no"}follow" end end end diff --git a/app/helpers/alchemy/url_helper.rb b/app/helpers/alchemy/url_helper.rb index 0ce6846d80..a628ac6ecd 100644 --- a/app/helpers/alchemy/url_helper.rb +++ b/app/helpers/alchemy/url_helper.rb @@ -18,7 +18,7 @@ def show_alchemy_page_url(page, optional_params = {}) # Returns the correct params-hash for passing to show_page_path def show_page_path_params(page, optional_params = {}) - raise ArgumentError, 'Page is nil' if page.nil? + raise ArgumentError, "Page is nil" if page.nil? url_params = {urlname: page.urlname}.update(optional_params) prefix_locale? ? url_params.update(locale: page.language_code) : url_params diff --git a/app/mailers/alchemy/base_mailer.rb b/app/mailers/alchemy/base_mailer.rb index 7a72340240..ff69598fdc 100644 --- a/app/mailers/alchemy/base_mailer.rb +++ b/app/mailers/alchemy/base_mailer.rb @@ -2,7 +2,7 @@ module Alchemy begin - base_class = Object.const_get('::ApplicationMailer') + base_class = Object.const_get("::ApplicationMailer") rescue NameError base_class = ActionMailer::Base end diff --git a/app/mailers/alchemy/messages_mailer.rb b/app/mailers/alchemy/messages_mailer.rb index 61640737e3..b5d2e77080 100644 --- a/app/mailers/alchemy/messages_mailer.rb +++ b/app/mailers/alchemy/messages_mailer.rb @@ -8,7 +8,7 @@ def contact_form_mail(message, mail_to, mail_from, subject) from: mail_from, to: mail_to, reply_to: message.try(:email), - subject: subject + subject: subject, ) end end diff --git a/app/models/alchemy/attachment.rb b/app/models/alchemy/attachment.rb index a3b9a451eb..080247d20b 100644 --- a/app/models/alchemy/attachment.rb +++ b/app/models/alchemy/attachment.rb @@ -22,7 +22,7 @@ class Attachment < BaseRecord include Alchemy::Filetypes include Alchemy::NameConversions include Alchemy::Taggable - include Alchemy::ContentTouching + include Alchemy::TouchElements dragonfly_accessor :file, app: :alchemy_attachments do after_assign { |f| write_attribute(:file_mime_type, f.mime_type) } @@ -30,7 +30,7 @@ class Attachment < BaseRecord stampable stamper_class_name: Alchemy.user_class_name - has_many :essence_files, class_name: 'Alchemy::EssenceFile', foreign_key: 'attachment_id' + has_many :essence_files, class_name: "Alchemy::EssenceFile", foreign_key: "attachment_id" has_many :contents, through: :essence_files has_many :elements, through: :contents has_many :pages, through: :elements @@ -42,24 +42,25 @@ def searchable_alchemy_resource_attributes end def allowed_filetypes - Config.get(:uploader).fetch('allowed_filetypes', {}).fetch('alchemy/attachments', []) + Config.get(:uploader).fetch("allowed_filetypes", {}).fetch("alchemy/attachments", []) end def file_types_for_select file_types = Alchemy::Attachment.pluck(:file_mime_type).uniq.map do |type| - [Alchemy.t(type, scope: 'mime_types'), type] + [Alchemy.t(type, scope: "mime_types"), type] end file_types.sort_by(&:first) end end validates_presence_of :file - validates_size_of :file, maximum: Config.get(:uploader)['file_size_limit'].megabytes - validates_property :ext, of: :file, + validates_size_of :file, maximum: Config.get(:uploader)["file_size_limit"].megabytes + validates_property :ext, + of: :file, in: allowed_filetypes, case_sensitive: false, message: Alchemy.t("not a valid file"), - unless: -> { self.class.allowed_filetypes.include?('*') } + unless: -> { self.class.allowed_filetypes.include?("*") } before_save :set_name, if: :file_name_changed? @@ -71,13 +72,13 @@ def to_jq_upload { "name" => read_attribute(:file_name), "size" => read_attribute(:file_size), - 'error' => errors[:file].join + "error" => errors[:file].join, } end # An url save filename without format suffix def urlname - CGI.escape(file_name.gsub(/\.#{extension}$/, '').tr('.', ' ')) + CGI.escape(file_name.gsub(/\.#{extension}$/, "").tr(".", " ")) end # Checks if the attachment is restricted, because it is attached on restricted pages only @@ -89,6 +90,7 @@ def restricted? def extension file_name.split(".").last end + alias_method :suffix, :extension # Returns a css class name for kind of file diff --git a/app/models/alchemy/base_record.rb b/app/models/alchemy/base_record.rb index 3a023c6460..300e372563 100644 --- a/app/models/alchemy/base_record.rb +++ b/app/models/alchemy/base_record.rb @@ -1,14 +1,10 @@ # frozen_string_literal: true module Alchemy def self.table_name_prefix - 'alchemy_' + "alchemy_" end class BaseRecord < ActiveRecord::Base self.abstract_class = true - - def active_record_5_1? - ActiveRecord.gem_version >= Gem::Version.new('5.1.0') - end end end diff --git a/app/models/alchemy/content.rb b/app/models/alchemy/content.rb index 25621726c7..c6b34e5f28 100644 --- a/app/models/alchemy/content.rb +++ b/app/models/alchemy/content.rb @@ -28,29 +28,25 @@ class Content < BaseRecord belongs_to :element, touch: true, inverse_of: :contents has_one :page, through: :element - stampable stamper_class_name: Alchemy.user_class_name - - acts_as_list scope: [:element_id] - # Essence scopes - scope :essence_booleans, -> { where(essence_type: "Alchemy::EssenceBoolean") } - scope :essence_dates, -> { where(essence_type: "Alchemy::EssenceDate") } - scope :essence_files, -> { where(essence_type: "Alchemy::EssenceFile") } - scope :essence_htmls, -> { where(essence_type: "Alchemy::EssenceHtml") } - scope :essence_links, -> { where(essence_type: "Alchemy::EssenceLink") } - scope :essence_pictures, -> { where(essence_type: "Alchemy::EssencePicture") } + scope :essence_booleans, -> { where(essence_type: "Alchemy::EssenceBoolean") } + scope :essence_dates, -> { where(essence_type: "Alchemy::EssenceDate") } + scope :essence_files, -> { where(essence_type: "Alchemy::EssenceFile") } + scope :essence_htmls, -> { where(essence_type: "Alchemy::EssenceHtml") } + scope :essence_links, -> { where(essence_type: "Alchemy::EssenceLink") } + scope :essence_pictures, -> { where(essence_type: "Alchemy::EssencePicture") } scope :essence_richtexts, -> { where(essence_type: "Alchemy::EssenceRichtext") } - scope :essence_selects, -> { where(essence_type: "Alchemy::EssenceSelect") } - scope :essence_texts, -> { where(essence_type: "Alchemy::EssenceText") } - scope :named, ->(name) { where(name: name) } - scope :available, -> { published.not_trashed } - scope :published, -> { joins(:element).merge(Element.published) } - scope :not_trashed, -> { joins(:element).merge(Element.not_trashed) } - scope :not_restricted, -> { joins(:element).merge(Element.not_restricted) } - - delegate :restricted?, to: :page, allow_nil: true - delegate :trashed?, to: :element, allow_nil: true - delegate :public?, to: :element, allow_nil: true + scope :essence_selects, -> { where(essence_type: "Alchemy::EssenceSelect") } + scope :essence_texts, -> { where(essence_type: "Alchemy::EssenceText") } + scope :named, ->(name) { where(name: name) } + scope :available, -> { published.not_trashed } + scope :published, -> { joins(:element).merge(Element.published) } + scope :not_trashed, -> { joins(:element).merge(Element.not_trashed) } + scope :not_restricted, -> { joins(:element).merge(Element.not_restricted) } + + delegate :restricted?, to: :page, allow_nil: true + delegate :trashed?, to: :element, allow_nil: true + delegate :public?, to: :element, allow_nil: true class << self # Returns the translated label for a content name. @@ -72,7 +68,7 @@ def translated_label_for(content_name, element_name = nil) Alchemy.t( content_name, scope: "content_names.#{element_name}", - default: Alchemy.t("content_names.#{content_name}", default: content_name.humanize) + default: Alchemy.t("content_names.#{content_name}", default: content_name.humanize), ) end end @@ -132,7 +128,7 @@ def serialize { name: name, value: serialized_ingredient, - link: essence.try(:link) + link: essence.try(:link), }.delete_if { |_k, v| v.blank? } end @@ -174,12 +170,12 @@ def essence_validation_failed? end def has_validations? - definition['validate'].present? + definition["validate"].present? end # Returns a string used as dom id on html elements. def dom_id - return '' if essence.nil? + return "" if essence.nil? "#{essence_partial_name}_#{id}" end @@ -195,7 +191,7 @@ def linked? # Returns true if this content should be taken for element preview. def preview_content? - !!definition['as_element_title'] + !!definition["as_element_title"] end # Proxy method that returns the preview text from essence. @@ -205,7 +201,7 @@ def preview_text(maxlength = 30) end def essence_partial_name - return '' if essence.nil? + return "" if essence.nil? essence.partial_name end diff --git a/app/models/alchemy/content/factory.rb b/app/models/alchemy/content/factory.rb index c01ba3505b..44c197125a 100644 --- a/app/models/alchemy/content/factory.rb +++ b/app/models/alchemy/content/factory.rb @@ -56,11 +56,11 @@ def copy(source, differences = {}) new_content = Content.new( source.attributes. except(*SKIPPED_ATTRIBUTES_ON_COPY). - merge(differences.with_indifferent_access) + merge(differences.with_indifferent_access), ) new_essence = source.essence.class.create!( source.essence.attributes. - except(*SKIPPED_ATTRIBUTES_ON_COPY) + except(*SKIPPED_ATTRIBUTES_ON_COPY), ) new_content.tap do |content| content.essence = new_essence @@ -71,7 +71,7 @@ def copy(source, differences = {}) # Returns all content definitions from elements.yml # def definitions - definitions = Element.definitions.flat_map { |e| e['contents'] } + definitions = Element.definitions.flat_map { |e| e["contents"] } definitions.compact! definitions end @@ -119,7 +119,7 @@ def definition # def build_essence(type = essence_type) self.essence = essence_class(type).new({ - ingredient: default_value + ingredient: default_value, }) end @@ -139,7 +139,7 @@ def create_essence!(type = nil) # If an optional type is passed, this type of essence gets constantized. # def essence_class(type = nil) - Content.normalize_essence_type(type || definition['type']).constantize + Content.normalize_essence_type(type || definition["type"]).constantize end end end diff --git a/app/models/alchemy/element.rb b/app/models/alchemy/element.rb index 2385a3269c..4cef90a3b2 100644 --- a/app/models/alchemy/element.rb +++ b/app/models/alchemy/element.rb @@ -22,6 +22,8 @@ module Alchemy class Element < BaseRecord + NAME_REGEXP = /\A[a-z0-9_-]+\z/ + include Alchemy::Logger include Alchemy::Taggable include Alchemy::Hints @@ -34,7 +36,7 @@ class Element < BaseRecord "hint", "taggable", "compact", - "message" + "message", ].freeze SKIPPED_ATTRIBUTES_ON_COPY = [ @@ -45,7 +47,7 @@ class Element < BaseRecord "folded", "position", "updated_at", - "updater_id" + "updater_id", ].freeze # All Elements that share the same page id and parent element id and are fixed or not are considered a list. @@ -60,17 +62,17 @@ class Element < BaseRecord stampable stamper_class_name: Alchemy.user_class_name - has_many :contents, -> { order(:position) }, dependent: :destroy, inverse_of: :element + has_many :contents, dependent: :destroy, inverse_of: :element has_many :all_nested_elements, -> { order(:position).not_trashed }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", foreign_key: :parent_element_id, dependent: :destroy has_many :nested_elements, -> { order(:position).available }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", foreign_key: :parent_element_id, dependent: :destroy, inverse_of: :parent_element @@ -79,17 +81,17 @@ class Element < BaseRecord # A nested element belongs to a parent element. belongs_to :parent_element, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", optional: true, touch: true, inverse_of: :nested_elements has_and_belongs_to_many :touchable_pages, -> { distinct }, - class_name: 'Alchemy::Page', + class_name: "Alchemy::Page", join_table: ElementToPage.table_name validates_presence_of :name, on: :create - validates_format_of :name, on: :create, with: /\A[a-z0-9_-]+\z/ + validates_format_of :name, on: :create, with: NAME_REGEXP attr_accessor :autogenerate_contents attr_accessor :autogenerate_nested_elements @@ -98,19 +100,19 @@ class Element < BaseRecord after_update :touch_touchable_pages - scope :trashed, -> { where(position: nil).order('updated_at DESC') } - scope :not_trashed, -> { where.not(position: nil) } - scope :published, -> { where(public: true) } - scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) } - scope :available, -> { published.not_trashed } - scope :named, ->(names) { where(name: names) } - scope :excluded, ->(names) { where.not(name: names) } - scope :fixed, -> { where(fixed: true) } - scope :unfixed, -> { where(fixed: false) } - scope :from_current_site, -> { where(Language.table_name => {site_id: Site.current || Site.default}).joins(page: 'language') } - scope :folded, -> { where(folded: true) } - scope :expanded, -> { where(folded: false) } - scope :not_nested, -> { where(parent_element_id: nil) } + scope :trashed, -> { where(position: nil).order("updated_at DESC") } + scope :not_trashed, -> { where.not(position: nil) } + scope :published, -> { where(public: true) } + scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) } + scope :available, -> { published.not_trashed } + scope :named, ->(names) { where(name: names) } + scope :excluded, ->(names) { where.not(name: names) } + scope :fixed, -> { where(fixed: true) } + scope :unfixed, -> { where(fixed: false) } + scope :from_current_site, -> { where(Language.table_name => { site_id: Site.current || Site.default }).joins(page: "language") } + scope :folded, -> { where(folded: true) } + scope :expanded, -> { where(folded: false) } + scope :not_nested, -> { where(parent_element_id: nil) } delegate :restricted?, to: :page, allow_nil: true @@ -132,7 +134,7 @@ class << self def new(attributes = {}) return super if attributes[:name].blank? - element_attributes = attributes.to_h.merge(name: attributes[:name].split('#').first) + element_attributes = attributes.to_h.merge(name: attributes[:name].split("#").first) element_definition = Element.definition_by_name(element_attributes[:name]) if element_definition.nil? raise(ElementDefinitionError, attributes) @@ -154,13 +156,13 @@ def new(attributes = {}) # def copy(source_element, differences = {}) attributes = source_element.attributes.with_indifferent_access - .except(*SKIPPED_ATTRIBUTES_ON_COPY) - .merge(differences) - .merge({ - autogenerate_contents: false, - autogenerate_nested_elements: false, - tag_list: source_element.tag_list - }) + .except(*SKIPPED_ATTRIBUTES_ON_COPY) + .merge(differences) + .merge({ + autogenerate_contents: false, + autogenerate_nested_elements: false, + tag_list: source_element.tag_list, + }) new_element = create!(attributes) @@ -178,7 +180,7 @@ def copy(source_element, differences = {}) def all_from_clipboard(clipboard) return [] if clipboard.nil? - where(id: clipboard.collect { |e| e['id'] }) + where(id: clipboard.collect { |e| e["id"] }) end # All elements in clipboard that could be placed on page @@ -197,7 +199,7 @@ def all_from_clipboard_for_page(clipboard, page) # Pass an element name to get next of this kind. # def next(name = nil) - elements = page.elements.published.where('position > ?', position) + elements = page.elements.published.where("position > ?", position) select_element(elements, name, :asc) end @@ -206,7 +208,7 @@ def next(name = nil) # Pass an element name to get previous of this kind. # def prev(name = nil) - elements = page.elements.published.where('position < ?', position) + elements = page.elements.published.where("position < ?", position) select_element(elements, name, :desc) end @@ -232,7 +234,7 @@ def trashed? # Returns true if the definition of this element has a taggable true value. def taggable? - definition['taggable'] == true + definition["taggable"] == true end # The opposite of folded? @@ -242,7 +244,7 @@ def expanded? # Defined as compact element? def compact? - definition['compact'] == true + definition["compact"] == true end # The element's view partial is dependent from its name @@ -279,7 +281,7 @@ def cache_key # A collection of element names that can be nested inside this element. def nestable_elements - definition.fetch('nestable_elements', []) + definition.fetch("nestable_elements", []) end # Copy all nested elements from current element to given target element. @@ -287,7 +289,7 @@ def copy_nested_elements_to(target_element) nested_elements.map do |nested_element| Element.copy(nested_element, { parent_element_id: target_element.id, - page_id: target_element.page_id + page_id: target_element.page_id, }) end end @@ -295,7 +297,7 @@ def copy_nested_elements_to(target_element) private def generate_nested_elements - definition.fetch('autogenerate', []).each do |nestable_element| + definition.fetch("autogenerate", []).each do |nestable_element| if nestable_elements.include?(nestable_element) Element.create(page: page, parent_element_id: id, name: nestable_element) else diff --git a/app/models/alchemy/element/definitions.rb b/app/models/alchemy/element/definitions.rb index ed42ab42a2..6bea4c26a0 100644 --- a/app/models/alchemy/element/definitions.rb +++ b/app/models/alchemy/element/definitions.rb @@ -19,7 +19,7 @@ def definitions # Returns one element definition by given name. # def definition_by_name(name) - definitions.detect { |d| d['name'] == name } + definitions.detect { |d| d["name"] == name } end private @@ -37,14 +37,14 @@ def read_definitions_file # Returns the +elements.yml+ file path # def definitions_file_path - Rails.root.join 'config/alchemy/elements.yml' + Rails.root.join "config/alchemy/elements.yml" end end # The definition of this element. # def definition - if definition = self.class.definitions.detect { |d| d['name'] == name } + if definition = self.class.definitions.detect { |d| d["name"] == name } definition else log_warning "Could not find element definition for #{name}. Please check your elements.yml file!" diff --git a/app/models/alchemy/element/element_contents.rb b/app/models/alchemy/element/element_contents.rb index 254b941740..6175097b5a 100644 --- a/app/models/alchemy/element/element_contents.rb +++ b/app/models/alchemy/element/element_contents.rb @@ -73,7 +73,7 @@ def copy_contents_to(element) # rss_title: true # def content_for_rss_title - content_for_rss_meta('title') + content_for_rss_meta("title") end # Returns the content that is marked as rss description. @@ -87,14 +87,14 @@ def content_for_rss_title # rss_description: true # def content_for_rss_description - content_for_rss_meta('description') + content_for_rss_meta("description") end # Returns the array with the hashes for all element contents in the elements.yml file def content_definitions return nil if definition.blank? - definition['contents'] + definition["contents"] end # Returns the definition for given content_name @@ -103,7 +103,7 @@ def content_definition_for(content_name) log_warning "Element #{name} is missing the content definition for #{content_name}" nil else - content_definitions.detect { |d| d['name'] == content_name.to_s } + content_definitions.detect { |d| d["name"] == content_name.to_s } end end @@ -139,12 +139,12 @@ def content_for_rss_meta(type) definition = content_definitions.detect { |c| c["rss_#{type}"] } return if definition.blank? - contents.detect { |content| content.name == definition['name'] } + contents.detect { |content| content.name == definition["name"] } end # creates the contents for this element as described in the elements.yml def create_contents - definition.fetch('contents', []).each do |attributes| + definition.fetch("contents", []).each do |attributes| Content.create(attributes.merge(element: self)) end end diff --git a/app/models/alchemy/element/element_essences.rb b/app/models/alchemy/element/element_essences.rb index 706cde4fb2..e94237a8dc 100644 --- a/app/models/alchemy/element/element_essences.rb +++ b/app/models/alchemy/element/element_essences.rb @@ -97,12 +97,12 @@ def essence_error_messages errors.each do |error| messages << Alchemy.t( "#{name}.#{content_name}.#{error}", - scope: 'content_validations', + scope: "content_validations", default: [ "fields.#{content_name}.#{error}".to_sym, - "errors.#{error}".to_sym + "errors.#{error}".to_sym, ], - field: Content.translated_label_for(content_name, name) + field: Content.translated_label_for(content_name, name), ) end end diff --git a/app/models/alchemy/element/presenters.rb b/app/models/alchemy/element/presenters.rb index 2f08573ef1..2feb754185 100644 --- a/app/models/alchemy/element/presenters.rb +++ b/app/models/alchemy/element/presenters.rb @@ -23,7 +23,7 @@ module ClassMethods # If no translation is found a humanized name is used. # def display_name_for(name) - Alchemy.t(name, scope: 'element_names', default: name.to_s.humanize) + Alchemy.t(name, scope: "element_names", default: name.to_s.humanize) end end @@ -32,7 +32,7 @@ def display_name_for(name) # @see Alchemy::Element::Presenters#display_name_for # def display_name - self.class.display_name_for(definition['name'] || name) + self.class.display_name_for(definition["name"] || name) end # Returns a preview text for element. diff --git a/app/models/alchemy/element_to_page.rb b/app/models/alchemy/element_to_page.rb index f48f6d79ef..4fe35ffb56 100644 --- a/app/models/alchemy/element_to_page.rb +++ b/app/models/alchemy/element_to_page.rb @@ -3,7 +3,7 @@ module Alchemy class ElementToPage def self.table_name - [Alchemy::Element.table_name, Alchemy::Page.table_name].join('_') + [Alchemy::Element.table_name, Alchemy::Page.table_name].join("_") end end end diff --git a/app/models/alchemy/essence_boolean.rb b/app/models/alchemy/essence_boolean.rb index b943e4f67a..075ef37f6a 100644 --- a/app/models/alchemy/essence_boolean.rb +++ b/app/models/alchemy/essence_boolean.rb @@ -8,14 +8,12 @@ # value :boolean # created_at :datetime not null # updated_at :datetime not null -# creator_id :integer -# updater_id :integer # # Stores boolean values. # Provides a checkbox in the editor views. module Alchemy class EssenceBoolean < BaseRecord - acts_as_essence ingredient_column: 'value' + acts_as_essence ingredient_column: "value" end end diff --git a/app/models/alchemy/essence_date.rb b/app/models/alchemy/essence_date.rb index 8b4bfa8534..d3b7db2b4e 100644 --- a/app/models/alchemy/essence_date.rb +++ b/app/models/alchemy/essence_date.rb @@ -6,15 +6,13 @@ # # id :integer not null, primary key # date :datetime -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # module Alchemy class EssenceDate < BaseRecord - acts_as_essence ingredient_column: 'date' + acts_as_essence ingredient_column: "date" # Returns self.date for the Element#preview_text method. def preview_text(_maxlength = nil) diff --git a/app/models/alchemy/essence_file.rb b/app/models/alchemy/essence_file.rb index c732875553..3485d53a3a 100644 --- a/app/models/alchemy/essence_file.rb +++ b/app/models/alchemy/essence_file.rb @@ -8,8 +8,6 @@ # attachment_id :integer # title :string # css_class :string -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # link_text :string @@ -18,7 +16,7 @@ module Alchemy class EssenceFile < BaseRecord belongs_to :attachment, optional: true - acts_as_essence ingredient_column: 'attachment' + acts_as_essence ingredient_column: "attachment" def attachment_url return if attachment.nil? @@ -26,7 +24,7 @@ def attachment_url routes.download_attachment_path( id: attachment.id, name: attachment.urlname, - format: attachment.suffix + format: attachment.suffix, ) end diff --git a/app/models/alchemy/essence_html.rb b/app/models/alchemy/essence_html.rb index bb8b5bc7bc..09208db337 100644 --- a/app/models/alchemy/essence_html.rb +++ b/app/models/alchemy/essence_html.rb @@ -6,15 +6,13 @@ # # id :integer not null, primary key # source :text -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # module Alchemy class EssenceHtml < BaseRecord - acts_as_essence ingredient_column: 'source' + acts_as_essence ingredient_column: "source" # Returns the first x (default = 30) (HTML escaped) characters from self.source for the Element#preview_text method. def preview_text(maxlength = 30) diff --git a/app/models/alchemy/essence_link.rb b/app/models/alchemy/essence_link.rb index 6b0f0a5491..77735d8d22 100644 --- a/app/models/alchemy/essence_link.rb +++ b/app/models/alchemy/essence_link.rb @@ -11,12 +11,10 @@ # link_class_name :string # created_at :datetime not null # updated_at :datetime not null -# creator_id :integer -# updater_id :integer # module Alchemy class EssenceLink < BaseRecord - acts_as_essence ingredient_column: 'link' + acts_as_essence ingredient_column: "link" end end diff --git a/app/models/alchemy/essence_node.rb b/app/models/alchemy/essence_node.rb new file mode 100644 index 0000000000..162dba46cc --- /dev/null +++ b/app/models/alchemy/essence_node.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Alchemy + class EssenceNode < BaseRecord + acts_as_essence( + ingredient_column: :node, + preview_text_column: :node_name, + belongs_to: { + class_name: "Alchemy::Node", + foreign_key: :node_id, + inverse_of: :essence_nodes, + optional: true, + }, + ) + + delegate :name, to: :node, prefix: true, allow_nil: true + end +end diff --git a/app/models/alchemy/essence_page.rb b/app/models/alchemy/essence_page.rb index ad369cd975..749583bcdf 100644 --- a/app/models/alchemy/essence_page.rb +++ b/app/models/alchemy/essence_page.rb @@ -2,28 +2,15 @@ module Alchemy class EssencePage < BaseRecord - PAGE_ID = /\A\d+\z/ - acts_as_essence( ingredient_column: :page, preview_text_method: :name, belongs_to: { - class_name: 'Alchemy::Page', + class_name: "Alchemy::Page", foreign_key: :page_id, inverse_of: :essence_pages, - optional: true - } + optional: true, + }, ) - - def ingredient=(page) - case page - when PAGE_ID - self.page = Alchemy::Page.new(id: page) - when Alchemy::Page - self.page = page - else - super - end - end end end diff --git a/app/models/alchemy/essence_picture.rb b/app/models/alchemy/essence_picture.rb index 473f1dbd96..dc777a296a 100644 --- a/app/models/alchemy/essence_picture.rb +++ b/app/models/alchemy/essence_picture.rb @@ -14,8 +14,6 @@ # link_title :string # css_class :string # link_target :string -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # crop_from :string @@ -26,10 +24,10 @@ module Alchemy class EssencePicture < BaseRecord acts_as_essence ingredient_column: :picture, belongs_to: { - class_name: 'Alchemy::Picture', + class_name: "Alchemy::Picture", foreign_key: :picture_id, inverse_of: :essence_pictures, - optional: true + optional: true, } serialize :render_gravity, JSON @@ -83,7 +81,7 @@ def picture_url_options format: picture.default_render_format, crop_from: crop_from.presence, crop_size: crop_size.presence, - size: content.settings[:size] + size: content.settings[:size], }.with_indifferent_access end @@ -105,7 +103,7 @@ def thumbnail_url crop_from: crop_from.presence, crop_size: crop_size.presence, flatten: true, - format: picture.image_file_format + format: picture.image_file_format, } picture.url(options) @@ -201,7 +199,7 @@ def allow_image_cropping? content && content.settings[:crop] && picture && picture.can_be_cropped_to( content.settings[:size], - content.settings[:upsample] + content.settings[:upsample], ) end @@ -231,7 +229,7 @@ def validate_render_gravity end def normalize_crop_value(crop_value) - self[crop_value].split('x').map { |n| normalize_number(n) }.join('x') + self[crop_value].split("x").map { |n| normalize_number(n) }.join("x") end def normalize_number(number) diff --git a/app/models/alchemy/essence_picture_view.rb b/app/models/alchemy/essence_picture_view.rb index d8af6bbec0..ddd97e7c42 100644 --- a/app/models/alchemy/essence_picture_view.rb +++ b/app/models/alchemy/essence_picture_view.rb @@ -41,12 +41,12 @@ def render output = link_to(output, url_for(essence.link), { title: essence.link_title.presence, target: essence.link_target == "blank" ? "_blank" : nil, - data: {link_target: essence.link_target.presence} + data: { link_target: essence.link_target.presence }, }) end if caption - content_tag(:figure, output, {class: essence.css_class.presence}.merge(html_options)) + content_tag(:figure, output, { class: essence.css_class.presence }.merge(html_options)) else output end @@ -66,8 +66,8 @@ def img_tag alt: essence.alt_tag.presence, title: essence.title.presence, class: caption ? nil : essence.css_class.presence, - srcset: srcset.join(', ').presence, - sizes: options[:sizes].join(', ').presence + srcset: srcset.join(", ").presence, + sizes: options[:sizes].join(", ").presence, }.merge(caption ? {} : html_options) ) end diff --git a/app/models/alchemy/essence_richtext.rb b/app/models/alchemy/essence_richtext.rb index 8c545d76c8..606b1af90b 100644 --- a/app/models/alchemy/essence_richtext.rb +++ b/app/models/alchemy/essence_richtext.rb @@ -8,15 +8,13 @@ # body :text # stripped_body :text # public :boolean -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # module Alchemy class EssenceRichtext < BaseRecord - acts_as_essence preview_text_column: 'stripped_body' + acts_as_essence preview_text_column: "stripped_body" before_save :strip_content diff --git a/app/models/alchemy/essence_select.rb b/app/models/alchemy/essence_select.rb index b9e2865fbe..167e94abda 100644 --- a/app/models/alchemy/essence_select.rb +++ b/app/models/alchemy/essence_select.rb @@ -8,13 +8,11 @@ # value :string # created_at :datetime not null # updated_at :datetime not null -# creator_id :integer -# updater_id :integer # # Provides a select box that stores string values. module Alchemy class EssenceSelect < BaseRecord - acts_as_essence ingredient_column: 'value' + acts_as_essence ingredient_column: "value" end end diff --git a/app/models/alchemy/essence_text.rb b/app/models/alchemy/essence_text.rb index 7f10c0ef1c..dbde3047dd 100644 --- a/app/models/alchemy/essence_text.rb +++ b/app/models/alchemy/essence_text.rb @@ -11,8 +11,6 @@ # link_class_name :string # public :boolean default(FALSE) # link_target :string -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/alchemy/language.rb b/app/models/alchemy/language.rb index 3770f1c2c0..9626acdc45 100644 --- a/app/models/alchemy/language.rb +++ b/app/models/alchemy/language.rb @@ -20,15 +20,18 @@ # locale :string # -require_dependency 'alchemy/site' +require_dependency "alchemy/site" module Alchemy class Language < BaseRecord belongs_to :site - has_many :pages + has_many :pages, inverse_of: :language + has_many :nodes, inverse_of: :language before_validation :set_locale, if: -> { locale.blank? } + has_one :root_page, -> { where(parent: nil, layoutpage: false) }, class_name: "Alchemy::Page" + validates :name, presence: true validates :page_layout, presence: true validates :frontpage_name, presence: true @@ -59,8 +62,8 @@ class Language < BaseRecord throw(:abort) end - scope :published, -> { where(public: true) } - scope :with_root_page, -> { joins(:pages).where(Page.table_name => {language_root: true}) } + scope :published, -> { where(public: true) } + scope :with_root_page, -> { joins(:pages).where(Page.table_name => { language_root: true }) } class << self def on_site(site) @@ -104,16 +107,6 @@ def label(attrib) include Alchemy::Language::Code - # Root page - def root_page - @root_page ||= pages.language_roots.first - end - - # Layout root page - def layout_root_page - @layout_root_page ||= Page.layout_root_for(id) - end - # All available locales matching this language # # Matching either the code (+language_code+ + +country_code+) or the +language_code+ @@ -122,10 +115,14 @@ def layout_root_page # def matching_locales @_matching_locales ||= ::I18n.available_locales.select do |locale| - locale.to_s.split('-')[0] == language_code + locale.to_s.split("-")[0] == language_code end end + def available_menu_names + Alchemy::Node.available_menu_names - nodes.reject(&:parent_id).map(&:menu_type) + end + private def set_locale @@ -167,11 +164,7 @@ def remove_old_default end def should_set_pages_language? - if active_record_5_1? - saved_change_to_language_code? || saved_change_to_country_code? - else - language_code_changed? || country_code_changed? - end + saved_change_to_language_code? || saved_change_to_country_code? end def set_pages_language @@ -179,11 +172,7 @@ def set_pages_language end def should_unpublish_pages? - if active_record_5_1? - saved_changes[:public] == [true, false] - else - changes[:public] == [true, false] - end + saved_changes[:public] == [true, false] end def unpublish_pages diff --git a/app/models/alchemy/language/code.rb b/app/models/alchemy/language/code.rb index 3b90d98b75..78d257315e 100644 --- a/app/models/alchemy/language/code.rb +++ b/app/models/alchemy/language/code.rb @@ -4,7 +4,7 @@ module Alchemy::Language::Code extend ActiveSupport::Concern def code - [language_code, country_code].select(&:present?).join('-') + [language_code, country_code].select(&:present?).join("-") end def code=(code) @@ -13,11 +13,11 @@ def code=(code) module ClassMethods def find_by_code(code) - codes = code.split('-') - codes << '' if codes.length == 1 + codes = code.split("-") + codes << "" if codes.length == 1 on_current_site.find_by( language_code: codes[0], - country_code: codes[1] + country_code: codes[1], ) end end diff --git a/app/models/alchemy/legacy_page_url.rb b/app/models/alchemy/legacy_page_url.rb index 5e24960d0b..f733105e28 100644 --- a/app/models/alchemy/legacy_page_url.rb +++ b/app/models/alchemy/legacy_page_url.rb @@ -13,7 +13,7 @@ class Alchemy::LegacyPageUrl < ActiveRecord::Base belongs_to :page, - class_name: 'Alchemy::Page', + class_name: "Alchemy::Page", required: true validates :urlname, diff --git a/app/models/alchemy/message.rb b/app/models/alchemy/message.rb index e1c5fea234..64326e07c5 100644 --- a/app/models/alchemy/message.rb +++ b/app/models/alchemy/message.rb @@ -22,17 +22,17 @@ def self.config attr_accessor :contact_form_id, :ip - config['fields'].each do |field| + config["fields"].each do |field| attr_accessor field.to_sym end - config['validate_fields'].each do |field| + config["validate_fields"].each do |field| validates_presence_of field case field.to_sym when /email/ validates_format_of field, - with: Alchemy::Config.get('format_matchers')['email'], + with: Alchemy::Config.get("format_matchers")["email"], if: -> { send(field).present? } when :email_confirmation validates_confirmation_of :email diff --git a/app/models/alchemy/node.rb b/app/models/alchemy/node.rb index 7649a5d64a..187c310cf7 100644 --- a/app/models/alchemy/node.rb +++ b/app/models/alchemy/node.rb @@ -4,13 +4,22 @@ module Alchemy class Node < BaseRecord VALID_URL_REGEX = /\A(\/|\D[a-z\+\d\.\-]+:)/ - acts_as_nested_set scope: 'language_id', touch: true + before_destroy :check_if_related_essence_nodes_present + + acts_as_nested_set scope: "language_id", touch: true stampable stamper_class_name: Alchemy.user_class_name - belongs_to :site, class_name: 'Alchemy::Site' - belongs_to :language, class_name: 'Alchemy::Language' - belongs_to :page, class_name: 'Alchemy::Page', optional: true, inverse_of: :nodes + belongs_to :language, class_name: "Alchemy::Language" + belongs_to :page, class_name: "Alchemy::Page", optional: true, inverse_of: :nodes + + has_one :site, through: :language + + has_many :essence_nodes, class_name: "Alchemy::EssenceNode", foreign_key: :node_id, inverse_of: :ingredient_association + before_validation :translate_root_menu_name, if: -> { root? } + before_validation :set_menu_type_from_root, unless: -> { root? } + + validates :menu_type, presence: true validates :name, presence: true, if: -> { page.nil? } validates :url, format: { with: VALID_URL_REGEX }, unless: -> { url.nil? } @@ -25,7 +34,7 @@ def name class << self # Returns all root nodes for current language def language_root_nodes - raise 'No language found' if Language.current.nil? + raise "No language found" if Language.current.nil? roots.where(language_id: Language.current.id) end @@ -49,7 +58,7 @@ def read_definitions_file # Returns the +menus.yml+ file path # def definitions_file_path - Rails.root.join 'config/alchemy/menus.yml' + Rails.root.join "config/alchemy/menus.yml" end end @@ -58,15 +67,29 @@ def definitions_file_path # Either the value is stored in the database, aka. an external url. # Or, if attached, the values comes from a page. def url - page && "/#{page.urlname}" || read_attribute(:url).presence + page&.url_path || read_attribute(:url).presence end def to_partial_path - "#{view_folder_name}/wrapper" + "alchemy/menus/#{menu_type}/node" + end + + private + + def check_if_related_essence_nodes_present + dependent_essence_nodes = self_and_descendants.flat_map(&:essence_nodes) + if dependent_essence_nodes.any? + errors.add(:base, :essence_nodes_present, page_names: dependent_essence_nodes.map(&:page).map(&:name).to_sentence) + throw(:abort) + end + end + + def translate_root_menu_name + self.name ||= Alchemy.t(menu_type, scope: :menu_names) end - def view_folder_name - "alchemy/menus/#{name.parameterize.underscore}" + def set_menu_type_from_root + self.menu_type = root.menu_type end end end diff --git a/app/models/alchemy/page.rb b/app/models/alchemy/page.rb index f4d6b051d9..669ad7b92c 100644 --- a/app/models/alchemy/page.rb +++ b/app/models/alchemy/page.rb @@ -17,7 +17,6 @@ # rgt :integer # parent_id :integer # depth :integer -# visible :boolean default(FALSE) # locked_by :integer # restricted :boolean default(FALSE) # robot_index :boolean default(TRUE) @@ -44,11 +43,10 @@ class Page < BaseRecord DEFAULT_ATTRIBUTES_FOR_COPY = { autogenerate_elements: false, - visible: false, public_on: nil, public_until: nil, locked_at: nil, - locked_by: nil + locked_by: nil, } SKIPPED_ATTRIBUTES_ON_COPY = %w( @@ -78,16 +76,15 @@ class Page < BaseRecord :tag_list, :title, :urlname, - :visible, :layoutpage, - :menu_id + :menu_id, ] - acts_as_nested_set(dependent: :destroy) + acts_as_nested_set(dependent: :destroy, scope: [:layoutpage, :language_id]) stampable stamper_class_name: Alchemy.user_class_name - belongs_to :language, optional: true + belongs_to :language belongs_to :creator, primary_key: Alchemy.user_class_primary_key, @@ -110,42 +107,33 @@ class Page < BaseRecord has_one :site, through: :language has_many :site_languages, through: :site, source: :languages has_many :folded_pages - has_many :legacy_urls, class_name: 'Alchemy::LegacyPageUrl' - has_many :nodes, class_name: 'Alchemy::Node', inverse_of: :page + has_many :legacy_urls, class_name: "Alchemy::LegacyPageUrl" + has_many :nodes, class_name: "Alchemy::Node", inverse_of: :page - validates_presence_of :language, on: :create, unless: :root - validates_presence_of :page_layout, unless: :systempage? - validates_format_of :page_layout, with: /\A[a-z0-9_-]+\z/, unless: -> { systempage? || page_layout.blank? } - validates_presence_of :parent_id, if: proc { Page.count > 1 } + before_validation :set_language, + if: -> { language.nil? } + + validates_presence_of :page_layout + validates_format_of :page_layout, with: /\A[a-z0-9_-]+\z/, unless: -> { page_layout.blank? } + validates_presence_of :parent, unless: -> { layoutpage? || language_root? } before_save :set_language_code, - if: -> { language.present? }, - unless: :systempage? + if: -> { language.present? } before_save :set_restrictions_to_child_pages, - if: :restricted_changed?, - unless: :systempage? + if: :restricted_changed? before_save :inherit_restricted_status, - if: -> { parent && parent.restricted? }, - unless: :systempage? + if: -> { parent && parent.restricted? } before_save :set_published_at, - if: -> { public_on.present? && published_at.nil? }, - unless: :systempage? + if: -> { public_on.present? && published_at.nil? } before_save :set_fixed_attributes, if: -> { fixed_attributes.any? } - before_create :set_language_from_parent_or_default, - if: -> { language_id.blank? }, - unless: :systempage? - after_update :create_legacy_url, - if: :should_create_legacy_url? - - after_update :attach_to_menu!, - if: :should_attach_to_menu? + if: :saved_change_to_urlname? after_update -> { nodes.update_all(updated_at: Time.current) } @@ -158,22 +146,9 @@ class Page < BaseRecord # site_name accessor delegate :name, to: :site, prefix: true, allow_nil: true - attr_accessor :menu_id - # Class methods # class << self - # The root page of the page tree - # - # Internal use only. You wouldn't use this page ever. - # - # Automatically created when accessed the first time. - # - def root - super || create!(name: 'Root') - end - alias_method :rootpage, :root - # Used to store the current page previewed in the edit page template. # def current_preview=(page) @@ -217,30 +192,12 @@ def copy(source, differences = {}) end end - def layout_root_for(language_id) - where({parent_id: Page.root.id, layoutpage: true, language_id: language_id}).limit(1).first - end - - def find_or_create_layout_root_for(language_id) - layoutroot = layout_root_for(language_id) - return layoutroot if layoutroot - - language = Language.find(language_id) - Page.create!( - name: "Layoutroot for #{language.name}", - layoutpage: true, - language: language, - autogenerate_elements: false, - parent_id: Page.root.id - ) - end - def copy_and_paste(source, new_parent, new_name) page = copy(source, { parent_id: new_parent.id, language: new_parent.language, name: new_name, - title: new_name + title: new_name, }) if source.children.any? source.copy_children_to(page) @@ -251,7 +208,7 @@ def copy_and_paste(source, new_parent, new_name) def all_from_clipboard(clipboard) return [] if clipboard.blank? - where(id: clipboard.collect { |p| p['id'] }) + where(id: clipboard.collect { |p| p["id"] }) end def all_from_clipboard_for_select(clipboard, language_id, layoutpage = false) @@ -259,28 +216,20 @@ def all_from_clipboard_for_select(clipboard, language_id, layoutpage = false) clipboard_pages = all_from_clipboard(clipboard) allowed_page_layouts = Alchemy::PageLayout.selectable_layouts(language_id, layoutpage) - allowed_page_layout_names = allowed_page_layouts.collect { |p| p['name'] } + allowed_page_layout_names = allowed_page_layouts.collect { |p| p["name"] } clipboard_pages.select { |cp| allowed_page_layout_names.include?(cp.page_layout) } end def link_target_options - options = [[Alchemy.t(:default, scope: 'link_target_options'), '']] + options = [[Alchemy.t(:default, scope: "link_target_options"), ""]] link_target_options = Config.get(:link_target_options) link_target_options.each do |option| - options << [Alchemy.t(option, scope: 'link_target_options', - default: option.to_s.humanize), option] + options << [Alchemy.t(option, scope: "link_target_options", + default: option.to_s.humanize), option] end options end - # Returns an array of all pages in the same branch from current. - # I.e. used to find the active page in navigation. - def ancestors_for(current) - return [] if current.nil? - - current.self_and_ancestors.contentpages - end - private # Aggregates the attributes from given source for copy of page. @@ -295,7 +244,7 @@ def attributes_from_source_for_copy(source, differences = {}) differences.stringify_keys! attributes = source.attributes.merge(differences) attributes.merge!(DEFAULT_ATTRIBUTES_FOR_COPY) - attributes['name'] = new_name_for_copy(differences['name'], source.name) + attributes["name"] = new_name_for_copy(differences["name"], source.name) attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY) end @@ -312,7 +261,7 @@ def attributes_from_source_for_copy(source, differences = {}) def new_name_for_copy(custom_name, source_name) return custom_name if custom_name.present? - "#{source_name} (#{Alchemy.t('Copy')})" + "#{source_name} (#{Alchemy.t("Copy")})" end end @@ -345,6 +294,13 @@ def find_elements(options = {}) finder.elements(page: self) end + # = The url_path for this page + # + # @see Alchemy::Page::UrlPath#call + def url_path + Alchemy::Page::UrlPath.new(self).call + end + # The page's view partial is dependent from its page layout # # == Define page layouts @@ -371,9 +327,10 @@ def to_partial_path # only public pages (true), skip public pages (false) # def previous(options = {}) - pages = self_and_siblings.where('lft < ?', lft) + pages = self_and_siblings.where("lft < ?", lft) select_page(pages, options.merge(order: :desc)) end + alias_method :previous_page, :previous # Returns the next page on the same level or nil. @@ -384,9 +341,10 @@ def previous(options = {}) # only public pages (true), skip public pages (false) # def next(options = {}) - pages = self_and_siblings.where('lft > ?', lft) + pages = self_and_siblings.where("lft > ?", lft) select_page(pages, options.merge(order: :asc)) end + alias_method :next_page, :next # Locks the page to given user @@ -435,7 +393,7 @@ def copy_children_to(new_parent) new_child = Page.copy(child, { language_id: new_parent.language_id, - language_code: new_parent.language_code + language_code: new_parent.language_code, }) new_child.move_to_child_of(new_parent) child.copy_children_to(new_child) unless child.children.blank? @@ -454,7 +412,7 @@ def publish! update_columns( published_at: current_time, public_on: already_public_for?(current_time) ? public_on : current_time, - public_until: still_public_for?(current_time) ? public_until : nil + public_until: still_public_for?(current_time) ? public_until : nil, ) end @@ -467,9 +425,9 @@ def publish! # A tree node with new lft, rgt, depth, url, parent_id and restricted indexes to be updated # def update_node!(node) - hash = {lft: node.left, rgt: node.right, parent_id: node.parent, depth: node.depth, restricted: node.restricted} + hash = { lft: node.left, rgt: node.right, parent_id: node.parent, depth: node.depth, restricted: node.restricted } - if Config.get(:url_nesting) && urlname != node.url + if urlname != node.url LegacyPageUrl.create(page_id: id, urlname: urlname) hash[:urlname] = node.url end @@ -520,7 +478,7 @@ def public_until # does not respond to +#name+ it returns +'unknown'+ # def creator_name - creator.try(:name) || Alchemy.t('unknown') + creator.try(:name) || Alchemy.t("unknown") end # Returns the name of the last updater of this page. @@ -529,7 +487,7 @@ def creator_name # does not respond to +#name+ it returns +'unknown'+ # def updater_name - updater.try(:name) || Alchemy.t('unknown') + updater.try(:name) || Alchemy.t("unknown") end # Returns the name of the user currently editing this page. @@ -538,7 +496,7 @@ def updater_name # does not respond to +#name+ it returns +'unknown'+ # def locker_name - locker.try(:name) || Alchemy.t('unknown') + locker.try(:name) || Alchemy.t("unknown") end # Menus (aka. root nodes) this page is attached to @@ -562,8 +520,8 @@ def select_page(pages, options = {}) .limit(1).first end - def set_language_from_parent_or_default - self.language = parent.language || Language.default + def set_language + self.language = parent&.language || Language.current set_language_code end @@ -571,41 +529,13 @@ def set_language_code self.language_code = language.code end - def should_create_legacy_url? - if active_record_5_1? - saved_change_to_urlname? - else - urlname_changed? - end - end - # Stores the old urlname in a LegacyPageUrl def create_legacy_url - if active_record_5_1? - former_urlname = urlname_before_last_save - else - former_urlname = urlname_was - end - legacy_urls.find_or_create_by(urlname: former_urlname) + legacy_urls.find_or_create_by(urlname: urlname_before_last_save) end def set_published_at self.published_at = Time.current end - - def attach_to_menu! - current_site_id = Alchemy::Site.current.id - node = Alchemy::Node.find_by!(id: menu_id, site_id: current_site_id) - node.children.create!( - site_id: current_site_id, - language_id: language_id, - page_id: id, - name: name - ) - end - - def should_attach_to_menu? - menu_id.present? && nodes.none? - end end end diff --git a/app/models/alchemy/page/fixed_attributes.rb b/app/models/alchemy/page/fixed_attributes.rb index 9a91116d92..679a058eb0 100644 --- a/app/models/alchemy/page/fixed_attributes.rb +++ b/app/models/alchemy/page/fixed_attributes.rb @@ -15,7 +15,6 @@ module Alchemy # fixed_attributes: # - public_on: nil # - public_until: nil - # - visible: false # class Page::FixedAttributes attr_reader :page @@ -31,7 +30,7 @@ def initialize(page) # @return Hash # def attributes - @_attributes ||= page.definition.fetch('fixed_attributes', {}).symbolize_keys + @_attributes ||= page.definition.fetch("fixed_attributes", {}).symbolize_keys end alias_method :all, :attributes diff --git a/app/models/alchemy/page/page_elements.rb b/app/models/alchemy/page/page_elements.rb index 7caa06ce01..ea9fbe33c0 100644 --- a/app/models/alchemy/page/page_elements.rb +++ b/app/models/alchemy/page/page_elements.rb @@ -9,37 +9,37 @@ module Page::PageElements has_many :all_elements, -> { order(:position) }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", inverse_of: :page has_many :elements, -> { order(:position).not_nested.unfixed.available }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", inverse_of: :page has_many :trashed_elements, -> { Element.trashed.order(:position) }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", inverse_of: :page has_many :fixed_elements, -> { order(:position).fixed.available }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", inverse_of: :page has_many :dependent_destroyable_elements, -> { not_nested }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", dependent: :destroy has_many :contents, through: :elements has_and_belongs_to_many :to_be_swept_elements, -> { distinct }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", join_table: ElementToPage.table_name after_create :generate_elements, - unless: -> { systempage? || autogenerate_elements == false } + unless: -> { autogenerate_elements == false } after_update :trash_not_allowed_elements!, - if: :has_page_layout_changed? + if: :saved_change_to_page_layout? after_update :generate_elements, - if: :has_page_layout_changed? + if: :saved_change_to_page_layout? end module ClassMethods @@ -53,7 +53,7 @@ def copy_elements(source, target) source_elements = source.all_elements.not_nested.not_trashed source_elements.order(:position).map do |source_element| Element.copy(source_element, { - page_id: target.id + page_id: target.id, }).tap(&:move_to_bottom) end end @@ -81,11 +81,11 @@ def copy_elements(source, target) # def available_element_definitions(only_element_named = nil) @_element_definitions ||= if only_element_named - definition = Element.definition_by_name(only_element_named) - element_definitions_by_name(definition['nestable_elements']) - else - element_definitions - end + definition = Element.definition_by_name(only_element_named) + element_definitions_by_name(definition["nestable_elements"]) + else + element_definitions + end return [] if @_element_definitions.blank? @@ -100,20 +100,20 @@ def available_element_definitions(only_element_named = nil) # All names of elements that can actually be placed on current page. # def available_element_names - @_available_element_names ||= available_element_definitions.map { |e| e['name'] } + @_available_element_names ||= available_element_definitions.map { |e| e["name"] } end # Available element definitions excluding nested unique elements. # def available_elements_within_current_scope(parent) @_available_elements = if parent - parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name) - available_element_definitions(parent.name).reject do |e| - parents_unique_nested_elements.include? e['name'] + parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name) + available_element_definitions(parent.name).reject do |e| + parents_unique_nested_elements.include? e["name"] + end + else + available_element_definitions end - else - available_element_definitions - end end # All element definitions defined for page's page layout @@ -129,10 +129,10 @@ def element_definitions # def descendent_element_definitions definitions = element_definitions_by_name(element_definition_names) - definitions.select { |d| d.key?('nestable_elements') }.each do |d| - definitions += element_definitions_by_name(d['nestable_elements']) + definitions.select { |d| d.key?("nestable_elements") }.each do |d| + definitions += element_definitions_by_name(d["nestable_elements"]) end - definitions.uniq { |d| d['name'] } + definitions.uniq { |d| d["name"] } end # All names of elements that are defined in the page definition. @@ -145,7 +145,7 @@ def descendent_element_definitions # elements: [headline, contactform] # def element_definition_names - definition['elements'] || [] + definition["elements"] || [] end # Element definitions with given name(s) @@ -161,7 +161,7 @@ def element_definitions_by_name(names) if names.to_s == "all" Element.definitions else - Element.definitions.select { |e| names.include? e['name'] } + Element.definitions.select { |e| names.include? e["name"] } end end @@ -174,14 +174,14 @@ def element_definitions_by_name(names) # feed_elements: [element_name, element_2_name] # def feed_elements - elements.named(definition['feed_elements']) + elements.named(definition["feed_elements"]) end # Returns an array of all EssenceRichtext contents ids from not folded elements # def richtext_contents_ids Alchemy::Content.joins(:element) - .where(Element.table_name => {page_id: id, folded: false}) + .where(Element.table_name => { page_id: id, folded: false }) .select(&:has_tinymce?) .collect(&:id) end @@ -195,7 +195,7 @@ def richtext_contents_ids def generate_elements existing_elements = all_elements.not_nested.not_trashed existing_element_names = existing_elements.pluck(:name).uniq - definition.fetch('autogenerate', []).each do |element_name| + definition.fetch("autogenerate", []).each do |element_name| next if existing_element_names.include?(element_name) Element.create(page: self, name: element_name) @@ -206,24 +206,16 @@ def generate_elements def trash_not_allowed_elements! not_allowed_elements = elements.where([ "#{Element.table_name}.name NOT IN (?)", - element_definition_names + element_definition_names, ]) not_allowed_elements.to_a.map(&:trash!) end - def has_page_layout_changed? - if active_record_5_1? - saved_change_to_page_layout? - else - page_layout_changed? - end - end - # Deletes unique and already present definitions from @_element_definitions. # def delete_unique_element_definitions! @_element_definitions.delete_if do |element| - element['unique'] && @_existing_element_names.include?(element['name']) + element["unique"] && @_existing_element_names.include?(element["name"]) end end @@ -231,8 +223,8 @@ def delete_unique_element_definitions! # def delete_outnumbered_element_definitions! @_element_definitions.delete_if do |element| - outnumbered = @_existing_element_names.select { |name| name == element['name'] } - element['amount'] && outnumbered.count >= element['amount'].to_i + outnumbered = @_existing_element_names.select { |name| name == element["name"] } + element["amount"] && outnumbered.count >= element["amount"].to_i end end end diff --git a/app/models/alchemy/page/page_naming.rb b/app/models/alchemy/page/page_naming.rb index dd55a5668f..6c6f8f61b5 100644 --- a/app/models/alchemy/page/page_naming.rb +++ b/app/models/alchemy/page/page_naming.rb @@ -9,24 +9,22 @@ module Page::PageNaming included do before_validation :set_urlname, if: :renamed?, - unless: -> { systempage? || name.blank? } + unless: -> { name.blank? } validates :name, presence: true validates :urlname, - uniqueness: {scope: [:language_id, :layoutpage], if: -> { urlname.present? }}, - exclusion: {in: RESERVED_URLNAMES}, - length: {minimum: 3, if: -> { urlname.present? }} + uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? } }, + exclusion: { in: RESERVED_URLNAMES }, + length: { minimum: 3, if: -> { urlname.present? } } before_save :set_title, - unless: -> { systempage? }, if: -> { title.blank? } after_update :update_descendants_urlnames, - if: :should_update_descendants_urlnames? + if: :saved_change_to_urlname? - after_move :update_urlname!, - if: -> { Config.get(:url_nesting) } + after_move :update_urlname! end # Returns true if name or urlname has changed. @@ -37,7 +35,7 @@ def renamed? # Makes a slug of all ancestors urlnames including mine and delimit them be slash. # So the whole path is stored as urlname in the database. def update_urlname! - new_urlname = nested_url_name(slug) + new_urlname = nested_url_name if urlname != new_urlname legacy_urls.create(urlname: urlname) update_column(:urlname, new_urlname) @@ -46,34 +44,11 @@ def update_urlname! # Returns always the last part of a urlname path def slug - urlname.to_s.split('/').last - end - - # Returns an array of visible/non-language_root ancestors. - def visible_ancestors - return [] unless parent - - if new_record? - parent.visible_ancestors.tap do |base| - base.push(parent) if parent.visible? - end - else - ancestors.visible.contentpages.where(language_root: nil).to_a - end + urlname.to_s.split("/").last end private - def should_update_descendants_urlnames? - return false if !Config.get(:url_nesting) - - if active_record_5_1? - saved_change_to_urlname? || saved_change_to_visible? - else - urlname_changed? || visible_changed? - end - end - def update_descendants_urlnames reload descendants.each(&:update_urlname!) @@ -81,14 +56,9 @@ def update_descendants_urlnames # Sets the urlname to a url friendly slug. # Either from name, or if present, from urlname. - # If url_nesting is enabled the urlname contains the whole path. + # The urlname contains the whole path including parent urlnames. def set_urlname - if Config.get(:url_nesting) - value = slug - else - value = urlname - end - self[:urlname] = nested_url_name(value) + self[:urlname] = nested_url_name end def set_title @@ -100,26 +70,17 @@ def set_title # Names shorter than 3 will be filled up with dashes, # so it does not collidate with the language code. # - def convert_url_name(value) - url_name = convert_to_urlname(value.blank? ? name : value) - if url_name.length < 3 - ('-' * (3 - url_name.length)) + url_name - else - url_name - end + def converted_url_name + url_name = convert_to_urlname(slug.blank? ? name : slug) + url_name.rjust(3, "-") end - def nested_url_name(value) - (ancestor_slugs << convert_url_name(value)).join('/') - end - - # Slugs of all visible/non-language_root ancestors. - # Returns [], if there is no parent, the parent is - # the root page itself, or url_nesting is off. - def ancestor_slugs - return [] if !Config.get(:url_nesting) || parent.nil? || parent.root? - - visible_ancestors.map(&:slug).compact + def nested_url_name + if parent&.language_root? + converted_url_name + else + [parent&.urlname, converted_url_name].compact.join("/") + end end end end diff --git a/app/models/alchemy/page/page_natures.rb b/app/models/alchemy/page/page_natures.rb index 63be5afe2b..5347f35596 100644 --- a/app/models/alchemy/page/page_natures.rb +++ b/app/models/alchemy/page/page_natures.rb @@ -14,19 +14,13 @@ def expiration_time end def taggable? - definition['taggable'] == true + definition["taggable"] == true end def rootpage? !new_record? && parent_id.blank? end - def systempage? - return true if Page.count.zero? - - rootpage? || (parent_id == Page.root.id && !language_root?) - end - def folded?(user_id) return unless Alchemy.user_class < ActiveRecord::Base @@ -67,9 +61,8 @@ def locked? def status { public: public?, - visible: visible?, locked: locked?, - restricted: restricted? + restricted: restricted?, } end @@ -95,7 +88,7 @@ def definition # Page layout names are defined inside the config/alchemy/page_layouts.yml file. # Translate the name in your config/locales language yml file. def layout_display_name - Alchemy.t(page_layout, scope: 'page_layout_names') + Alchemy.t(page_layout, scope: "page_layout_names") end # Returns the name for the layout partial @@ -155,7 +148,7 @@ def cache_page? return false unless caching_enabled? page_layout = PageLayout.get(self.page_layout) - page_layout['cache'] != false && page_layout['searchresults'] != true + page_layout["cache"] != false && page_layout["searchresults"] != true end private diff --git a/app/models/alchemy/page/page_scopes.rb b/app/models/alchemy/page/page_scopes.rb index acfbafd2a1..8f6255750b 100644 --- a/app/models/alchemy/page/page_scopes.rb +++ b/app/models/alchemy/page/page_scopes.rb @@ -21,20 +21,17 @@ module Page::PageScopes # All pages locked by given user # - scope :locked_by, ->(user) { - if user.class.respond_to? :primary_key - locked.where(locked_by: user.send(user.class.primary_key)) - end - } + scope :locked_by, + ->(user) { + if user.class.respond_to? :primary_key + locked.where(locked_by: user.send(user.class.primary_key)) + end + } # All not locked pages # scope :not_locked, -> { where(locked_at: nil, locked_by: nil) } - # All visible pages - # - scope :visible, -> { where(visible: true) } - # All not restricted pages # scope :not_restricted, -> { where(restricted: false) } @@ -45,29 +42,27 @@ module Page::PageScopes # All pages that are a published language root # - scope :public_language_roots, -> { - published.language_roots.where( - language_code: Language.published.pluck(:language_code) - ) - } + scope :public_language_roots, + -> { + published.language_roots.where( + language_code: Language.published.pluck(:language_code), + ) + } # Last 5 pages that where recently edited by given user # - scope :all_last_edited_from, ->(user) { - where(updater_id: user.id).order('updated_at DESC').limit(5) - } + scope :all_last_edited_from, + ->(user) { + where(updater_id: user.id).order("updated_at DESC").limit(5) + } # Returns all pages that have the given +language_id+ # - scope :with_language, ->(language_id) { - where(language_id: language_id) - } + scope :with_language, ->(language_id) { where(language_id: language_id) } # Returns all content pages. # - scope :contentpages, -> { - where(layoutpage: [false, nil]).where.not(parent_id: nil) - } + scope :contentpages, -> { where(layoutpage: [false, nil]) } # Returns all public contentpages that are not locked. # @@ -79,9 +74,7 @@ module Page::PageScopes # # Used for flushing all pages caches at once. # - scope :flushable_layoutpages, -> { - not_locked.layoutpages.where.not(parent_id: Page.unscoped.root.id) - } + scope :flushable_layoutpages, -> { not_locked.layoutpages } # All searchable pages # @@ -89,9 +82,10 @@ module Page::PageScopes # All pages from +Alchemy::Site.current+ # - scope :from_current_site, -> { - where(Language.table_name => {site_id: Site.current || Site.default}).joins(:language) - } + scope :from_current_site, + -> { + where(Language.table_name => { site_id: Site.current || Site.default }).joins(:language) + } # All pages for xml sitemap # diff --git a/app/models/alchemy/page/url_path.rb b/app/models/alchemy/page/url_path.rb new file mode 100644 index 0000000000..b5fca4aa08 --- /dev/null +++ b/app/models/alchemy/page/url_path.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Alchemy + class Page + # = The url_path for this page + # + # Use this to build relative links to this page + # + # It takes several circumstances into account: + # + # 1. It returns just a slash for language root pages of the default langauge + # 2. It returns a url path with a leading slash for regular pages + # 3. It returns a url path with a leading slash and language code prefix for pages not having the default language + # 4. It returns a url path with a leading slash and the language code for language root pages of a non-default language + # + # == Examples + # + # Using Rails' link_to helper + # + # link_to page.url + # + class UrlPath + ROOT_PATH = "/" + + def initialize(page) + @page = page + @language = @page.language + @site = @language.site + end + + def call + if @page.language_root? + language_root_path + elsif @site.languages.select(&:public?).length > 1 + page_path_with_language_prefix + else + page_path_with_leading_slash + end + end + + private + + def language_root_path + @language.default? ? ROOT_PATH : language_path + end + + def page_path_with_language_prefix + @language.default? ? page_path : language_path + page_path + end + + def page_path_with_leading_slash + @page.language_root? ? ROOT_PATH : page_path + end + + def language_path + "/#{@page.language_code}" + end + + def page_path + "/#{@page.urlname}" + end + end + end +end diff --git a/app/models/alchemy/picture.rb b/app/models/alchemy/picture.rb index 9deb1fd11e..961f14805f 100644 --- a/app/models/alchemy/picture.rb +++ b/app/models/alchemy/picture.rb @@ -22,17 +22,23 @@ module Alchemy class Picture < BaseRecord + THUMBNAIL_SIZES = { + small: "80x60", + medium: "160x120", + large: "240x180", + }.with_indifferent_access.freeze + CONVERTIBLE_FILE_FORMATS = %w(gif jpg jpeg png).freeze include Alchemy::NameConversions include Alchemy::Taggable - include Alchemy::ContentTouching + include Alchemy::TouchElements include Alchemy::Picture::Transformations include Alchemy::Picture::Url has_many :essence_pictures, - class_name: 'Alchemy::EssencePicture', - foreign_key: 'picture_id', + class_name: "Alchemy::EssencePicture", + foreign_key: "picture_id", inverse_of: :ingredient_association has_many :contents, through: :essence_pictures @@ -50,24 +56,37 @@ class Picture < BaseRecord raise PictureInUseError, Alchemy.t(:cannot_delete_picture_notice) % { name: name } end + # Image preprocessing class + def self.preprocessor_class + @_preprocessor_class ||= Preprocessor + end + + # Set a image preprocessing class + # + # # config/initializers/alchemy.rb + # Alchemy::Picture.preprocessor_class = My::ImagePreprocessor + # + def self.preprocessor_class=(klass) + @_preprocessor_class = klass + end + # Enables Dragonfly image processing dragonfly_accessor :image_file, app: :alchemy_pictures do # Preprocess after uploading the picture - after_assign do |p| - resize = Config.get(:preprocess_image_resize) - p.thumb!(resize) if resize.present? + after_assign do |image| + self.class.preprocessor_class.new(image).call end end # We need to define this method here to have it available in the validations below. class << self def allowed_filetypes - Config.get(:uploader).fetch('allowed_filetypes', {}).fetch('alchemy/pictures', []) + Config.get(:uploader).fetch("allowed_filetypes", {}).fetch("alchemy/pictures", []) end end validates_presence_of :image_file - validates_size_of :image_file, maximum: Config.get(:uploader)['file_size_limit'].megabytes + validates_size_of :image_file, maximum: Config.get(:uploader)["file_size_limit"].megabytes validates_property :format, of: :image_file, in: allowed_filetypes, @@ -76,21 +95,10 @@ def allowed_filetypes stampable stamper_class_name: Alchemy.user_class_name - scope :named, ->(name) { - where("#{table_name}.name LIKE ?", "%#{name}%") - } - - scope :recent, -> { - where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) - } - - scope :deletable, -> { - where("#{table_name}.id NOT IN (SELECT picture_id FROM #{EssencePicture.table_name})") - } - - scope :without_tag, -> { - left_outer_joins(:taggings).where(gutentag_taggings: {id: nil}) - } + scope :named, ->(name) { where("#{table_name}.name LIKE ?", "%#{name}%") } + scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) } + scope :deletable, -> { where("#{table_name}.id NOT IN (SELECT picture_id FROM #{EssencePicture.table_name})") } + scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: { id: nil }) } # Class methods @@ -124,11 +132,11 @@ def search_by(params, query, per_page = nil) pictures.order(:name) end - def filtered_by(filter = '') + def filtered_by(filter = "") case filter - when 'recent' then recent - when 'last_upload' then last_upload - when 'without_tag' then without_tag + when "recent" then recent + when "last_upload" then last_upload + when "without_tag" then without_tag else all end @@ -167,7 +175,7 @@ def to_jq_upload { name: image_file_name, size: image_file_size, - error: errors[:image_file].join + error: errors[:image_file].join, } end @@ -177,7 +185,7 @@ def urlname if name.blank? "image_#{id}" else - ::CGI.escape(name.gsub(/\.(gif|png|jpe?g|tiff?)/i, '').tr('.', ' ')) + ::CGI.escape(name.gsub(/\.(gif|png|jpe?g|tiff?)/i, "").tr(".", " ")) end end @@ -215,7 +223,7 @@ def default_render_format # def convertible? Config.get(:image_output_format) && - Config.get(:image_output_format) != 'original' && + Config.get(:image_output_format) != "original" && has_convertible_format? end diff --git a/app/models/alchemy/picture/preprocessor.rb b/app/models/alchemy/picture/preprocessor.rb new file mode 100644 index 0000000000..9798ae125b --- /dev/null +++ b/app/models/alchemy/picture/preprocessor.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Alchemy + class Picture < BaseRecord + class Preprocessor + def initialize(image_file) + @image_file = image_file + end + + # Preprocess images after upload + # + # Define preprocessing options in the Alchemy::Config + # + # preprocess_image_resize [String] - Downsizing example: '1000x1000>' + # + def call + max_image_size = Alchemy::Config.get(:preprocess_image_resize) + image_file.thumb!(max_image_size) if max_image_size.present? + end + + private + + attr_reader :image_file + end + end +end diff --git a/app/models/alchemy/picture/transformations.rb b/app/models/alchemy/picture/transformations.rb index 9382afd970..3ef3819722 100644 --- a/app/models/alchemy/picture/transformations.rb +++ b/app/models/alchemy/picture/transformations.rb @@ -279,7 +279,7 @@ def sizes_from_string(string = "0x0") height = 0 if height.nil? { width: width, - height: height + height: height, } end @@ -309,7 +309,7 @@ def square_format? def image_size { width: image_file_width, - height: image_file_height + height: image_file_height, } end @@ -348,7 +348,7 @@ def point_from_string(string = "0x0") y = 0 if y.nil? { x: x, - y: y + y: y, } end @@ -359,7 +359,7 @@ def point_from_string(string = "0x0") def get_top_left_crop_corner(dimensions) { x: (image_file_width - dimensions[:width]) / 2, - y: (image_file_height - dimensions[:height]) / 2 + y: (image_file_height - dimensions[:height]) / 2, } end @@ -383,7 +383,7 @@ def get_base_dimensions def size_when_fitting(target, dimensions = get_base_dimensions) zoom = [ dimensions[:width].to_f / target[:width], - dimensions[:height].to_f / target[:height] + dimensions[:height].to_f / target[:height], ].max if zoom == 0.0 @@ -406,7 +406,7 @@ def point_and_mask_to_points(point, mask) x1: point[:x], y1: point[:y], x2: point[:x] + mask[:width], - y2: point[:y] + mask[:height] + y2: point[:y] + mask[:height], } end @@ -449,7 +449,7 @@ def xy_crop_resize(dimensions, top_left, crop_dimensions, upsample) def reduce_to_image(dimensions) { width: [dimensions[:width], image_file_width].min, - height: [dimensions[:height], image_file_height].min + height: [dimensions[:height], image_file_height].min, } end end diff --git a/app/models/alchemy/picture/url.rb b/app/models/alchemy/picture/url.rb index 6b2f8a7f13..160e3fe2af 100644 --- a/app/models/alchemy/picture/url.rb +++ b/app/models/alchemy/picture/url.rb @@ -77,8 +77,8 @@ def encoded_image(image, options = {}) end options = { - flatten: target_format != 'gif' && image_file_format == 'gif' - }.merge(options) + flatten: target_format != "gif" && image_file_format == "gif", + }.with_indifferent_access.merge(options) encoding_options = [] @@ -88,13 +88,13 @@ def encoded_image(image, options = {}) end if options[:flatten] - encoding_options << '-flatten' + encoding_options << "-flatten" end convertion_needed = target_format != image_file_format || encoding_options.present? if has_convertible_format? && convertion_needed - image = image.encode(target_format, encoding_options.join(' ')) + image = image.encode(target_format, encoding_options.join(" ")) end image diff --git a/app/models/alchemy/site/layout.rb b/app/models/alchemy/site/layout.rb index a863e834f5..12088dfb8b 100644 --- a/app/models/alchemy/site/layout.rb +++ b/app/models/alchemy/site/layout.rb @@ -3,7 +3,7 @@ module Alchemy module Site::Layout extend ActiveSupport::Concern - SITE_DEFINITIONS_FILE = Rails.root.join('config/alchemy/site_layouts.yml') + SITE_DEFINITIONS_FILE = Rails.root.join("config/alchemy/site_layouts.yml") module ClassMethods # Returns the site layouts definition defined in +site_layouts.yml+ file @@ -28,7 +28,7 @@ def read_site_definitions # Returns site's layout definition # def definition - self.class.definitions.detect { |l| l['name'] == partial_name } + self.class.definitions.detect { |l| l["name"] == partial_name } end # Returns the name for the layout partial diff --git a/app/models/concerns/alchemy/content_touching.rb b/app/models/concerns/alchemy/content_touching.rb deleted file mode 100644 index f7fe71d1be..0000000000 --- a/app/models/concerns/alchemy/content_touching.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module Alchemy - module ContentTouching - def self.included(base) - base.after_update(:touch_contents) - end - - private - - # If the model has a +contents+ association, - # it updates all their timestamps. - # - # CAUTION: Only use on bottom to top releations, - # e.g. +Alchemy::Picture+ or +Alchemy::Attachment+ - # not on top to bottom ones like +Alchemy::Element+. - # - def touch_contents - return unless respond_to?(:contents) - - contents.update_all(updated_at: Time.current) - end - end -end diff --git a/app/models/concerns/alchemy/touch_elements.rb b/app/models/concerns/alchemy/touch_elements.rb new file mode 100644 index 0000000000..53d07df594 --- /dev/null +++ b/app/models/concerns/alchemy/touch_elements.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Alchemy + # If the model has a +elements+ association, + # it updates all their timestamps after save. + # + # Should only be used on bottom to top relations, + # e.g. +Alchemy::Picture+ or +Alchemy::Attachment+ + # not on top to bottom ones like +Alchemy::Page+. + # + module TouchElements + def self.included(base) + base.after_save(:touch_elements) + end + + private + + def touch_elements + return unless respond_to?(:elements) + + elements.map(&:touch) + end + end +end diff --git a/app/serializers/alchemy/content_serializer.rb b/app/serializers/alchemy/content_serializer.rb index e9b39494ec..1adc3b6e98 100644 --- a/app/serializers/alchemy/content_serializer.rb +++ b/app/serializers/alchemy/content_serializer.rb @@ -6,9 +6,6 @@ class ContentSerializer < ActiveModel::Serializer :name, :ingredient, :element_id, - :position, - :created_at, - :updated_at, :settings has_one :essence, polymorphic: true diff --git a/app/serializers/alchemy/essence_boolean_serializer.rb b/app/serializers/alchemy/essence_boolean_serializer.rb index d1cc8d2571..6b790875df 100644 --- a/app/serializers/alchemy/essence_boolean_serializer.rb +++ b/app/serializers/alchemy/essence_boolean_serializer.rb @@ -2,9 +2,9 @@ module Alchemy class EssenceBooleanSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :value, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_date_serializer.rb b/app/serializers/alchemy/essence_date_serializer.rb index 889fa6e322..b738f6a4e7 100644 --- a/app/serializers/alchemy/essence_date_serializer.rb +++ b/app/serializers/alchemy/essence_date_serializer.rb @@ -2,9 +2,9 @@ module Alchemy class EssenceDateSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :date, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_file_serializer.rb b/app/serializers/alchemy/essence_file_serializer.rb index 9afc98fc3f..7ccb46ea7a 100644 --- a/app/serializers/alchemy/essence_file_serializer.rb +++ b/app/serializers/alchemy/essence_file_serializer.rb @@ -2,9 +2,11 @@ module Alchemy class EssenceFileSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :title, - :css_class + :css_class, + ) has_one :attachment end diff --git a/app/serializers/alchemy/essence_html_serializer.rb b/app/serializers/alchemy/essence_html_serializer.rb index afaafafc88..8f76946360 100644 --- a/app/serializers/alchemy/essence_html_serializer.rb +++ b/app/serializers/alchemy/essence_html_serializer.rb @@ -2,9 +2,9 @@ module Alchemy class EssenceHtmlSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :source, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_link_serializer.rb b/app/serializers/alchemy/essence_link_serializer.rb index b665693da1..29dcec97dd 100644 --- a/app/serializers/alchemy/essence_link_serializer.rb +++ b/app/serializers/alchemy/essence_link_serializer.rb @@ -2,12 +2,12 @@ module Alchemy class EssenceLinkSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :link, :link_title, :link_target, :link_class_name, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_picture_serializer.rb b/app/serializers/alchemy/essence_picture_serializer.rb index 3082b78b39..5ae7a34c22 100644 --- a/app/serializers/alchemy/essence_picture_serializer.rb +++ b/app/serializers/alchemy/essence_picture_serializer.rb @@ -2,15 +2,15 @@ module Alchemy class EssencePictureSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :picture_id, :caption, :title, :alt_tag, :css_class, :link, - :created_at, - :updated_at + ) has_one :picture @@ -21,7 +21,7 @@ def link url: object.link, css_class: object.link_class_name, title: object.link_title, - target: object.link_target + target: object.link_target, } end end diff --git a/app/serializers/alchemy/essence_richtext_serializer.rb b/app/serializers/alchemy/essence_richtext_serializer.rb index b519ce7c65..bd8291748f 100644 --- a/app/serializers/alchemy/essence_richtext_serializer.rb +++ b/app/serializers/alchemy/essence_richtext_serializer.rb @@ -2,10 +2,10 @@ module Alchemy class EssenceRichtextSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :body, :stripped_body, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_select_serializer.rb b/app/serializers/alchemy/essence_select_serializer.rb index 86483bfa6b..51d9a1a731 100644 --- a/app/serializers/alchemy/essence_select_serializer.rb +++ b/app/serializers/alchemy/essence_select_serializer.rb @@ -2,9 +2,9 @@ module Alchemy class EssenceSelectSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :value, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_text_serializer.rb b/app/serializers/alchemy/essence_text_serializer.rb index 3f1d9c6ec6..dd2326518c 100644 --- a/app/serializers/alchemy/essence_text_serializer.rb +++ b/app/serializers/alchemy/essence_text_serializer.rb @@ -2,11 +2,11 @@ module Alchemy class EssenceTextSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :body, :link, - :created_at, - :updated_at + ) def link return if object.link.blank? @@ -15,7 +15,7 @@ def link url: object.link, title: object.link_title, css_class: object.link_class_name, - target: object.link_target + target: object.link_target, } end end diff --git a/app/serializers/alchemy/legacy_element_serializer.rb b/app/serializers/alchemy/legacy_element_serializer.rb deleted file mode 100644 index e3e4e119e2..0000000000 --- a/app/serializers/alchemy/legacy_element_serializer.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Alchemy - class LegacyElementSerializer < ActiveModel::Serializer - attributes :id, - :name, - :position, - :page_id, - :tag_list, - :created_at, - :updated_at - - has_many :contents - end -end diff --git a/app/serializers/alchemy/node_serializer.rb b/app/serializers/alchemy/node_serializer.rb index a260fba3b9..95fc2f8bd1 100644 --- a/app/serializers/alchemy/node_serializer.rb +++ b/app/serializers/alchemy/node_serializer.rb @@ -8,5 +8,7 @@ class NodeSerializer < ActiveModel::Serializer :rgt, :url, :parent_id + + has_many :ancestors, record_type: :node, serializer: self end end diff --git a/app/serializers/alchemy/page_serializer.rb b/app/serializers/alchemy/page_serializer.rb index 776cda862e..1fb53f3b41 100644 --- a/app/serializers/alchemy/page_serializer.rb +++ b/app/serializers/alchemy/page_serializer.rb @@ -13,7 +13,8 @@ class PageSerializer < ActiveModel::Serializer :tag_list, :created_at, :updated_at, - :status + :status, + :url_path has_many :elements end diff --git a/app/serializers/alchemy/page_tree_serializer.rb b/app/serializers/alchemy/page_tree_serializer.rb index f6a36b6d29..a59b6d810c 100644 --- a/app/serializers/alchemy/page_tree_serializer.rb +++ b/app/serializers/alchemy/page_tree_serializer.rb @@ -3,7 +3,7 @@ module Alchemy class PageTreeSerializer < BaseSerializer def attributes - {'pages' => nil} + {"pages" => nil} end def pages @@ -53,15 +53,15 @@ def page_hash(page, has_children, level, folded) id: page.id, name: page.name, public: page.public?, - visible: page.visible?, restricted: page.restricted?, page_layout: page.page_layout, slug: page.slug, urlname: page.urlname, + url_path: page.url_path, level: level, - root: level == 1, - root_or_leaf: level == 1 || !has_children, - children: [] + root: page.depth == 1, + root_or_leaf: page.depth == 1 || !has_children, + children: [], } if opts[:elements] @@ -73,9 +73,9 @@ def page_hash(page, has_children, level, folded) definition_missing: page.definition.blank?, folded: folded, locked: page.locked?, - locked_notice: page.locked? ? Alchemy.t('This page is locked', name: page.locker_name) : nil, + locked_notice: page.locked? ? Alchemy.t("This page is locked", name: page.locker_name) : nil, permissions: page_permissions(page, opts[:ability]), - status_titles: page_status_titles(page) + status_titles: page_status_titles(page), }) else p_hash @@ -83,10 +83,10 @@ def page_hash(page, has_children, level, folded) end def page_elements(page) - if opts[:elements] == 'true' + if opts[:elements] == "true" page.elements else - page.elements.named(opts[:elements].split(',') || []) + page.elements.named(opts[:elements].split(",") || []) end end @@ -97,15 +97,14 @@ def page_permissions(page, ability) copy: ability.can?(:copy, page), destroy: ability.can?(:destroy, page), create: ability.can?(:create, Alchemy::Page), - edit_content: ability.can?(:edit_content, page) + edit_content: ability.can?(:edit_content, page), } end def page_status_titles(page) { public: page.status_title(:public), - visible: page.status_title(:visible), - restricted: page.status_title(:restricted) + restricted: page.status_title(:restricted), } end end diff --git a/app/views/alchemy/admin/layoutpages/index.html.erb b/app/views/alchemy/admin/layoutpages/index.html.erb index 43e7e1e0aa..d7dbe6dcea 100644 --- a/app/views/alchemy/admin/layoutpages/index.html.erb +++ b/app/views/alchemy/admin/layoutpages/index.html.erb @@ -4,7 +4,7 @@ <%= render 'alchemy/admin/partials/language_tree_select' %> <%= toolbar_button( icon: :plus, - url: alchemy.new_admin_page_path(parent_id: @layout_root.id, layoutpage: true), + url: alchemy.new_admin_page_path(language: @current_language, layoutpage: true), hotkey: 'alt+n', dialog_options: { title: Alchemy.t('Add global page'), @@ -30,6 +30,10 @@ <% end %> +<%= Alchemy.t(:language_does_not_exist) %>
@@ -37,7 +36,6 @@ <%= form.hidden_field :language_id, value: @language.id %> <%= form.hidden_field :language_code, value: @language.code %> <%= form.hidden_field :language_root, value: true %> - <%= form.hidden_field :parent_id, value: root.id %> <%= form.hidden_field :public, value: Alchemy::Language.all.size == 1 %> <%= form.submit Alchemy.t("create_tree_as_new_language", language: @language.name), autofocus: true %> <% end %> @@ -50,9 +48,3 @@ <%- end -%>Please run bin/rake db:seed
task.
<%= @page.layout_display_name %>
<%= "/#{@page.urlname}" %>