diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 000000000..aacf2e734 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,6 @@ +version: "2" +plugins: + rubocop: + enabled: true + channel: rubocop-1-56-3 + diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index db2472009..000000000 --- a/.codecov.yml +++ /dev/null @@ -1 +0,0 @@ -comment: off diff --git a/.flayignore b/.flayignore new file mode 100644 index 000000000..17bc5d29f --- /dev/null +++ b/.flayignore @@ -0,0 +1,4 @@ +spec/**/*.rb +features/step_definitions/*.rb +features/support/*.rb +lib/**/*.rake diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 501ce4df0..b82f589c4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,19 +1,31 @@ -**Checklist** +### [Pivotal Tracker Link][tracker] -- [ ] I have read the [Contribution & Best practices Guide](https://github.com/openSUSE/osem/blob/master/CONTRIBUTING.md). -- [ ] My branch is up-to-date with the upstream `master` branch. -- [ ] The tests pass locally with my changes. -- [ ] I have added tests that prove my fix is effective or that my feature works(if appropriate). -- [ ] I have added necessary documentation (if appropriate). + +[tracker]: https://www.pivotaltracker.com/story/show/your-story-id -**Short description of what this resolves/which [issues](https://github.com/openSUSE/osem/issues) does this fix?:** +## What this PR does: + - +This pull request fixes|implements (pick one...) ______. -- +### Include screenshots, videos, etc. -**Changes proposed in this pull request:** +#### Who authored this PR? + - -- +### How should this PR be tested? + +* Is there a deploy we can view? +* What do the specs/features test? +* Are there edge cases to watch out for? + +#### Are there any complications to deploying this? + + + +### Checklist: + +- [ ] Has this been deployed to a staging environment or reviewed by a customer? +- [ ] Tag someone for code review (either a coach / team member) +- [ ] I have renamed the branch to match PivotTracker's suggested one (necessary for BlueJay) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..de025eadd --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '39 5 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/next-rails.yml b/.github/workflows/next-rails.yml index 43f03260b..187eb2ed1 100644 --- a/.github/workflows/next-rails.yml +++ b/.github/workflows/next-rails.yml @@ -3,7 +3,7 @@ name: Next-rails on: pull_request: branches: - - master + - main workflow_dispatch: jobs: @@ -26,7 +26,7 @@ jobs: echo "BUNDLE_CACHE_PATH=vendor/cache.next" >> $GITHUB_ENV - uses: ruby/setup-ruby@v1 with: - ruby-version: 3.1.4 + ruby-version: 3.2.2 bundler-cache: true - name: Prepare spec run: | diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 7da756c6a..4c7af5acf 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -2,51 +2,85 @@ name: Specs on: push: - branches: - - master pull_request: - branches: - - master workflow_dispatch: jobs: linters: + continue-on-error: true runs-on: ubuntu-latest + env: + PRONTO_PULL_REQUEST_ID: ${{ github.event.pull_request.number }} + PRONTO_GITHUB_ACCESS_TOKEN: "${{ github.token }}" steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: - ruby-version: 3.1.4 + ruby-version: 3.2.2 bundler-cache: true + # - name: Run Pronto + # run: bundle exec pronto run + # run: pronto run -f github_combined_status github_pr_review -c origin/${{ github.base_ref }} - run: bundle exec rubocop - run: bundle exec haml-lint app/views spec: - needs: linters + continue-on-error: true runs-on: ubuntu-latest name: spec env: OSEM_DB_ADAPTER: sqlite3 RAILS_ENV: test + CCTR: ./cc-test-reporter + CCTR_ID: ${{ secrets.CC_TEST_REPORTER_ID }} strategy: matrix: - suite: [models, features, controllers, ability, leftovers] + suite: [models, features, controllers, ability, leftovers, cucumber] steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: - ruby-version: 3.1.4 + ruby-version: 3.2.2 bundler-cache: true + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: '16.x' + - run: sudo apt-get install xvfb + - name: Install JavaScript libraries via npm + run: npm install + - name: set up CodeClimate test-reporter + run: | + curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > $CCTR + chmod +x $CCTR + $CCTR before-build - name: Prepare spec run: | rm -f osem_test osem_development bundle exec rake db:setup --trace bundle exec bin/rails webdrivers:chromedriver:update bundle exec rake factory_bot:lint RAILS_ENV=test + # TODO: Not all suites need xvfb - name: spec/${{ matrix.suite }} - run: bundle exec rake spec:${{ matrix.suite }} - - name: coverage upload ${{ matrix.suite }} - uses: codacy/codacy-coverage-reporter-action@master - if: github.ref == 'refs/heads/master' + run: | + xvfb-run --auto-servernum bundle exec rake spec:${{ matrix.suite }} + $CCTR format-coverage --output coverage/codeclimate.${{ matrix.suite }}.json --input-type simplecov + # - name: coverage upload ${{ matrix.suite }} + # uses: codacy/codacy-coverage-reporter-action@v1 + # if: github.ref == 'refs/heads/master' && always() + # with: + # project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + # coverage-reports: coverage/coverage.xml + - name: Upload Capybara Failure Screenshots + uses: actions/upload-artifact@v3 + if: always() with: - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - coverage-reports: coverage/coverage.xml + name: capybara-screenshots + path: tmp/capybara/ + retention-days: 7 + + - name: Publish code coverage + run: | + export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" + $CCTR sum-coverage coverage/codeclimate.*.json + $CCTR upload-coverage --id "6d21ff1a59b134f3741779d50325f7bd5183cbe6b205051573d955705148960f" + $CCTR after-build --id "6d21ff1a59b134f3741779d50325f7bd5183cbe6b205051573d955705148960f" diff --git a/.gitignore b/.gitignore index 3d1ffb4d5..551ac3e65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,27 @@ +# Global, for Macs +*~ + +# Compiled Python files +*.pyc + +# Folder view configuration files +.DS_Store +Desktop.ini + +# Thumbnail cache files +._* +Thumbs.db + +# Files that might appear on external disks +.Spotlight-V100 +.Trashes + + +# Legacy from before Feb 2021 /db/test.sqlite3-journal config/application.rb config/config.yml +config/local_env.yml config/secrets.yml config/master.key config/credentials.yml.enc @@ -25,22 +46,88 @@ capybara-*.html **.orig rerun.txt pickle-email-*.html -*~ /public/assets /bundle /doc/app .vagrant/ -.env -.env.production -.env.development -.env.test -.env.local -.envrc +.env* docker-compose.override.yml -.DS_Store + .byebug_history .buildconfig osem_development osem_test + +# From GitHub, for Ruby +# https://github.com/github/gitignore/blob/master/Ruby.gitignore +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ + +# ensure the folder exists for puma +!tmp/pids/.keep + +# Ignore Byebug command history file. +.byebug_history + +## Specific to RubyMotion: +.dat* +.repl_history +build/ +*.bridgesupport +build-iPhoneOS/ +build-iPhoneSimulator/ + +## Specific to RubyMotion (use of CocoaPods): +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# vendor/Pods/ + +## Documentation cache and generated files: +/.yardoc/ +/_yardoc/ +/doc/ +/rdoc/ + +## Environment normalization: +/.bundle/ +/vendor/bundle +/lib/bundler/man/ + +# for a library or gem, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# Gemfile.lock +# .ruby-version +# .ruby-gemset + +# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: +.rvmrc + +# Used by RuboCop. Remote config files pulled in from inherit_from directive. +.rubocop-https?--* + +# Ignore Javascript package binaries +/node_modules + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml + +# common db things from heroku +latest.dump* .ackrc spec/support/deprecation_shitlist.json +.vs/* + +cc-test-reporter diff --git a/.haml-lint.yml b/.haml-lint.yml index 013b144fe..6a143078d 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -1 +1,5 @@ inherits_from: .haml-lint_todo.yml + +linters: + UnnecessaryStringOutput: + enabled: false diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 76ec92e79..7b80c2d84 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -12,15 +12,21 @@ linters: RuboCop: enabled: false + ConsecutiveComments: + exclude: + # These seems like a bug in haml-lint. + - "app/views/admin/conferences/show.html.haml" + - "app/views/proposals/index.html.haml" + # Offense count: 24 ConsecutiveSilentScripts: + max_consecutive: 3 exclude: - "app/views/admin/booths/_change_state_dropdown.html.haml" - "app/views/admin/schedules/_day_tab.html.haml" - "app/views/admin/schedules/_event.html.haml" - "app/views/admin/versions/_object_desc_and_link.html.haml" - "app/views/booths/index.html.haml" - - "app/views/schedules/_carousel.html.haml" # Offense count: 981 LineLength: @@ -111,26 +117,19 @@ linters: SpaceBeforeScript: enabled: false - # Offense count: 1 - ImplicitDiv: - exclude: - - "app/views/admin/registrations/index.html.haml" - # Offense count: 9 MultilinePipe: exclude: - "app/views/admin/schedules/_day_tab.html.haml" - "app/views/admin/schedules/_event.html.haml" - - "app/views/schedules/_carousel.html.haml" - "app/views/schedules/_event.html.haml" - "app/views/schedules/_schedule_item.html.haml" - # Offense count: 9 + # Offense count: 4 ClassAttributeWithStaticValue: exclude: - "app/views/admin/survey_questions/_form.html.haml" - "app/views/admin/surveys/_survey_question.html.haml" - - "app/views/conferences/_gallery.html.haml" - "app/views/layouts/_navigation.html.haml" - "app/views/schedules/events.html.haml" diff --git a/.rubocop.yml b/.rubocop.yml index e6c0a8538..53babd565 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -12,22 +12,86 @@ inherit_mode: - Exclude AllCops: + SuggestExtensions: false UseCache: true CacheRootDirectory: tmp/rubocop_cache_rails_dir MaxFilesInCache: 4000 + NewCops: enable Exclude: - db/schema.rb - NewCops: enable + - features/step_definitions/web_steps.rb + - features/support/selectors.rb + - lib/tasks/*.rake + - db/migrate/*.rb + - bin/* #################### Style ########################### +Style/CommentAnnotation: + Keywords: + - TODO-SNAPCON + - TODO + - OPTIMIZE + - HACK + - REVIEW + +Style/HashSyntax: + EnforcedShorthandSyntax: never + +Style/SymbolArray: + Enabled: false + Layout/HashAlignment: EnforcedHashRocketStyle: table EnforcedColonStyle: table +Layout/EndOfLine: + EnforcedStyle: lf + +Lint/AmbiguousRegexpLiteral: + Exclude: + - features/step_definitions/* + ##################### Metrics ################################## ##################### Rails ################################## +# TODO: Would be good to enable, but leads to spec failures. +Rails: + Enabled: false + Exclude: + - spec/**/*.rb + +# TODO: This would be good to enable at some point...specs need work. +Rails/Date: + Enabled: false + +Rails/FilePath: + Enabled: false + +Rails/I18nLocaleTexts: + Enabled: false + +# TODO-SNAPCON: Investigate if that makes sense to enable. +Rails/InverseOf: + Enabled: false + +Rails/HasAndBelongsToMany: + Enabled: false + +Rails/HasManyOrHasOneDependent: + Enabled: false + +Rails/OutputSafety: + Enabled: false + +Rails/RenderInline: + Enabled: false + +Rails/SkipsModelValidations: + Enabled: false + +Rails/UniqueValidationWithoutIndex: + Enabled: false ##################### RSpec ################################## @@ -35,4 +99,3 @@ RSpec/DescribeClass: Exclude: - "spec/views/**/*" - "spec/ability/*" - diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7e343ea1b..e32398240 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,10 @@ # This configuration was generated by # `rubocop --auto-gen-config` +<<<<<<< HEAD # on 2024-03-04 16:04:49 UTC using RuboCop version 1.61.0. +======= +# on 2024-02-13 04:01:02 UTC using RuboCop version 1.60.2. +>>>>>>> main # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -14,6 +18,7 @@ Bundler/OrderedGems: Exclude: - 'Gemfile' +<<<<<<< HEAD # Offense count: 161 # Configuration parameters: EnforcedStyle. # SupportedStyles: link_or_button, strict @@ -38,6 +43,9 @@ Capybara/CurrentPathExpectation: - 'spec/features/user_ability_spec.rb' # Offense count: 3 +======= +# Offense count: 1 +>>>>>>> main # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: have_no, not_to @@ -59,11 +67,12 @@ Capybara/RSpec/HaveSelector: - 'spec/features/track_organizer_ability_spec.rb' - 'spec/features/voting_spec.rb' -# Offense count: 82 +# Offense count: 98 # This cop supports safe autocorrection (--autocorrect). Capybara/SpecificFinders: Enabled: false +<<<<<<< HEAD # Offense count: 1 Capybara/SpecificMatcher: Exclude: @@ -447,6 +456,8 @@ Layout/TrailingEmptyLines: - 'lib/tasks/event_attatchments.rake' - 'lib/tasks/roles.rake' +======= +>>>>>>> main # Offense count: 13 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -460,25 +471,6 @@ Lint/AmbiguousBlockAssociation: - 'spec/controllers/schedules_controller_spec.rb' - 'spec/models/user_spec.rb' -# Offense count: 13 -# This cop supports safe autocorrection (--autocorrect). -Lint/AmbiguousOperatorPrecedence: - Exclude: - - 'app/controllers/application_controller.rb' - - 'app/helpers/conference_helper.rb' - - 'app/models/admin_ability.rb' - - 'app/models/commercial.rb' - - 'app/models/conference.rb' - - 'app/models/track.rb' - - 'app/pdfs/ticket_pdf.rb' - -# Offense count: 1 -# Configuration parameters: AllowedMethods. -# AllowedMethods: enums -Lint/ConstantDefinitionInBlock: - Exclude: - - 'lib/tasks/data.rake' - # Offense count: 5 # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. Lint/DuplicateBranch: @@ -486,18 +478,13 @@ Lint/DuplicateBranch: - 'app/helpers/format_helper.rb' - 'app/uploaders/picture_uploader.rb' -# Offense count: 2 -Lint/DuplicateHashKey: - Exclude: - - 'db/migrate/20140801164901_move_conference_media_to_commercial.rb' - - 'db/migrate/20140801170430_move_event_media_to_commercial.rb' - # Offense count: 4 Lint/IneffectiveAccessModifier: Exclude: - 'app/models/commercial.rb' - 'app/models/conference.rb' +<<<<<<< HEAD # Offense count: 4 # This cop supports unsafe autocorrection (--autocorrect-all). Lint/NonAtomicFileOperation: @@ -532,45 +519,43 @@ Lint/UriRegexp: - 'app/models/contact.rb' # Offense count: 127 +======= +# Offense count: 111 +>>>>>>> main # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 72 -# Offense count: 28 +# Offense count: 19 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine Metrics/BlockLength: - Max: 211 + Max: 227 -# Offense count: 1 -# Configuration parameters: CountBlocks. -Metrics/BlockNesting: - Max: 4 - -# Offense count: 14 +# Offense count: 13 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 652 + Max: 270 # Offense count: 26 # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/CyclomaticComplexity: - Max: 16 + Max: 15 -# Offense count: 151 +# Offense count: 127 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Max: 56 + Max: 55 -# Offense count: 5 +# Offense count: 4 # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: - Max: 174 + Max: 168 -# Offense count: 23 +# Offense count: 25 # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: - Max: 19 + Max: 17 # Offense count: 13 Naming/AccessorMethodName: @@ -604,14 +589,6 @@ Naming/PredicateName: - 'app/models/comment.rb' - 'app/models/contact.rb' -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: PreferredName. -Naming/RescuedExceptionsVariableName: - Exclude: - - 'app/models/commercial.rb' - - 'app/models/payment.rb' - # Offense count: 9 # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, normalcase, non_integer @@ -623,6 +600,12 @@ Naming/VariableNumber: - 'spec/models/payment_spec.rb' - 'spec/models/ticket_purchase_spec.rb' +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Performance/Casecmp: + Exclude: + - 'config/environments/production.rb' + # Offense count: 1 # Configuration parameters: MinSize. Performance/CollectionLiteralInLoop: @@ -636,12 +619,11 @@ Performance/InefficientHashSearch: - 'app/controllers/admin/versions_controller.rb' - 'app/helpers/versions_helper.rb' -# Offense count: 2 +# Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). Performance/MapCompact: Exclude: - 'app/datatables/registration_datatable.rb' - - 'lib/tasks/events_registrations.rake' # Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). @@ -649,11 +631,12 @@ Performance/StringInclude: Exclude: - 'app/models/commercial.rb' -# Offense count: 28 +# Offense count: 32 RSpec/AnyInstance: Exclude: - 'spec/controllers/admin/rooms_controller_spec.rb' - 'spec/controllers/admin/sponsorship_levels_controller_spec.rb' + - 'spec/controllers/admin/tickets_controller_spec.rb' - 'spec/controllers/admin/tracks_controller_spec.rb' - 'spec/controllers/admin/users_controller_spec.rb' - 'spec/controllers/conference_registration_controller_spec.rb' @@ -667,119 +650,32 @@ RSpec/BeEmpty: - 'spec/controllers/conference_registration_controller_spec.rb' - 'spec/models/conference_spec.rb' -# Offense count: 161 +# Offense count: 12 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnabledMethods. RSpec/Capybara/FeatureMethods: - Enabled: false + Exclude: + - 'spec/features/proposals_spec.rb' + - 'spec/features/user_spec.rb' + - 'spec/features/voting_spec.rb' -# Offense count: 318 +# Offense count: 338 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: Enabled: false -# Offense count: 73 +# Offense count: 141 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle. # SupportedStyles: described_class, explicit RSpec/DescribedClass: - Exclude: - - 'spec/mailers/mailbot_spec.rb' - - 'spec/models/cfp_spec.rb' - - 'spec/models/conference_spec.rb' - - 'spec/models/role_spec.rb' - - 'spec/models/sponsorship_level_spec.rb' - - 'spec/models/survey_question_spec.rb' - - 'spec/models/ticket_purchase_spec.rb' - - 'spec/models/ticket_spec.rb' - - 'spec/models/user_spec.rb' - - 'spec/serializers/conference_serializer_spec.rb' - - 'spec/serializers/event_serializer_spec.rb' - - 'spec/serializers/room_serializer_spec.rb' - - 'spec/serializers/speaker_serializer_spec.rb' - - 'spec/serializers/track_serializer_spec.rb' - -# Offense count: 11 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowConsecutiveOneLiners. -RSpec/EmptyLineAfterExample: - Exclude: - - 'spec/ability/ability_spec.rb' - - 'spec/controllers/admin/comments_controller_spec.rb' - - 'spec/controllers/admin/registration_periods_controller_spec.rb' - - 'spec/controllers/admin/users_controller_spec.rb' - - 'spec/helpers/events_helper_spec.rb' - - 'spec/models/event_spec.rb' - - 'spec/models/survey_spec.rb' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -RSpec/EmptyLineAfterExampleGroup: - Exclude: - - 'spec/controllers/admin/users_controller_spec.rb' - -# Offense count: 11 -# This cop supports safe autocorrection (--autocorrect). -RSpec/EmptyLineAfterFinalLet: - Exclude: - - 'spec/controllers/admin/event_schedules_controller_spec.rb' - - 'spec/controllers/admin/users_controller_spec.rb' - - 'spec/controllers/application_controller_spec.rb' - - 'spec/features/conference_spec.rb' - - 'spec/models/conference_spec.rb' - - 'spec/models/payment_spec.rb' - - 'spec/models/ticket_spec.rb' - -# Offense count: 16 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowConsecutiveOneLiners. -RSpec/EmptyLineAfterHook: - Exclude: - - 'spec/controllers/admin/booths_controller_spec.rb' - - 'spec/controllers/admin/comments_controller_spec.rb' - - 'spec/controllers/admin/organizations_controller_spec.rb' - - 'spec/controllers/admin/ticket_scannings_controller_spec.rb' - - 'spec/controllers/admin/users_controller_spec.rb' - - 'spec/features/organization_spec.rb' - - 'spec/features/roles_spec.rb' - - 'spec/models/conference_spec.rb' - - 'spec/models/payment_spec.rb' - - 'spec/models/program_spec.rb' - -# Offense count: 8 -# This cop supports safe autocorrection (--autocorrect). -RSpec/EmptyLineAfterSubject: - Exclude: - - 'spec/ability/ability_spec.rb' - - 'spec/models/booth_spec.rb' - - 'spec/models/cfp_spec.rb' - - 'spec/models/event_spec.rb' - - 'spec/models/program_spec.rb' - - 'spec/models/registration_spec.rb' - - 'spec/models/survey_spec.rb' - - 'spec/models/track_spec.rb' + Enabled: false -# Offense count: 215 +# Offense count: 248 # Configuration parameters: CountAsOne. RSpec/ExampleLength: - Max: 187 - -# Offense count: 15 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: CustomTransform, IgnoredWords, DisallowedExamples. -# DisallowedExamples: works -RSpec/ExampleWording: - Exclude: - - 'spec/controllers/admin/organizations_controller_spec.rb' - - 'spec/controllers/admin/registration_periods_controller_spec.rb' - - 'spec/helpers/application_helper_spec.rb' - - 'spec/helpers/events_helper_spec.rb' - - 'spec/helpers/format_helper_spec.rb' - - 'spec/models/conference_spec.rb' - - 'spec/models/event_spec.rb' - - 'spec/models/ticket_purchase_spec.rb' - - 'spec/models/ticket_spec.rb' + Max: 222 # Offense count: 37 # This cop supports unsafe autocorrection (--autocorrect-all). @@ -817,18 +713,20 @@ RSpec/FilePath: - 'spec/models/comment_spec.rb' - 'spec/models/openid.rb' -# Offense count: 172 +# Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: implicit, each, example RSpec/HookArgument: - Enabled: false + Exclude: + - 'spec/features/voting_spec.rb' # Offense count: 2 RSpec/IdenticalEqualityAssertion: Exclude: - 'spec/controllers/admin/conferences_controller_spec.rb' +<<<<<<< HEAD # Offense count: 140 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -869,17 +767,27 @@ RSpec/ImplicitSubject: # Offense count: 19 # Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns. +======= +# Offense count: 53 +# Configuration parameters: Max. +>>>>>>> main RSpec/IndexedLet: Exclude: - 'spec/controllers/admin/reports_controller_spec.rb' - 'spec/controllers/admin/roles_controller_spec.rb' + - 'spec/controllers/schedules_controller_spec.rb' + - 'spec/features/proposals_spec.rb' + - 'spec/features/splashpage_spec.rb' - 'spec/features/voting_spec.rb' + - 'spec/helpers/conference_helper_spec.rb' - 'spec/models/conference_spec.rb' + - 'spec/models/event_schedule_spec.rb' - 'spec/models/ticket_purchase_spec.rb' - 'spec/models/ticket_spec.rb' - 'spec/models/user_spec.rb' + - 'spec/services/full_calendar_formatter_spec.rb' -# Offense count: 321 +# Offense count: 344 # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Exclude: @@ -899,15 +807,7 @@ RSpec/InstanceVariable: - 'spec/models/track_spec.rb' - 'spec/models/user_spec.rb' -# Offense count: 4 -# This cop supports safe autocorrection (--autocorrect). -RSpec/LeadingSubject: - Exclude: - - 'spec/ability/ability_spec.rb' - - 'spec/models/conference_spec.rb' - - 'spec/models/ticket_spec.rb' - -# Offense count: 61 +# Offense count: 64 RSpec/LetSetup: Enabled: false @@ -930,65 +830,66 @@ RSpec/MultipleDescribes: Exclude: - 'spec/models/conference_spec.rb' -# Offense count: 270 +# Offense count: 298 RSpec/MultipleExpectations: Max: 97 -# Offense count: 249 +# Offense count: 272 # Configuration parameters: AllowSubject. RSpec/MultipleMemoizedHelpers: Max: 32 -# Offense count: 396 +# Offense count: 438 # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. # SupportedStyles: always, named_only RSpec/NamedSubject: Exclude: + - 'spec/ability/ability_spec.rb' + - 'spec/models/booth_spec.rb' - 'spec/models/conference_spec.rb' + - 'spec/models/event_type_spec.rb' + - 'spec/models/organization_spec.rb' + - 'spec/models/program_spec.rb' + - 'spec/models/registration_period_spec.rb' - 'spec/models/registration_spec.rb' - 'spec/models/room_spec.rb' + - 'spec/models/sponsor_spec.rb' + - 'spec/models/sponsorship_level_spec.rb' + - 'spec/models/ticket_purchase_spec.rb' + - 'spec/models/ticket_spec.rb' - 'spec/models/track_spec.rb' -# Offense count: 208 +# Offense count: 219 # Configuration parameters: AllowedGroups. RSpec/NestedGroups: Max: 7 -# Offense count: 3 +# Offense count: 4 # Configuration parameters: AllowedPatterns. # AllowedPatterns: ^expect_, ^assert_ RSpec/NoExpectationExample: Exclude: - 'spec/controllers/admin/conferences_controller_spec.rb' - 'spec/controllers/admin/registration_periods_controller_spec.rb' + - 'spec/features/proposals_spec.rb' - 'spec/features/voting_spec.rb' -# Offense count: 83 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: not_to, to_not -RSpec/NotToNot: - Enabled: false - # Offense count: 1 RSpec/OverwritingSetup: Exclude: - 'spec/controllers/admin/booths_controller_spec.rb' -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -RSpec/Rails/AvoidSetupHook: +# Offense count: 11 +RSpec/PendingWithoutReason: Exclude: + - 'spec/ability/ability_spec.rb' + - 'spec/controllers/admin/conferences_controller_spec.rb' + - 'spec/datatables/user_datatable_spec.rb' + - 'spec/features/proposals_spec.rb' - 'spec/features/versions_spec.rb' - - 'spec/helpers/events_helper_spec.rb' - -# Offense count: 2 -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/Rails/HaveHttpStatus: - Exclude: - - 'spec/controllers/admin/event_schedules_controller_spec.rb' + - 'spec/models/user_spec.rb' -# Offense count: 11 +# Offense count: 12 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Inferences. RSpec/Rails/InferredSpecType: @@ -997,6 +898,7 @@ RSpec/Rails/InferredSpecType: - 'spec/controllers/admin/programs_controller_spec.rb' - 'spec/controllers/application_controller_spec.rb' - 'spec/controllers/conference_registration_controller_spec.rb' + - 'spec/features/omniauth_spec.rb' - 'spec/helpers/application_helper_spec.rb' - 'spec/helpers/conference_helper_spec.rb' - 'spec/helpers/date_time_helper_spec.rb' @@ -1026,21 +928,6 @@ RSpec/RepeatedExampleGroupBody: Exclude: - 'spec/models/conference_spec.rb' -# Offense count: 10 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: and_return, block -RSpec/ReturnFromStub: - Exclude: - - 'spec/helpers/events_helper_spec.rb' - -# Offense count: 19 -# This cop supports safe autocorrection (--autocorrect). -RSpec/ScatteredLet: - Exclude: - - 'spec/ability/ability_spec.rb' - - 'spec/models/payment_spec.rb' - # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). RSpec/ScatteredSetup: @@ -1077,473 +964,94 @@ RSpec/VoidExpect: Exclude: - 'spec/models/conference_spec.rb' -# Offense count: 24 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: ExpectedOrder, Include. -# ExpectedOrder: index, show, new, edit, create, update, destroy -# Include: app/controllers/**/*.rb -Rails/ActionOrder: - Enabled: false - -# Offense count: 4 -# This cop supports unsafe autocorrection (--autocorrect-all). -Rails/ActiveRecordAliases: +# Offense count: 3 +Security/Open: Exclude: - - 'db/migrate/20141104131625_generate_username.rb' - - 'db/migrate/20141117214230_move_banner_description_to_conference.rb' - - 'db/migrate/20141118153918_change_venue_conference_association.rb' - - 'db/migrate/20141118162030_change_lodging_association_to_conference.rb' + - 'app/pdfs/ticket_pdf.rb' -# Offense count: 3 +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/ActiveRecordCallbacksOrder: +# Configuration parameters: EnforcedStyle. +# SupportedStyles: separated, grouped +Style/AccessorGrouping: Exclude: - - 'app/models/event.rb' - - 'app/models/track.rb' - - 'app/models/venue.rb' + - 'app/models/payment.rb' -# Offense count: 1 +# Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). -Rails/ApplicationController: +Style/ArrayIntersect: Exclude: - - 'app/controllers/api/base_controller.rb' + - 'app/helpers/application_helper.rb' + - 'app/models/admin_ability.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). -Rails/ApplicationMailer: +# Configuration parameters: MinBranchesCount. +Style/CaseLikeIf: Exclude: - - 'app/mailers/mailbot.rb' + - 'app/views/admin/events/events.xlsx.axlsx' -# Offense count: 129 -# This cop supports unsafe autocorrection (--autocorrect-all). -Rails/ApplicationRecord: - Enabled: false +<<<<<<< HEAD +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: is_a?, kind_of? +Style/ClassCheck: + Exclude: + - 'app/models/email_settings.rb' -# Offense count: 10 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: NilOrEmpty, NotPresent, UnlessPresent. -Rails/Blank: +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Style/ColonMethodCall: Exclude: - - 'app/controllers/conferences_controller.rb' - - 'app/controllers/users/omniauth_callbacks_controller.rb' - - 'app/models/program.rb' - - 'app/models/user.rb' - - 'spec/factories/event_schedule.rb' + - 'app/models/commercial.rb' + - 'app/models/contact.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). -Rails/CompactBlank: +Style/CombinableLoops: Exclude: - - 'app/controllers/surveys_controller.rb' + - 'db/migrate/20140820093735_migrating_supporter_registrations_to_ticket_users.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). -Rails/ContentTag: +# Configuration parameters: Keywords, RequireColon. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE +Style/CommentAnnotation: Exclude: - - 'app/helpers/application_helper.rb' + - 'spec/controllers/admin/versions_controller_spec.rb' -# Offense count: 13 -# Configuration parameters: Include. -# Include: db/migrate/*.rb -Rails/CreateTableWithTimestamps: - Exclude: - - 'db/migrate/20121223115117_create_rooms_table.rb' - - 'db/migrate/20121223120413_create_event_types.rb' - - 'db/migrate/20130202130737_create_supporter_level_table.rb' - - 'db/migrate/20130202130923_create_table_supporter_registrations.rb' - - 'db/migrate/20130216070725_create_social_events_table.rb' - - 'db/migrate/20131228214532_create_vchoices.rb' - - 'db/migrate/20140109191145_create_qanswers.rb' - - 'db/migrate/20140623100942_create_visits.rb' - - 'db/migrate/20140623101032_create_ahoy_events.rb' - - 'db/migrate/20160309182642_remove_social_events_table.rb' - - 'db/migrate/20160628093634_create_survey_questions.rb' - - 'db/migrate/20170129075434_create_resources_table.rb' - - 'db/migrate/20170529215453_create_organizations.rb' +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'app/helpers/format_helper.rb' + - 'db/migrate/20140610165551_migrate_data_person_to_user.rb' + - 'db/migrate/20140820124117_undo_wrong_migration20140801080705_add_users_to_events.rb' +# Offense count: 518 +======= # Offense count: 103 -# Configuration parameters: EnforcedStyle, AllowToTime. -# SupportedStyles: strict, flexible -Rails/Date: +>>>>>>> main +# Configuration parameters: AllowedConstants. +Style/Documentation: Enabled: false -# Offense count: 3 +<<<<<<< HEAD +# Offense count: 2 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforceForPrefixed. -Rails/Delegate: +Style/EmptyCaseCondition: Exclude: - - 'app/models/event.rb' - - 'app/models/room.rb' - - 'app/models/track.rb' + - 'app/helpers/format_helper.rb' + - 'app/helpers/versions_helper.rb' -# Offense count: 4 +# Offense count: 1 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Severity. -Rails/DuplicateAssociation: +Style/EmptyLiteral: Exclude: - - 'app/models/program.rb' - - 'app/models/user.rb' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -Rails/DurationArithmetic: - Exclude: - - 'spec/models/program_spec.rb' - - 'spec/models/user_spec.rb' - -# Offense count: 3 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Whitelist, AllowedMethods, AllowedReceivers. -# Whitelist: find_by_sql, find_by_token_for -# AllowedMethods: find_by_sql, find_by_token_for -# AllowedReceivers: Gem::Specification, page -Rails/DynamicFindBy: - Exclude: - - 'app/controllers/admin/events_controller.rb' - - 'db/migrate/20140701123203_add_events_per_week_to_conference.rb' - -# Offense count: 5 -# This cop supports safe autocorrection (--autocorrect). -Rails/EagerEvaluationLogMessage: - Exclude: - - 'app/controllers/admin/events_controller.rb' - - 'app/controllers/application_controller.rb' - - 'app/controllers/proposals_controller.rb' - - 'app/models/event.rb' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/EnumHash: - Exclude: - - 'app/models/conference.rb' - - 'app/models/survey.rb' - - 'app/models/survey_question.rb' - -# Offense count: 7 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: slashes, arguments -Rails/FilePath: - Exclude: - - 'app/pdfs/ticket_pdf.rb' - - 'config/initializers/carrierwave.rb' - - 'lib/tasks/migrate_config.rake' - - 'spec/features/lodgings_spec.rb' - - 'spec/features/sponsor_spec.rb' - - 'spec/support/deprecation_shitlist.rb' - -# Offense count: 6 -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/HasAndBelongsToMany: - Exclude: - - 'app/models/conference.rb' - - 'app/models/qanswer.rb' - - 'app/models/question.rb' - - 'app/models/registration.rb' - - 'app/models/vchoice.rb' - -# Offense count: 24 -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/HasManyOrHasOneDependent: - Enabled: false - -# Offense count: 5 -# Configuration parameters: Include. -# Include: app/helpers/**/*.rb -Rails/HelperInstanceVariable: - Exclude: - - 'app/helpers/application_helper.rb' - - 'app/helpers/events_helper.rb' - - 'app/helpers/format_helper.rb' - -# Offense count: 8 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: numeric, symbolic -Rails/HttpStatus: - Exclude: - - 'app/controllers/admin/commercials_controller.rb' - - 'app/controllers/admin/event_schedules_controller.rb' - - 'app/controllers/admin/programs_controller.rb' - - 'app/controllers/admin/tracks_controller.rb' - - 'app/controllers/admin/venue_commercials_controller.rb' - - 'app/controllers/commercials_controller.rb' - -# Offense count: 100 -Rails/I18nLocaleTexts: - Enabled: false - -# Offense count: 7 -# Configuration parameters: IgnoreScopes, Include. -# Include: app/models/**/*.rb -Rails/InverseOf: - Exclude: - - 'app/models/booth.rb' - - 'app/models/conference.rb' - - 'app/models/event.rb' - - 'app/models/user.rb' - -# Offense count: 1 -# Configuration parameters: Include. -# Include: app/controllers/**/*.rb, app/mailers/**/*.rb -Rails/LexicallyScopedActionFilter: - Exclude: - - 'app/controllers/registrations_controller.rb' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Rails/LinkToBlank: - Exclude: - - 'app/helpers/format_helper.rb' - -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Include. -# Include: app/mailers/**/*.rb -Rails/MailerName: - Exclude: - - 'app/mailers/mailbot.rb' - -# Offense count: 3 -Rails/OutputSafety: - Exclude: - - 'app/helpers/events_helper.rb' - - 'app/models/commercial.rb' - -# Offense count: 6 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: conservative, aggressive -Rails/PluckInWhere: - Exclude: - - 'app/models/ability.rb' - - 'app/models/admin_ability.rb' - -# Offense count: 16 -# This cop supports safe autocorrection (--autocorrect). -Rails/PluralizationGrammar: - Exclude: - - 'spec/models/conference_spec.rb' - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -Rails/Presence: - Exclude: - - 'app/controllers/schedules_controller.rb' - - 'app/models/user.rb' - -# Offense count: 15 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: NotNilAndNotEmpty, NotBlank, UnlessBlank. -Rails/Present: - Exclude: - - 'app/models/cfp.rb' - - 'app/models/email_settings.rb' - - 'app/models/event.rb' - - 'app/models/program.rb' - - 'app/models/venue.rb' - -# Offense count: 6 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Include. -# Include: **/Rakefile, **/*.rake -Rails/RakeEnvironment: - Exclude: - - 'lib/tasks/dump_db.rake' - - 'lib/tasks/spec.rake' - -# Offense count: 21 -# This cop supports unsafe autocorrection (--autocorrect-all). -Rails/RedundantPresenceValidationOnBelongsTo: - Enabled: false - -# Offense count: 2 -Rails/RenderInline: - Exclude: - - 'app/controllers/conferences_controller.rb' - - 'app/controllers/schedules_controller.rb' - -# Offense count: 10 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Include. -# Include: spec/controllers/**/*.rb, spec/requests/**/*.rb, test/controllers/**/*.rb, test/integration/**/*.rb -Rails/ResponseParsedBody: - Exclude: - - 'spec/controllers/api/v1/conferences_controller_spec.rb' - - 'spec/controllers/api/v1/events_controller_spec.rb' - - 'spec/controllers/api/v1/rooms_controller_spec.rb' - - 'spec/controllers/api/v1/speakers_controller_spec.rb' - - 'spec/controllers/api/v1/tracks_controller_spec.rb' - -# Offense count: 4 -# Configuration parameters: Include. -# Include: db/**/*.rb -Rails/ReversibleMigration: - Exclude: - - 'db/migrate/20170108053041_add_default_to_revision_in_conference.rb' - - 'db/migrate/20170715131706_make_track_state_not_null_and_add_default_value.rb' - - 'db/migrate/20170720134353_make_track_cfp_active_not_null.rb' - - 'db/migrate/20171118113113_change_visit_id_type_of_ahoy_events_to_integer.rb' - -# Offense count: 48 -# Configuration parameters: ForbiddenMethods, AllowedMethods. -# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all -Rails/SkipsModelValidations: - Enabled: false - -# Offense count: 77 -# Configuration parameters: Include. -# Include: db/**/*.rb -Rails/ThreeStateBooleanColumn: - Enabled: false - -# Offense count: 40 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: strict, flexible -Rails/TimeZone: - Exclude: - - 'app/models/comment.rb' - - 'app/models/conference.rb' - - 'config/environments/test.rb' - - 'db/migrate/20180226032958_add_created_at_and_updated_at_to_event_types.rb' - - 'db/migrate/20180313012253_add_timestamps_to_tickets.rb' - - 'lib/tasks/dump_db.rake' - - 'spec/controllers/admin/comments_controller_spec.rb' - - 'spec/factories/users.rb' - - 'spec/models/conference_spec.rb' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Severity. -Rails/TopLevelHashWithIndifferentAccess: - Exclude: - - 'db/migrate/20140701123203_add_events_per_week_to_conference.rb' - -# Offense count: 13 -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/UniqueValidationWithoutIndex: - Exclude: - - 'app/models/booth.rb' - - 'app/models/cfp.rb' - - 'app/models/commercial.rb' - - 'app/models/conference.rb' - - 'app/models/events_registration.rb' - - 'app/models/organization.rb' - - 'app/models/registration.rb' - - 'app/models/role.rb' - - 'app/models/subscription.rb' - - 'app/models/survey_reply.rb' - - 'app/models/survey_submission.rb' - - 'app/models/track.rb' - - 'app/models/vote.rb' - -# Offense count: 17 -# This cop supports unsafe autocorrection (--autocorrect-all). -Rails/WhereEquals: - Exclude: - - 'app/controllers/admin/registrations_controller.rb' - - 'app/models/admin_ability.rb' - - 'app/models/conference.rb' - - 'app/models/event_schedule.rb' - - 'app/models/program.rb' - - 'app/models/user.rb' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Rails/WhereNot: - Exclude: - - 'db/migrate/20140820093735_migrating_supporter_registrations_to_ticket_users.rb' - -# Offense count: 3 -Security/Open: - Exclude: - - 'app/pdfs/ticket_pdf.rb' - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: separated, grouped -Style/AccessorGrouping: - Exclude: - - 'app/models/payment.rb' - -# Offense count: 3 -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/ArrayIntersect: - Exclude: - - 'app/helpers/application_helper.rb' - - 'app/models/admin_ability.rb' - -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: MinBranchesCount. -Style/CaseLikeIf: - Exclude: - - 'app/views/admin/events/events.xlsx.axlsx' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: is_a?, kind_of? -Style/ClassCheck: - Exclude: - - 'app/models/email_settings.rb' - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -Style/ColonMethodCall: - Exclude: - - 'app/models/commercial.rb' - - 'app/models/contact.rb' - -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/CombinableLoops: - Exclude: - - 'db/migrate/20140820093735_migrating_supporter_registrations_to_ticket_users.rb' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Keywords, RequireColon. -# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE -Style/CommentAnnotation: - Exclude: - - 'spec/controllers/admin/versions_controller_spec.rb' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. -# SupportedStyles: assign_to_condition, assign_inside_condition -Style/ConditionalAssignment: - Exclude: - - 'app/helpers/format_helper.rb' - - 'db/migrate/20140610165551_migrate_data_person_to_user.rb' - - 'db/migrate/20140820124117_undo_wrong_migration20140801080705_add_users_to_events.rb' - -# Offense count: 518 -# Configuration parameters: AllowedConstants. -Style/Documentation: - Enabled: false - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -Style/EmptyCaseCondition: - Exclude: - - 'app/helpers/format_helper.rb' - - 'app/helpers/versions_helper.rb' - -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Style/EmptyLiteral: - Exclude: - - 'spec/models/conference_spec.rb' + - 'spec/models/conference_spec.rb' # Offense count: 7 # This cop supports safe autocorrection (--autocorrect). @@ -1573,6 +1081,9 @@ Style/ExpandPathArguments: - 'spec/spec_helper.rb' # Offense count: 36 +======= +# Offense count: 40 +>>>>>>> main # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never @@ -1585,12 +1096,13 @@ Style/GlobalStdStream: Exclude: - 'config/environments/production.rb' -# Offense count: 28 +# Offense count: 36 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Enabled: false +<<<<<<< HEAD # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. @@ -1609,20 +1121,14 @@ Style/HashConversion: - 'app/models/conference.rb' - 'spec/factories/event_users.rb' +======= +>>>>>>> main # Offense count: 1 # Configuration parameters: MinBranchesCount. Style/HashLikeCase: Exclude: - 'app/helpers/versions_helper.rb' -# Offense count: 367 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. -# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -# SupportedShorthandSyntax: always, never, either, consistent -Style/HashSyntax: - Enabled: false - # Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). Style/HashTransformValues: @@ -1630,7 +1136,14 @@ Style/HashTransformValues: - 'app/controllers/admin/comments_controller.rb' - 'app/helpers/chart_helper.rb' -# Offense count: 58 +# Offense count: 4 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/IdenticalConditionalBranches: + Exclude: + - 'app/controllers/admin/booths_controller.rb' + - 'app/controllers/admin/events_controller.rb' + +# Offense count: 32 # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Enabled: false @@ -1642,21 +1155,13 @@ Style/LineEndConcatenation: - 'spec/features/conference_spec.rb' - 'spec/features/registration_periods_spec.rb' -# Offense count: 1 +# Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). Style/MapToHash: Exclude: - 'app/controllers/admin/comments_controller.rb' - -# Offense count: 9 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline -Style/MethodDefParentheses: - Exclude: + - 'app/helpers/chart_helper.rb' - 'app/models/conference.rb' - - 'app/models/user.rb' - - 'lib/tasks/demo_data_for_development.rake' # Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). @@ -1670,62 +1175,20 @@ Style/MixinUsage: Exclude: - 'spec/spec_helper.rb' -# Offense count: 7 -# This cop supports safe autocorrection (--autocorrect). -Style/MultilineIfModifier: +# Offense count: 1 +Style/MultilineBlockChain: Exclude: - - 'app/controllers/conferences_controller.rb' - - 'app/controllers/schedules_controller.rb' - - 'app/models/cfp.rb' - - 'app/models/event.rb' - - 'app/models/registration_period.rb' + - 'app/controllers/admin/comments_controller.rb' -# Offense count: 2 +# Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict Style/MutableConstant: Exclude: - 'app/models/event_user.rb' - - 'lib/tasks/migrate_config.rake' - -# Offense count: 4 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods. -# AllowedMethods: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with -Style/NestedParenthesizedCalls: - Exclude: - - 'spec/features/conference_spec.rb' - - 'spec/models/conference_spec.rb' - -# Offense count: 27 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinBodyLength. -# SupportedStyles: skip_modifier_ifs, always -Style/Next: - Enabled: false - -# Offense count: 115 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedOctalStyle. -# SupportedOctalStyles: zero_with_o, zero_only -Style/NumericLiteralPrefix: - Exclude: - - 'config/environments/test.rb' - - 'spec/controllers/admin/conferences_controller_spec.rb' - - 'spec/helpers/date_time_helper_spec.rb' - - 'spec/models/conference_spec.rb' - - 'spec/models/email_settings_spec.rb' - - 'spec/serializers/conference_serializer_spec.rb' - - 'spec/serializers/event_serializer_spec.rb' - -# Offense count: 7 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Strict, AllowedNumbers, AllowedPatterns. -Style/NumericLiterals: - MinDigits: 15 -# Offense count: 36 +# Offense count: 32 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison @@ -1734,7 +1197,6 @@ Style/NumericPredicate: - 'app/controllers/admin/conferences_controller.rb' - 'app/helpers/application_helper.rb' - 'app/helpers/date_time_helper.rb' - - 'app/helpers/format_helper.rb' - 'app/models/cfp.rb' - 'app/models/conference.rb' - 'app/models/event.rb' @@ -1742,7 +1204,6 @@ Style/NumericPredicate: - 'app/models/registration.rb' - 'app/models/ticket_purchase.rb' - 'app/models/user.rb' - - 'lib/tasks/events_registrations.rake' # Offense count: 3 Style/OptionalArguments: @@ -1758,6 +1219,7 @@ Style/OptionalBooleanParameter: - 'app/helpers/format_helper.rb' - 'app/models/event.rb' +<<<<<<< HEAD # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Style/OrAssignment: @@ -1802,31 +1264,15 @@ Style/PreferredHashMethods: # Offense count: 6 # This cop supports unsafe autocorrection (--autocorrect-all). +======= +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +>>>>>>> main # Configuration parameters: AllowedCompactTypes. # SupportedStyles: compact, exploded Style/RaiseArgs: EnforcedStyle: compact -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -Style/RandomWithOffset: - Exclude: - - 'spec/factories/sponsors.rb' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantAssignment: - Exclude: - - 'app/helpers/application_helper.rb' - - 'app/helpers/format_helper.rb' - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantCondition: - Exclude: - - 'app/helpers/versions_helper.rb' - - 'app/models/ticket.rb' - # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). Style/RedundantConstantBase: @@ -1841,6 +1287,7 @@ Style/RedundantFetchBlock: Exclude: - 'config/puma.rb' +<<<<<<< HEAD # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). Style/RedundantFilterChain: @@ -1870,90 +1317,21 @@ Style/RedundantReturn: - 'app/controllers/admin/events_controller.rb' - 'app/controllers/admin/organizations_controller.rb' +======= +>>>>>>> main # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). Style/RedundantStringEscape: Exclude: - 'app/models/conference.rb' -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, AllowInnerSlashes. -# SupportedStyles: slashes, percent_r, mixed -Style/RegexpLiteral: - Exclude: - - 'app/uploaders/picture_uploader.rb' - -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, explicit -Style/RescueStandardError: - Exclude: - - 'app/controllers/users/omniauth_callbacks_controller.rb' - - 'lib/tasks/migrate_config.rake' - -# Offense count: 3 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: only_raise, only_fail, semantic -Style/SignalException: - Exclude: - - 'lib/tasks/user.rake' - - 'spec/support/flash.rb' - -# Offense count: 6 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowModifier. -Style/SoleNestedConditional: - Exclude: - - 'app/controllers/admin/users_controller.rb' - - 'app/models/event.rb' - - 'app/models/user.rb' - - 'db/migrate/20140801164901_move_conference_media_to_commercial.rb' - - 'db/migrate/20140801170430_move_event_media_to_commercial.rb' - - 'db/migrate/20151018152439_create_programs_table.rb' - -# Offense count: 28 +# Offense count: 34 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Mode. Style/StringConcatenation: Enabled: false -# Offense count: 17 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. -# SupportedStyles: single_quotes, double_quotes -Style/StringLiterals: - Exclude: - - 'Gemfile' - - 'config/deploy.rb' - - 'config/environments/production.rb' - - 'config/puma.rb' - - 'lib/tasks/dump_db.rake' - - 'lib/tasks/events_registrations.rake' - - 'lib/tasks/factory_bot.rake' - - 'lib/tasks/user.rake' - -# Offense count: 14 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: single_quotes, double_quotes -Style/StringLiteralsInInterpolation: - Exclude: - - 'app/views/admin/events/_all_events.xlsx.axlsx' - - 'app/views/admin/events/_all_with_comments.xlsx.axlsx' - - 'app/views/admin/events/_confirmed_events.xlsx.axlsx' - - 'lib/tasks/dump_db.rake' - -# Offense count: 110 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinSize. -# SupportedStyles: percent, brackets -Style/SymbolArray: - Enabled: false - -# Offense count: 10 +# Offense count: 11 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. # AllowedMethods: define_method, mail, respond_to @@ -1963,10 +1341,10 @@ Style/SymbolProc: - 'app/controllers/admin/questions_controller.rb' - 'app/models/ability.rb' - 'app/models/admin_ability.rb' - - 'db/migrate/20140730104658_migrate_roles_for_cancancan.rb' - 'spec/controllers/admin/conferences_controller_spec.rb' - 'spec/support/flash.rb' +<<<<<<< HEAD # Offense count: 2 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, AllowSafeAssignment. @@ -1985,16 +1363,19 @@ Style/TrailingCommaInHashLiteral: - 'spec/models/conference_spec.rb' # Offense count: 4 +======= +# Offense count: 1 +>>>>>>> main # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: WordRegex. # SupportedStyles: percent, brackets Style/WordArray: EnforcedStyle: percent - MinSize: 6 + MinSize: 5 -# Offense count: 524 +# Offense count: 260 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. # URISchemes: http, https Layout/LineLength: - Max: 619 + Max: 267 diff --git a/.ruby-version b/.ruby-version index 0aec50e6e..be94e6f53 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.1.4 +3.2.2 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..80e5486ad --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +ruby 3.2.2 +nodejs 16.20.2 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..81356792e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.indentSize": "tabSize" +} \ No newline at end of file diff --git a/Gemfile b/Gemfile index 05e824acb..29ba291ba 100644 --- a/Gemfile +++ b/Gemfile @@ -1,16 +1,16 @@ # frozen_string_literal: true def next? - File.basename(__FILE__) == "Gemfile.next" + File.basename(__FILE__) == 'Gemfile.next' end source 'https://rubygems.org' -ruby ENV.fetch('OSEM_RUBY_VERSION', '3.1.4') +ruby ENV.fetch('OSEM_RUBY_VERSION', '3.2.2') # as web framework if next? - gem 'rails', '~> 7.1' + gem 'rails', '~> 7' else gem 'rails', '~> 7.0' end @@ -23,16 +23,17 @@ gem 'puma' gem 'responders', '~> 3.0' # as supported databases -gem 'mysql2' gem 'pg' # for tracking data changes -gem 'paper_trail' +gem 'paper_trail', '< 13' # for upload management gem 'carrierwave' gem 'carrierwave-bombshelter' gem 'mini_magick' +# for uploading images to the cloud +gem 'cloudinary' # for internationalizing gem 'rails-i18n' @@ -41,8 +42,8 @@ gem 'rails-i18n' gem 'devise' gem 'devise_ichain_authenticatable' -# for openID authentication gem 'omniauth' +gem 'omniauth-discourse', github: 'snap-cloud/omniauth-discourse' gem 'omniauth-facebook' gem 'omniauth-github' gem 'omniauth-google-oauth2' @@ -62,7 +63,7 @@ gem 'rolify' gem 'unobtrusive_flash', '>=3' # as state machine -gem 'transitions', :require => %w( transitions active_record/transitions ) +gem 'transitions', require: %w[transitions active_record/transitions] # for comments gem 'acts_as_commentable_with_threading' @@ -83,6 +84,7 @@ gem 'bootstrap-sass', '~> 3.4.0' gem 'cocoon' # as the JavaScript library +# TODO: Consolidate with the rails-assets below or move to webpack... gem 'jquery-rails' gem 'jquery-ui-rails', '~> 6.0.1' @@ -94,7 +96,7 @@ gem 'bootstrap3-datetimepicker-rails', '~> 4.17.47' # data tables gem 'ajax-datatables-rails' -gem 'jquery-datatables-rails' +gem 'jquery-datatables' # for charts gem 'chartkick' @@ -106,6 +108,8 @@ gem 'leaflet-rails' gem 'gravtastic' # for country selects +# TODO-SNAPCON: Verify that this is no longer necessary. +# gem 'country_select', '< 7' gem 'i18n_data' # as PDF generator @@ -125,8 +129,10 @@ gem 'rqrcode' # to render XLS spreadsheets gem 'caxlsx_rails' -# as error catcher +# Application Monitoring +gem 'sentry-delayed_job' gem 'sentry-rails' +gem 'sentry-ruby' # to make links faster gem 'turbolinks' @@ -142,7 +148,7 @@ gem 'redcarpet' # for recurring jobs gem 'delayed_job_active_record' -gem 'whenever', :require => false +gem 'whenever', require: false # to run scripts gem 'daemons' @@ -159,9 +165,6 @@ gem 'bootstrap-switch-rails', '3.3.3' # Locked pending Bttstrp/bootstrap-switch# # for parsing OEmbed data gem 'ruby-oembed' -# for uploading images to the cloud -gem 'cloudinary' - # for setting app configuration in the environment gem 'dotenv-rails' @@ -170,7 +173,7 @@ gem 'dotenv-rails' gem 'feature' # For countable.js -gem "countable-rails" +gem 'countable-rails' # Both are not in a group as we use it also for rake data:demo # for fake data @@ -187,43 +190,50 @@ gem 'sprockets-rails' # for multiple speakers select on proposal/event forms gem 'selectize-rails' -# For collecting performance data -gem 'skylight' +# n+1 query logging +gem 'bullet' # memcached binary connector -gem 'dalli' +gem 'dalli', require: false +# Redis Cache +gem 'redis' # to generate ical files gem 'icalendar' +# for making external requests easier +gem 'httparty' + +# pagination +gem 'pagy', '<4.0' + # to tame logs gem 'lograge' group :development do - # for static code analisys - gem 'rubocop', require: false - gem 'rubocop-rspec', require: false - gem 'rubocop-rails', require: false - gem 'rubocop-capybara', require: false - gem 'rubocop-performance', require: false - gem 'haml_lint' # to open mails gem 'letter_opener' + # view mail at /letter_opener/ gem 'letter_opener_web' # as deployment system gem 'mina' # as debugger on error pages gem 'web-console' + # prepend models with db schema + gem 'annotate' end group :test do # as test framework gem 'capybara' + gem 'cucumber-rails', require: false + gem 'cucumber-rails-training-wheels' # basic imperative step defs like "Then I should see..." gem 'database_cleaner' gem 'geckodriver-helper' gem 'rspec-rails' gem 'webdrivers' # for measuring test coverage + gem 'simplecov', '<0.18' gem 'simplecov-cobertura' # for describing models gem 'shoulda-matchers', require: false @@ -242,12 +252,32 @@ group :test do # For managing the environment gem 'climate_control' # For PDFs - gem 'pdf-inspector', require: "pdf/inspector" + gem 'pdf-inspector', require: 'pdf/inspector' end -group :development, :test do +group :development, :test, :linters do # as debugger gem 'byebug' + + # for static code analisys + gem 'rubocop', require: false + gem 'rubocop-rspec', require: false + gem 'rubocop-rails', require: false + gem 'rubocop-capybara', require: false + gem 'rubocop-performance', require: false + gem 'haml_lint' + + gem 'faraday-retry', require: false + # TODO-SNAPCON: figure out which haml-lint OR haml_lint is good. + gem 'haml-lint', require: false + + # Easily run linters + gem 'pronto', require: false + gem 'pronto-haml', require: false + gem 'pronto-rubocop', require: false +end + +group :development, :test do # as development/test database gem 'sqlite3' # to test new rails version diff --git a/Gemfile.lock b/Gemfile.lock index 64dc765e0..78a4032ca 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/snap-cloud/omniauth-discourse.git + revision: 96dbfcb55bf7bef5a33f83a4919879c891af367c + specs: + omniauth-discourse (1.1.0) + addressable (~> 2.7) + omniauth (~> 2.0) + rack + GEM remote: https://rubygems.org/ specs: @@ -84,6 +93,9 @@ GEM ajax-datatables-rails (1.4.0) rails (>= 5.2) zeitwerk + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) + rake (>= 10.4, < 14.0) archive-zip (0.12.0) io-like (~> 0.3.0) ast (2.4.2) @@ -101,6 +113,9 @@ GEM bootstrap3-datetimepicker-rails (4.17.47) momentjs-rails (>= 2.8.1) builder (3.2.4) + bullet (7.0.2) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) byebug (11.1.3) cancancan (3.5.0) capybara (3.39.2) @@ -149,6 +164,46 @@ GEM crack (0.4.5) rexml crass (1.0.6) + cucumber (7.1.0) + builder (~> 3.2, >= 3.2.4) + cucumber-core (~> 10.1, >= 10.1.0) + cucumber-create-meta (~> 6.0, >= 6.0.1) + cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) + cucumber-gherkin (~> 22.0, >= 22.0.0) + cucumber-html-formatter (~> 17.0, >= 17.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-wire (~> 6.2, >= 6.2.0) + diff-lcs (~> 1.4, >= 1.4.4) + mime-types (~> 3.3, >= 3.3.1) + multi_test (~> 0.1, >= 0.1.2) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-core (10.1.1) + cucumber-gherkin (~> 22.0, >= 22.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-tag-expressions (~> 4.1, >= 4.1.0) + cucumber-create-meta (6.0.4) + cucumber-messages (~> 17.1, >= 17.1.1) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-cucumber-expressions (14.0.0) + cucumber-gherkin (22.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-html-formatter (17.0.0) + cucumber-messages (~> 17.1, >= 17.1.0) + cucumber-messages (17.1.1) + cucumber-rails (2.6.1) + capybara (>= 2.18, < 4) + cucumber (>= 3.2, < 9) + mime-types (~> 3.3) + nokogiri (~> 1.10) + railties (>= 5.0, < 8) + rexml (~> 3.0) + webrick (~> 1.7) + cucumber-rails-training-wheels (1.0.0) + cucumber-rails (>= 1.1.1) + cucumber-tag-expressions (4.1.0) + cucumber-wire (6.2.1) + cucumber-core (~> 10.1, >= 10.1.0) + cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) daemons (1.4.1) dalli (3.2.5) dante (0.2.0) @@ -193,6 +248,8 @@ GEM faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) + faraday-retry (2.0.0) + faraday (~> 2.0) fastimage (2.2.7) feature (1.4.0) ffi (1.16.3) @@ -200,13 +257,18 @@ GEM sassc (~> 2.0) geckodriver-helper (0.24.0) archive-zip (~> 0.7) - globalid (1.1.0) - activesupport (>= 5.0) + gitlab (4.19.0) + httparty (~> 0.20) + terminal-table (>= 1.5.1) + globalid (1.2.1) + activesupport (>= 6.1) gravtastic (3.2.6) haml (6.1.1) temple (>= 0.8.2) thor tilt + haml-lint (0.999.999) + haml_lint haml-rails (2.1.0) actionpack (>= 5.1) activesupport (>= 5.1) @@ -225,6 +287,9 @@ GEM http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) + httparty (0.21.0) + mini_mime (>= 1.0.0) + multi_xml (>= 0.5.2) i18n (1.14.1) concurrent-ruby (~> 1.0) i18n_data (0.17.1) @@ -237,11 +302,7 @@ GEM ruby-vips (>= 2.0.17, < 3) io-like (0.3.1) iso-639 (0.3.6) - jquery-datatables-rails (3.4.0) - actionpack (>= 3.1) - jquery-rails - railties (>= 3.1) - sass-rails + jquery-datatables (1.10.20) jquery-rails (4.5.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -270,7 +331,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -289,7 +350,6 @@ GEM rake mini_magick (4.12.0) mini_mime (1.1.5) - mini_portile2 (2.8.5) minitest (5.22.2) momentjs-rails (2.29.4.1) railties (>= 3.1) @@ -303,23 +363,26 @@ GEM money (~> 6.13) railties (>= 3.0) multi_json (1.15.0) + multi_test (0.1.2) multi_xml (0.6.0) - mysql2 (0.5.5) - net-imap (0.3.7) + net-imap (0.4.10) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.3.4) + net-smtp (0.4.0.1) net-protocol netrc (0.11.0) next_rails (1.2.4) colorize (>= 0.8.1) - nio4r (2.5.9) - nokogiri (1.16.2) - mini_portile2 (~> 2.8.2) + nio4r (2.7.0) + nokogiri (1.16.2-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.2-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) @@ -328,6 +391,9 @@ GEM rack (>= 1.2, < 4) snaky_hash (~> 2.0) version_gem (~> 1.1) + octokit (6.1.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) omniauth (2.1.1) hashie (>= 3.4.6) rack (>= 2.2.3) @@ -353,6 +419,7 @@ GEM omniauth (~> 2.0) open4 (1.3.4) orm_adapter (0.5.0) + pagy (3.11.0) paper_trail (12.3.0) activerecord (>= 5.2) request_store (~> 1.1) @@ -382,8 +449,22 @@ GEM prawn-table prawn-table (0.2.2) prawn (>= 1.3.0, < 3.0.0) + pronto (0.11.1) + gitlab (>= 4.4.0, < 5.0) + httparty (>= 0.13.7, < 1.0) + octokit (>= 4.7.0, < 7.0) + rainbow (>= 2.2, < 4.0) + rexml (>= 3.2.5, < 4.0) + rugged (>= 0.23.0, < 2.0) + thor (>= 0.20.3, < 2.0) + pronto-haml (0.11.1) + haml_lint (~> 0.23) + pronto (~> 0.11.0) + pronto-rubocop (0.11.2) + pronto (~> 0.11.0) + rubocop (>= 0.63.1, < 2.0) public_suffix (5.0.4) - puma (6.2.2) + puma (6.4.2) nio4r (~> 2.0) racc (1.7.3) rack (2.2.8.1) @@ -412,11 +493,13 @@ GEM actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails-i18n (7.0.7) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) @@ -428,9 +511,11 @@ GEM thor (~> 1.0) zeitwerk (~> 2.5) rainbow (3.1.1) - rake (13.0.6) + rake (13.1.0) recaptcha (5.14.0) + json redcarpet (3.6.0) + redis (4.7.1) regexp_parser (2.9.0) request_store (1.5.1) rack (>= 1.4) @@ -505,6 +590,7 @@ GEM ffi (~> 1.12) ruby2_keywords (0.0.5) rubyzip (2.3.2) + rugged (1.5.1) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -515,11 +601,17 @@ GEM sprockets (> 3.0) sprockets-rails tilt + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) selectize-rails (0.12.6) selenium-webdriver (4.10.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + sentry-delayed_job (5.9.0) + delayed_job (>= 4.0) + sentry-ruby (~> 5.9.0) sentry-rails (5.9.0) railties (>= 5.0) sentry-ruby (~> 5.9.0) @@ -528,41 +620,42 @@ GEM shoulda-matchers (5.3.0) activesupport (>= 5.2.0) simple_po_parser (1.1.6) - simplecov (0.22.0) + simplecov (0.17.1) docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-cobertura (2.1.0) - rexml - simplecov (~> 0.19) - simplecov-html (0.12.3) - simplecov_json_formatter (0.1.4) - skylight (5.3.4) - activesupport (>= 5.2.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-cobertura (1.4.2) + simplecov (~> 0.8) + simplecov-html (0.10.2) snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) - sprockets (4.2.0) + sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.6.3) - mini_portile2 (~> 2.8.0) + sqlite3 (1.6.3-arm64-darwin) + sqlite3 (1.6.3-x86_64-darwin) + sqlite3 (1.6.3-x86_64-linux) ssrf_filter (1.1.2) - stripe (5.53.0) + stripe (5.55.0) stripe-ruby-mock (3.1.0.rc3) dante (>= 0.2.0) multi_json (~> 1.0) stripe (> 5, < 6) + sys-uname (1.2.2) + ffi (~> 1.1) sysexits (1.2.0) temple (0.10.1) - thor (1.2.2) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + thor (1.3.1) tilt (2.1.0) timecop (0.9.6) - timeout (0.3.2) + timeout (0.4.1) transitions (1.3.0) ttfunk (1.7.0) turbolinks (5.2.1) @@ -576,9 +669,10 @@ GEM unf_ext unf_ext (0.0.8.2) unicode-display_width (2.5.0) + uniform_notifier (1.16.0) unobtrusive_flash (3.3.1) railties - version_gem (1.1.2) + version_gem (1.1.3) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.0) @@ -594,6 +688,7 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) + webrick (1.8.1) websocket (1.2.9) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -605,18 +700,22 @@ GEM zeitwerk (2.6.13) PLATFORMS - ruby + arm64-darwin-23 + x86_64-darwin-23 + x86_64-linux DEPENDENCIES active_model_serializers acts_as_commentable_with_threading acts_as_list ajax-datatables-rails + annotate autoprefixer-rails awesome_nested_set bootstrap-sass (~> 3.4.0) bootstrap-switch-rails (= 3.3.3) bootstrap3-datetimepicker-rails (~> 4.17.47) + bullet byebug cancancan capybara @@ -628,6 +727,8 @@ DEPENDENCIES cloudinary cocoon countable-rails + cucumber-rails + cucumber-rails-training-wheels daemons dalli database_cleaner @@ -637,16 +738,19 @@ DEPENDENCIES dotenv-rails factory_bot_rails faker + faraday-retry feature font-awesome-sass geckodriver-helper gravtastic + haml-lint haml-rails haml_lint + httparty i18n_data icalendar iso-639 - jquery-datatables-rails + jquery-datatables jquery-rails jquery-ui-rails (~> 6.0.1) json-schema @@ -658,25 +762,30 @@ DEPENDENCIES mina mini_magick money-rails - mysql2 next_rails omniauth + omniauth-discourse! omniauth-facebook omniauth-github omniauth-google-oauth2 omniauth-openid omniauth-rails_csrf_protection - paper_trail + pagy (< 4.0) + paper_trail (< 13) pdf-inspector pg prawn-qrcode prawn-rails + pronto + pronto-haml + pronto-rubocop puma rails (~> 7.0) rails-controller-testing rails-i18n recaptcha redcarpet + redis responders (~> 3.0) rexml rolify @@ -691,10 +800,12 @@ DEPENDENCIES ruby-oembed sass-rails (>= 4.0.2) selectize-rails + sentry-delayed_job sentry-rails + sentry-ruby shoulda-matchers + simplecov (< 0.18) simplecov-cobertura - skylight sprockets-rails sqlite3 stripe @@ -710,7 +821,7 @@ DEPENDENCIES whenever RUBY VERSION - ruby 3.1.4 + ruby 3.2.2 BUNDLED WITH - 2.3.26 + 2.5.6 diff --git a/Gemfile.next.lock b/Gemfile.next.lock index 0e8acc2e3..cdb7ec0a4 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/snap-cloud/omniauth-discourse.git + revision: 96dbfcb55bf7bef5a33f83a4919879c891af367c + specs: + omniauth-discourse (1.1.0) + addressable (~> 2.7) + omniauth (~> 2.0) + rack + GEM remote: https://rails-assets.org/ specs: @@ -107,6 +116,9 @@ GEM afm (0.2.2) ajax-datatables-rails (1.3.1) zeitwerk + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) + rake (>= 10.4, < 14.0) archive-zip (0.12.0) io-like (~> 0.3.0) ast (2.4.2) @@ -120,10 +132,13 @@ GEM bootstrap-sass (3.4.1) autoprefixer-rails (>= 5.2.1) sassc (>= 2.0.0) - bootstrap-switch-rails (3.3.5) + bootstrap-switch-rails (3.3.3) bootstrap3-datetimepicker-rails (4.17.47) momentjs-rails (>= 2.8.1) builder (3.2.4) + bullet (7.0.7) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) byebug (11.1.3) cancancan (3.3.0) capybara (3.35.3) @@ -165,8 +180,10 @@ GEM aws_cf_signer rest-client (>= 2.0.0) cocoon (1.2.15) + coderay (1.1.3) colorize (0.8.1) concurrent-ruby (1.1.9) + connection_pool (2.3.0) countable-rails (0.0.1) railties (>= 3.1) countries (4.0.1) @@ -178,6 +195,46 @@ GEM crack (0.4.5) rexml crass (1.0.6) + cucumber (7.1.0) + builder (~> 3.2, >= 3.2.4) + cucumber-core (~> 10.1, >= 10.1.0) + cucumber-create-meta (~> 6.0, >= 6.0.1) + cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) + cucumber-gherkin (~> 22.0, >= 22.0.0) + cucumber-html-formatter (~> 17.0, >= 17.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-wire (~> 6.2, >= 6.2.0) + diff-lcs (~> 1.4, >= 1.4.4) + mime-types (~> 3.3, >= 3.3.1) + multi_test (~> 0.1, >= 0.1.2) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-core (10.1.1) + cucumber-gherkin (~> 22.0, >= 22.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-tag-expressions (~> 4.1, >= 4.1.0) + cucumber-create-meta (6.0.4) + cucumber-messages (~> 17.1, >= 17.1.1) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-cucumber-expressions (14.0.0) + cucumber-gherkin (22.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-html-formatter (17.0.0) + cucumber-messages (~> 17.1, >= 17.1.0) + cucumber-messages (17.1.1) + cucumber-rails (2.6.1) + capybara (>= 2.18, < 4) + cucumber (>= 3.2, < 9) + mime-types (~> 3.3) + nokogiri (~> 1.10) + railties (>= 5.0, < 8) + rexml (~> 3.0) + webrick (~> 1.7) + cucumber-rails-training-wheels (1.0.0) + cucumber-rails (>= 1.1.1) + cucumber-tag-expressions (4.1.0) + cucumber-wire (6.2.1) + cucumber-core (~> 10.1, >= 10.1.0) + cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) daemons (1.4.0) dalli (2.7.11) dante (0.2.0) @@ -241,24 +298,49 @@ GEM fastimage (2.2.5) feature (1.4.0) ffi (1.15.3) - font-awesome-rails (4.7.0.8) - railties (>= 3.2, < 8.0) + flay (2.13.0) + erubi (~> 1.10) + path_expander (~> 1.0) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) + font-awesome-sass (6.3.0) + sassc (~> 2.0) + formatador (1.1.0) geckodriver-helper (0.24.0) archive-zip (~> 0.7) + gitlab (4.19.0) + httparty (~> 0.20) + terminal-table (>= 1.5.1) globalid (1.0.0) activesupport (>= 5.0) gravtastic (3.2.6) + guard (2.18.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) haml (5.2.2) temple (>= 0.8.0) tilt + haml-lint (0.999.999) + haml_lint haml-rails (2.0.1) actionpack (>= 5.1) activesupport (>= 5.1) haml (>= 4.0.6, < 6.0) html2haml (>= 1.0.1) railties (>= 5.1) - haml_lint (0.37.1) - haml (>= 4.0, < 5.3) + haml_lint (0.45.0) + haml (>= 4.0, < 6.2) parallel (~> 1.10) rainbow rubocop (>= 0.50.0) @@ -275,6 +357,9 @@ GEM http-accept (1.7.0) http-cookie (1.0.4) domain_name (~> 0.5) + httparty (0.21.0) + mini_mime (>= 1.0.0) + multi_xml (>= 0.5.2) i18n (1.9.1) concurrent-ruby (~> 1.0) i18n_data (0.13.0) @@ -287,11 +372,7 @@ GEM io-like (0.3.1) io-wait (0.2.1) iso-639 (0.3.5) - jquery-datatables-rails (3.4.0) - actionpack (>= 3.1) - jquery-rails - railties (>= 3.1) - sass-rails + jquery-datatables (1.10.20) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -314,9 +395,13 @@ GEM letter_opener (~> 1.7) railties (>= 5.2) rexml + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) loofah (2.13.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) + lumberjack (1.2.8) mail (2.7.1) mini_mime (>= 0.1.1) marcel (1.0.1) @@ -344,9 +429,10 @@ GEM money (~> 6.13.2) railties (>= 3.0) multi_json (1.15.0) + multi_test (0.1.2) multi_xml (0.6.0) multipart-post (2.1.1) - mysql2 (0.5.3) + nenv (0.3.0) net-imap (0.2.3) digest net-protocol @@ -369,12 +455,18 @@ GEM nokogiri (1.13.1) mini_portile2 (~> 2.7.0) racc (~> 1.4) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) oauth2 (1.4.7) faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) + octokit (6.0.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) omniauth (2.0.4) hashie (>= 3.4.6) rack (>= 1.6.2, < 3) @@ -400,12 +492,14 @@ GEM omniauth (~> 2.0) open4 (1.3.4) orm_adapter (0.5.0) + pagy (3.11.0) paper_trail (12.2.0) activerecord (>= 5.2) request_store (~> 1.1) parallel (1.21.0) parser (3.1.0.0) ast (~> 2.4.1) + path_expander (1.1.1) pdf-core (0.9.0) pdf-inspector (1.3.0) pdf-reader (>= 1.0, < 3.0.a) @@ -428,6 +522,29 @@ GEM prawn-table prawn-table (0.2.2) prawn (>= 1.3.0, < 3.0.0) + pronto (0.11.1) + gitlab (>= 4.4.0, < 5.0) + httparty (>= 0.13.7, < 1.0) + octokit (>= 4.7.0, < 7.0) + rainbow (>= 2.2, < 4.0) + rexml (>= 3.2.5, < 4.0) + rugged (>= 0.23.0, < 2.0) + thor (>= 0.20.3, < 2.0) + pronto-flay (0.11.1) + flay (~> 2.8) + pronto (~> 0.11.0) + pronto-haml (0.11.1) + haml_lint (~> 0.23) + pronto (~> 0.11.0) + pronto-rubocop (0.11.5) + pronto (~> 0.11.0) + rubocop (>= 0.63.1, < 2.0) + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.10.1) + byebug (~> 11.0) + pry (>= 0.13, < 0.15) public_suffix (4.0.6) puma (4.3.8) nio4r (~> 2.0) @@ -475,9 +592,16 @@ GEM zeitwerk (~> 2.5) rainbow (3.1.1) rake (13.0.6) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) recaptcha (5.8.1) json redcarpet (3.5.1) + redis (5.0.6) + redis-client (>= 0.9.0) + redis-client (0.12.2) + connection_pool regexp_parser (2.2.0) request_store (1.5.1) rack (>= 1.4) @@ -495,6 +619,10 @@ GEM chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.1.0) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) rspec-activemodel-mocks (1.1.0) activemodel (>= 3.0) activesupport (>= 3.0) @@ -527,7 +655,10 @@ GEM unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.15.1) parser (>= 3.0.1.1) - rubocop-rails (2.11.3) + rubocop-faker (1.1.0) + faker (>= 2.12.0) + rubocop (>= 0.82.0) + rubocop-rails (2.15.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) @@ -544,6 +675,7 @@ GEM ruby_parser (3.17.0) sexp_processor (~> 4.15, >= 4.15.1) rubyzip (2.3.2) + rugged (1.5.1) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -554,17 +686,26 @@ GEM sprockets (> 3.0) sprockets-rails tilt + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) selectize-rails (0.12.6) selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) + sentry-delayed_job (5.8.0) + delayed_job (>= 4.0) + sentry-ruby (~> 5.8.0) sentry-rails (4.6.5) railties (>= 5.0) sentry-ruby-core (~> 4.6.0) + sentry-ruby (5.8.0) + concurrent-ruby (~> 1.0, >= 1.0.2) sentry-ruby-core (4.6.5) concurrent-ruby faraday sexp_processor (4.15.3) + shellany (0.0.1) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) simplecov (0.21.2) @@ -576,7 +717,7 @@ GEM simplecov-html (0.12.3) simplecov_json_formatter (0.1.3) sixarm_ruby_unaccent (1.2.0) - skylight (5.1.0) + skylight (5.3.3) activesupport (>= 5.2.0) sort_alphabetical (1.1.0) unicode_utils (>= 1.2.2) @@ -595,8 +736,12 @@ GEM multi_json (~> 1.0) stripe (> 5, < 6) strscan (3.0.1) + sys-uname (1.2.2) + ffi (~> 1.1) sysexits (1.2.0) temple (0.8.2) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) thor (1.2.1) tilt (2.0.10) timecop (0.9.4) @@ -615,6 +760,7 @@ GEM unf_ext (0.0.7.7) unicode-display_width (2.1.0) unicode_utils (1.4.0) + uniform_notifier (1.16.0) unobtrusive_flash (3.3.1) railties warden (1.2.9) @@ -632,6 +778,7 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) + webrick (1.8.1) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -649,11 +796,13 @@ DEPENDENCIES acts_as_commentable_with_threading acts_as_list ajax-datatables-rails + annotate autoprefixer-rails awesome_nested_set bootstrap-sass (~> 3.4.0) - bootstrap-switch-rails (~> 3.3.5) + bootstrap-switch-rails (= 3.3.3) bootstrap3-datetimepicker-rails (~> 4.17.47) + bullet byebug cancancan capybara @@ -665,7 +814,9 @@ DEPENDENCIES cloudinary cocoon countable-rails - country_select + country_select (< 7) + cucumber-rails + cucumber-rails-training-wheels daemons dalli database_cleaner @@ -676,14 +827,16 @@ DEPENDENCIES factory_bot_rails faker feature - font-awesome-rails + font-awesome-sass geckodriver-helper gravtastic + guard-rspec + haml-lint haml-rails - haml_lint + httparty icalendar iso-639 - jquery-datatables-rails + jquery-datatables jquery-rails jquery-ui-rails (~> 6.0.1) json-schema @@ -694,26 +847,35 @@ DEPENDENCIES mina mini_magick money-rails - mysql2 next_rails - nokogiri (>= 1.8.1) + nokogiri omniauth + omniauth-discourse! omniauth-facebook omniauth-github omniauth-google-oauth2 omniauth-openid omniauth-rails_csrf_protection - paper_trail + pagy (< 4.0) + paper_trail (< 13) pdf-inspector pg prawn-qrcode prawn-rails - puma (~> 4.3) + pronto + pronto-flay + pronto-haml + pronto-rubocop + pry + pry-byebug + puma rails (~> 7) + rails-assets-bootstrap! rails-assets-bootstrap-markdown! rails-assets-bootstrap-select! rails-assets-date.format! rails-assets-holderjs! + rails-assets-jquery! rails-assets-jquery-smooth-scroll! rails-assets-markdown! rails-assets-momentjs! @@ -726,21 +888,25 @@ DEPENDENCIES rails-i18n recaptcha redcarpet + redis responders (~> 3.0) + rexml rolify rqrcode rspec-activemodel-mocks rspec-rails - rubocop + rubocop-faker rubocop-rails rubocop-rspec ruby-oembed sass-rails (>= 4.0.2) selectize-rails + sentry-delayed_job sentry-rails + sentry-ruby shoulda-matchers simplecov-cobertura - skylight + skylight (~> 5) sprockets-rails sqlite3 stripe @@ -756,7 +922,7 @@ DEPENDENCIES whenever RUBY VERSION - ruby 3.1.0 + ruby 3.2.2p185 BUNDLED WITH - 2.3.3 + 2.3.26 diff --git a/INSTALL_SNAPCON.md b/INSTALL_SNAPCON.md new file mode 100644 index 000000000..276f8fc21 --- /dev/null +++ b/INSTALL_SNAPCON.md @@ -0,0 +1,57 @@ +# Install Snap!Con +Snap!Con runs Ruby 2.7.6 with Rails 5.2. The backend is provided by PostgreSQL. It is recommended to use RVM to manage the gems for this project. Some JavaScript dependencies are used, which need to be installed via NPM or Yarn. + +## Setup +The recommended setup steps are as follows: + +1. Run `npm install` or `yarn` to install the necessary JavaScript dependencies. +1. Run `bundle config set without production` to ensure only developmental gems are installed. +1. Run `bundle config set path vendor/bundle` to install gems to `vendor/bundle`. +1. Run `bundle install` to install the necessary gems. +1. Configure your environment variables. + 1. Run `cp dotenv.example .env` + 1. At a bare minimum, you will likely need to provide credentials with which to access your PostgreSQL database. An example might be as follows: + ``` + OSEM_DB_USER= esobeck #this can be computer's username + OSEM_DB_PASSWORD= password123 #likely not necessary if using postgress with computer's username + ``` + 1. Other features will require more environment variables. See [Environment Variables](#environment-variables) and [INSTALL.md#configuration](INSTALL.md#configuration) for all the environment variables that may be set. +1. Run `rake db:setup` (this command and all following commands may need to prefixed with `bundle exec`) to initialize the database. + +## Setting Environment Variables for macOS + +For developers using macOS, it's necessary to set an environment variable to prevent issues related to forking processes. Before starting the application, please set `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` by following these steps: + +### For Temporary Use in the Current Terminal Session + +Execute the following command in your terminal: + +``` +export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES +``` +This will set the environment variable for the duration of your current terminal session. You'll need to run this command each time you open a new terminal window. + +## Local Deployment + +To run Snap!Con, using [Overmind](https://github.com/DarthSim/overmind) or [Foreman](https://github.com/ddollar/foreman) is recommended. Since a release command is run which automatically performs migrations, it is necessary to flag the `release` command as able to be exited without closing all other processes. The Rails server may be run via the typical `rails server` command, but do note that no jobs will be run. + +## Heroku Deployment + +**TODO:** Update this section... + +When deploying to Heroku, the `heroku/nodejs` buildpack must be run before the `heroku/ruby` buildpack. Since Snap!Con runs on PostgreSQL, the Postgres add-on must also be added to the Heroku deployment. + +Heroku Stack: `heroku-22` + +### Example Data +Example data can easily be generated by running `rake data:demo`. Please note that this command can fail if the database is not fresh. + +## Environment Variables + +In addition to the environment variables detailed under [INSTALL.md#configuration](INSTALL.md#configuration), the following environment variables may be configured. + +### `MAILBLUSTER_API_KEY` +A generated API key necessary to utilize the Mailbluster integration. + +### `FULL_CALENDAR_LICENSE_KEY=GPL-My-Project-Is-Open-Source` +If utilizing the FullCalendar display module, necessary to disable the license key warning. diff --git a/Procfile b/Procfile index 8b9e764cc..84cab0c9d 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -web: bundle exec rails server -b 0.0.0.0 +web: bundle exec puma -C config/puma.rb worker: bundle exec rails jobs:work diff --git a/README.md b/README.md index 69b58e21f..f8805568f 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,27 @@ -[![Build Status](https://github.com/openSUSE/osem/actions/workflows/spec.yml/badge.svg?branch=master)](https://github.com/openSUSE/osem/actions) -[![Code Climate](https://codeclimate.com/github/openSUSE/osem.png)](https://codeclimate.com/github/openSUSE/osem) -[![codecov](https://codecov.io/gh/opensuse/osem/branch/master/graph/badge.svg)](https://codecov.io/gh/opensuse/osem) -[![Dependencies](https://badges.depfu.com/badges/8fcd630367d20f5b48d393774c00c5fd/overview.svg)](https://depfu.com/repos/openSUSE/osem) +## Srping 2024 CS169L +[![Specs](https://github.com/snap-cloud/snapcon/actions/workflows/spec.yml/badge.svg)](https://github.com/snap-cloud/snapcon/actions/workflows/spec.yml) +[![Pivotal Tracker](doc/pivotal_tracker_logo.png)](https://www.pivotaltracker.com/n/projects/2487653) +[![Maintainability](https://api.codeclimate.com/v1/badges/b7b0d559a03bf218663a/maintainability)](https://codeclimate.com/github/snap-cloud/snapcon/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/b7b0d559a03bf218663a/test_coverage)](https://codeclimate.com/github/snap-cloud/snapcon/test_coverage) +[![codecov](https://codecov.io/gh/snap-cloud/snapcon/branch/snapcon/graph/badge.svg?token=EViEwaSjH4)](https://codecov.io/gh/snap-cloud/snapcon) + + Deploy + + +# [Snap!Con](https://snapcon.org) +Forked From: +## Open Source Event Manager - [osem.io](https://osem.io) -# Open Source Event Manager - [osem.io](https://osem.io) ![OSEM Logo](doc/osem-logo.png) An event management tool tailored to Free and Open Source Software conferences. ## Installation + Please refer to our [installation guide](INSTALL.md). ## How to contribute to OSEM + Please refer to our [contributing guide](CONTRIBUTING.md). ## Contact diff --git a/app/assets/images/snapcon_logo-original.png b/app/assets/images/snapcon_logo-original.png new file mode 100644 index 000000000..76c32bbd2 Binary files /dev/null and b/app/assets/images/snapcon_logo-original.png differ diff --git a/app/assets/images/snapcon_logo.png b/app/assets/images/snapcon_logo.png new file mode 100644 index 000000000..17f3a3340 Binary files /dev/null and b/app/assets/images/snapcon_logo.png differ diff --git a/app/assets/images/snapshot-2020.png b/app/assets/images/snapshot-2020.png new file mode 100644 index 000000000..3d9b05b17 Binary files /dev/null and b/app/assets/images/snapshot-2020.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index cf12a0859..8284fbf80 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -15,8 +15,14 @@ //= require jquery-ui/widgets/draggable //= require jquery-ui/widgets/droppable //= require waypoints/jquery.waypoints -//= require dataTables/jquery.dataTables -//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap + +//= require datatables/jquery.dataTables +//= require datatables/dataTables.bootstrap +//= require datatables/extensions/Buttons/dataTables.buttons +//= require datatables/extensions/Buttons/buttons.bootstrap +//= require datatables/extensions/Buttons/buttons.html5 +//= require datatables/extensions/Buttons/buttons.dataTables + //= require cocoon //= require bootstrap //= require Chart.bundle @@ -47,6 +53,9 @@ //= require selectize //= require bootstrap-select //= require osem-survey +//= require pagy +//= require fullcalendar-scheduler/main.js +//= require fullcalendar $(document).ready(function() { $('a[disabled=disabled]').click(function(event){ @@ -56,4 +65,6 @@ $(document).ready(function() { $('body').smoothScroll({ delegateSelector: 'a.smoothscroll' }); + + window.addEventListener("load", Pagy.init); }); diff --git a/app/assets/javascripts/fullcalendar.js.erb b/app/assets/javascripts/fullcalendar.js.erb new file mode 100644 index 000000000..e80570776 --- /dev/null +++ b/app/assets/javascripts/fullcalendar.js.erb @@ -0,0 +1,92 @@ +$( document ).ready(function() { + let calendarEl = document.getElementById('vert-schedule-full-calendar'); + if (!calendarEl) return; //check that we need a vertical schedule + let $fullCalendar = $('#fullcalendar'); + + let license_key = "<%= Rails.configuration.fullcalendar[:license_key]%>"; + + let offset = $fullCalendar.data('tzOffset'); + let interval = Math.max(5, $fullCalendar.data('minInterval')); + let localOffset = (new Date()).getTimezoneOffset()/60; + let startTime = $fullCalendar.data('startHour') - offset - localOffset; + let endTime = $fullCalendar.data('endHour') - offset - localOffset; + let startDate = new Date($fullCalendar.data('startDate')); + let endDate = new Date($fullCalendar.data('endDate')); + let event_num_days = (endDate.getTime() - startDate.getTime())/ (1000 * 3600 * 24); + let width = Math.min(4, event_num_days); + let localName = Intl.DateTimeFormat().resolvedOptions().timeZone; + // Program Hours * Minutes / Interval * Min Row Height for an event. + let contentHeight = Math.max(400, (endTime - startTime) * 60 / interval * 16); + // UTC JS offsets are "-1 *" of how they're displayed. + let operator = localOffset < 0 ? '+' : '-'; + $('.js-localTimezone').text(`(${localName} UTC ${operator}${Math.abs(localOffset)})`); + + let rightHeaderToolbar = 'resourceTimeGridDay,resourceTimeGridFourDay,listDay'; + if (event_num_days == 1) { + rightHeaderToolbar = 'resourceTimeGridDay,listDay'; + } + + // Remove subevents from the calendar. + let filterSubevents = (events) => { + return events.filter(event => event.has_parent === false) + }; + + var calendar = new FullCalendar.Calendar(calendarEl, { + schedulerLicenseKey: license_key, + nowIndicator: true, + now: $fullCalendar.data('now'), + contentHeight: contentHeight, + expandRows: true, + allDaySlot: false, + slotMinTime: startTime + ':00:00', + slotMaxTime: endTime + ':00:00', + // TODO: Set these dynamically. + slotDuration: '00:15:00', + slotLabelInterval: '00:15:00', + slotLabelFormat: { + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'short' + }, + validRange: { + start: $fullCalendar.data('startDate'), + end: $fullCalendar.data('endDate') + }, + timeZone: 'local', + initialDate: $fullCalendar.data('day'), + initialView: 'resourceTimeGridDay', + resources: $fullCalendar.data('rooms'), + resourceOrder: 'order, title', + // TODO: Move this to a XHR. + events: filterSubevents($fullCalendar.data('events')), + displayEventEnd: false, // TODO change in list view. + displayEventTime: false, + titleFormat: { // will produce something like "Tues, September 18" + month: 'long', + day: 'numeric', + weekday: 'short' + }, + headerToolbar: { + left: 'prev,next', + center: 'title', + right: rightHeaderToolbar + }, + // TODO: Make this conference Specific? + views: { + resourceTimeGridFourDay: { + type: 'resourceTimeGrid', + duration: { days: width }, + buttonText: 'overview', + datesAboveResources: true + }, + listDay: { + type: 'listDay', + displayEventEnd: true, + displayEventTime: true + } + } + }); + + calendar.render(); +}); diff --git a/app/assets/javascripts/osem-datatables.js b/app/assets/javascripts/osem-datatables.js index f5cfdd47e..5f8122330 100644 --- a/app/assets/javascripts/osem-datatables.js +++ b/app/assets/javascripts/osem-datatables.js @@ -1,5 +1,7 @@ $(function () { $.extend(true, $.fn.dataTable.defaults, { + "buttons": ["csv"], + "dom": "lBfrtip", "stateSave": true, "autoWidth": false, "pagingType": "full_numbers", diff --git a/app/assets/javascripts/osem-datepickers.js b/app/assets/javascripts/osem-datepickers.js index d2c01bd12..e603d15d0 100644 --- a/app/assets/javascripts/osem-datepickers.js +++ b/app/assets/javascripts/osem-datepickers.js @@ -1,5 +1,3 @@ -// get current_date -var today = new Date().toISOString().slice(0, 10); $(function () { $("input[id^='datetimepicker']").datetimepicker({ useCurrent: false, @@ -25,9 +23,6 @@ $(function () { format: "YYYY-MM-DD" }); - // today <= start_registration <= end_registration <= end_conference - var end_conference = $('form').data('end-conference'); - $('#registration-period-start-datepicker').datetimepicker({ format: 'YYYY-MM-DD' }); diff --git a/app/assets/javascripts/osem-schedule.js b/app/assets/javascripts/osem-schedule.js index aeb76ae37..45df23908 100644 --- a/app/assets/javascripts/osem-schedule.js +++ b/app/assets/javascripts/osem-schedule.js @@ -1,3 +1,5 @@ +// ADMIN SCHEDULE + var url; // Should be initialize in Schedule.initialize var schedule_id; // Should be initialize in Schedule.initialize @@ -114,18 +116,57 @@ $(document).ready( function() { }); }); -function eventClicked(e, element){ + +// PUBLIC SCHEDULE + +function starClicked(e) { + // stops the click from propagating + if (!e) var e = window.event; + e.preventDefault(); + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + + var callback = function(data) { + $(e.target).toggleClass('fa-solid fa-regular'); + } + + var params = { favourite_user_id: $(e.target).data('user') }; + + $.ajax({ + url: $(e.target).data('url'), + type: 'PATCH', + data: params, + success: callback, + dataType : 'json' + }); +} + +function eventClicked(e, element) { + if (e.target.href) { + return; + } var url = $(element).data('url'); - if(e.ctrlKey) - window.open(url,'_blank'); - else + if (e.ctrlKey || e.metaKey) { + window.open(url, '_blank'); + } else { window.location = url; + } +} + +function updateFavouriteStatus(options) { + if (options.loggedIn === false) { + $('.js-toggleEvent').hide(); + } + + options.events.forEach(function (id) { + $(`#eventFavourite-${id}`).removeClass('fa-regular').addClass('fa-solid'); + }); } /* Links inside event-panel (to make ctrl + click work for these links): = link_to text, '#', onClick: 'insideLinkClicked();', 'data-url' => url */ -function insideLinkClicked(event){ +function insideLinkClicked(event) { // stops the click from propagating if (!event) // for IE var event = window.event; @@ -133,7 +174,7 @@ function insideLinkClicked(event){ if (event.stopPropagation) event.stopPropagation(); var url = $(event.target).data('url'); - if(event.ctrlKey) + if(event.ctrlKey || e.metaKey) window.open(url,'_blank'); else window.location = url; diff --git a/app/assets/javascripts/osem.js b/app/assets/javascripts/osem.js index 240a48c95..87049a23a 100644 --- a/app/assets/javascripts/osem.js +++ b/app/assets/javascripts/osem.js @@ -4,7 +4,7 @@ $(function () { * releases a key on the keyboard */ $("#user_biography").bind('keyup', function() { - word_count(this, 'bio_length', 150); + word_count(this, 'bio-length', 150); } ); /** @@ -48,7 +48,8 @@ $(function () { var id = $(this).attr('id'); $('.' + id).collapse('hide'); - $('#' + $(this).val() + '-help.' + id).collapse('show'); + $(`#event_type_${$(this).val()}-help.${id}`).collapse('show'); + $(`#event_type_${$(this).val()}-instructions.${id}`).collapse('show');w }); $('.dropdown-toggle').dropdown(); @@ -142,13 +143,84 @@ function word_count(text, divId, maxcount) { }); }; +function replace_defaut_submission_text(input_selector, new_text, valid_defaults) { + let $area = $(input_selector); + let current_text = $area.val(); + + if (!current_text) { + $area.val(new_text); + $area.trigger('change'); + return; + } + + valid_defaults.some(default_text => { + if (current_text == default_text) { + $area.val(new_text); + $area.trigger('change'); + return true; + } + }); +} + +// TODO-SNAPCON: Verify what is on the proposals _from from upstream +/* Wait for the DOM to be ready before attaching events to the elements */ +$( document ).ready(function() { + /* Set the minimum and maximum proposal abstract word length */ + function updateEventTypeRequirements() { + var $selected = $("#event_event_type_id option:selected") + var max = $selected.data("max-words"); + var min = $selected.data("min-words"); + + // We replace the default text only if the current field is empty, + // or is set to the default text of another event type. + replace_defaut_submission_text( + '#event_submission_text', + $selected.data("instructions"), + $("#event_event_type_id option").toArray().map(e => $(e).data('instructions')) + ); + + $("#abstract-maximum-word-count").text(max); + $("#submission-maximum-word-count").text(max); + $("#abstract-minimum-word-count").text(min); + $("#submission-minimum-word-count").text(min); + word_count($('#event_abstract').get(0), 'abstract-count', max); + } + $("#event_event_type_id").change(updateEventTypeRequirements); + updateEventTypeRequirements(); + + /* Count the proposal abstract length */ + $("#event_abstract").on('input', function() { + var $selected = $("#event_event_type_id option:selected") + var max = $selected.data("max-words"); + word_count(this, 'abstract-count', max); + } ); + + /* Count the submission text length */ + $("#event_submission_text").bind('change keyup paste input', function() { + var $selected = $("event_event_type_id option:selected") + var max = $selected.data("max-words"); + word_count(this, 'submission-count', max); + }); + + /* Listen for reset template button, wait for confirm, and reset. */ + $('.js-resetSubmissionText').click((e) => { + let $selected = $("#event_event_type_id option:selected"); + let $this = $(e.target); + let affirm = confirm($this.data('confirm')); + if (affirm) { + let sub_text = $('#event_submission_text'); + sub_text.val($selected.data('instructions')); + sub_text.trigger('change'); + } + }); +}); + /* Commodity function for modal windows */ window.build_dialog = function(selector, content) { // Close it and remove content if it's already open $("#" + selector).modal('hide'); $("#" + selector).remove(); - // Add new content and pops it up - $("body").append("
\n" + content + "
"); + $("body").append(``); $("#" + selector).modal(); } diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 576997ae2..7c23a0d90 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,10 +1,18 @@ /* *= require strap-on - *= require dataTables/bootstrap/3/jquery.dataTables.bootstrap + *= require datatables/dataTables.bootstrap + *= require datatables/extensions/Buttons/buttons.dataTables + *= require datatables/extensions/Buttons/buttons.bootstrap + + *= require selectize + *= require selectize.bootstrap3 + *= require bootstrap-markdown + *= require bootstrap-datetimepicker + *= require bootstrap-select + *= require osem *= require osem-rating *= require osem-schedule - *= require osem-schedule-print *= require osem-dashboard *= require osem-splash *= require osem-fonts @@ -18,4 +26,6 @@ *= require selectize.bootstrap3 *= require bootstrap-select *= require conferences + + *= require fullcalendar-scheduler/main.css */ diff --git a/app/assets/stylesheets/osem-dashboard.scss b/app/assets/stylesheets/osem-dashboard.scss index 9e336c1a3..fbab73114 100644 --- a/app/assets/stylesheets/osem-dashboard.scss +++ b/app/assets/stylesheets/osem-dashboard.scss @@ -1,26 +1,34 @@ @import "breakpoints.scss"; -.dashbox span.fa-solid { - line-height: 1.1em; +.dashbox span { + margin-bottom: 2px; + + @include breakpoint(xs) { + font-size: 1.5em; + } @include breakpoint(sm) { - font-size: 3em; + font-size: 2em; } @include breakpoint(md) { - font-size: 4em; + font-size: 3.5em; } @include breakpoint(lg) { font-size: 5em; } } -.dashbox { - padding-top: 20px; - padding-bottom: 20px; -} +.dashbox.panel { + padding: 10px; + display: -webkit-flex; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.4rem; + line-height: 1.6rem; -.dashbox label { - display: block; - font-size: 1.1em; + label { + line-height: 2rem; + } } .todolist-missing span { diff --git a/app/assets/stylesheets/osem-navbar.scss b/app/assets/stylesheets/osem-navbar.scss index 011ebc890..4df2423f9 100644 --- a/app/assets/stylesheets/osem-navbar.scss +++ b/app/assets/stylesheets/osem-navbar.scss @@ -1,4 +1,6 @@ @import "breakpoints.scss"; +@import "osem-variables.scss"; + .nav-osem { border: none; @include breakpoint(xs) { @@ -29,7 +31,28 @@ } } .dropdown-menu { - padding: 17px; min-width: 225px; } + + &.navbar-default { + .navbar-nav > .open > a { + &:hover, &:focus { + color: $navbar-default-link-color; + } + } + } + + .navbar-brand img { + max-height: 100%; + } + + .trapezoid { + border-top-color: $navbar-default-bg; + } + + .profile-thumbnail { + border-radius: 3px; + max-height: 20px; + max-width: 20px; + } } diff --git a/app/assets/stylesheets/osem-rating.scss b/app/assets/stylesheets/osem-rating.scss index f686fbe2b..b4a43c28f 100644 --- a/app/assets/stylesheets/osem-rating.scss +++ b/app/assets/stylesheets/osem-rating.scss @@ -1,8 +1,9 @@ /* Styling for voting on proposals*/ .rating { background: image-url("star.png") 0 0; - width: 24px; - height: 24px; + background-size: 20px; + width: 20px; + height: 20px; display: inline-block; float: left; diff --git a/app/assets/stylesheets/osem-schedule-print.css b/app/assets/stylesheets/osem-schedule-print.css deleted file mode 100644 index 310c8eb9d..000000000 --- a/app/assets/stylesheets/osem-schedule-print.css +++ /dev/null @@ -1,54 +0,0 @@ -@media print { - -.navbar { - display: none; -} -.nav li, .tab-pane, #messages, #footer, h2, .text-error, br, .label, .schedule-subtitle, .schedule-track { - display: none; -} -.nav li.active, .tab-pane .active { - display: block; - width: 100%; - text-align: center; - text-size: 12px; - border-style: none; - font-weight: bold; -} -.nav-tabs { - border-bottom-style: none; -} -.nav-tabs > .active > a { - border-style: none; -} -#wrap { - min-height:0px; - height:auto; - margin:0px; - line-height: 12px; - font-size: 10px; -} -#content { - position: absolute; - top: 0px; - left: 0px; - max-width: 210mm !important; - max-height: 297mm !important; - padding: 10px; -} -#schedule td.event { - background-image: none; - border-style:solid; - border-width: 2px; - border-color: black; - line-height: auto; - font-size: 10px; -} -.event img, .img-circle { - display: none; -} -.table th, .table td { - line-height: 12px; - padding: 3px; -} - -} diff --git a/app/assets/stylesheets/osem-schedule.scss b/app/assets/stylesheets/osem-schedule.scss index 1cf1efecf..f722a41e9 100644 --- a/app/assets/stylesheets/osem-schedule.scss +++ b/app/assets/stylesheets/osem-schedule.scss @@ -1,14 +1,4 @@ -#schedule-content .carousel-control{ - width: 12%; -} - -@media (min-width: 768px){ - #schedule-content .carousel-control{ - width: 5%; - } -} - -.room-name{ +.room-name { font-weight: bold; padding:10px; margin-top: 30px; @@ -24,7 +14,7 @@ overflow: hidden; } -.schedule-room-slot{ +.schedule-room-slot { padding: 2px 3px; border: 1px solid #848484; height: 58px; @@ -32,21 +22,43 @@ font-size: 13px; } -.schedule-event{ +.schedule-event { padding: 7px; border: 2px solid #151515; position:relative; z-index:1; - cursor:move; + cursor: move; + + a { + color: black; + } +} + +.schedule-room-slot.compact { + line-height: 12px; + font-size: 10px; + padding: 1px; } +// When an event is in a room. .schedule-room-slot .schedule-event.compact { - margin-top: -23px; - margin-left: 20%; - width: 80%; + margin-top: -14px; + width: 85%; + margin-left: 15%; } -.schedule-event-text{ +.schedule-event.compact { + font-size: 75%; + padding: 0; + border-width: 1px; + + .schedule-event-text { + line-height: 12px; + overflow: hidden; + } +} + +.schedule-event-text { display: -webkit-box; text-overflow: ellipsis; -webkit-box-orient: vertical; @@ -54,25 +66,21 @@ overflow: hidden; } +.schedule-event.compact { + .schedule-event-delete-button { + padding: 1px; + margin-right: 3px; + } +} + .schedule-event-delete-button { - font-weight:bold; + font-weight: bold; cursor: pointer; - padding: 0px 3px; - border: 1px solid grey; + padding: 0 3px; margin-right: 5px; + color: black; } -#schedule td.event { - background-image: linear-gradient(to top, rgb(247,250,242) 24%, rgb(194,232,190) 97%, rgb(194,232,190) 100%); - -cursor: pointer; -padding: 3px 2px; -} - -#schedule td.event:hover -{ - background-image: linear-gradient(to top, rgb(247,250,242) 24%, rgb(83,171,74) 97%, rgb(83,171,74) 100%); -} .flexvideo { position: relative; @@ -96,29 +104,16 @@ padding: 3px 2px; margin-top: 10px; } -.schedule-title, .schedule-subtitle, .schedule-speaker, .schedule-track { - clear: none; - display: block; -} -.schedule-subtitle { - color: rgb(185, 74, 72); -} - -a.unstyled-link { - text-decoration: none; - color: #333333; - outline: 0; - } - -.program-dropdown, .schedule-dropdown{ +.program-dropdown { margin-left: 20%; margin-right: 20%; - margin-top: 40px; + margin-top: 10px; margin-bottom: 20px; } -.schedule-dropdown > button, .program-dropdown > button, .program-dropdown > .dropdown-menu, .schedule-dropdown > .dropdown-menu{ +.program-dropdown > button, +.program-dropdown > .dropdown-menu { width: 100%; } @@ -146,42 +141,6 @@ a.unstyled-link { hyphens: auto; } -.room, .event-title{ - font-size: 11px; - line-height: 17px; - overflow: hidden; -} - -td.room{ - width: 15%; - padding: 3px !important; - max-width:15%; -} - -th.date{ - font-size: 12px; - padding: 4px 0px 4px 2px !important; -} - -.speaker-pic{ - padding: 4px 2px; -} - -.schedule-table{ - width: 100%; - margin:0px; - padding:0px; -} - -td.no-padding{ - padding:0px !important; -} - -.all-events-title{ - margin-top: 40px; - border-bottom: 1px solid #848484; -} - .date-title{ font-size: 23px; font-weight: bold; @@ -189,136 +148,40 @@ td.no-padding{ display:inline-block; } -.start-time{ - font-size: 16px; - padding-left: 30px; - margin-top: 32px; -} - -.new-time-event{ - margin-top: 20px; -} - -.date-content{ +.date-content { border-bottom: 1px solid #6E6E6E; - margin-top: 30px; + margin: 30px 0 20px; } .unscheduled-event{ margin-top: 8px; } -.all-speaker-pic{ - padding: 20px 10px 20px 10px; -} - -.track{ +.track { padding: 0px 5px 0px 5px; display: inline-block; } -.event-panel{ - cursor: pointer; - } - -.program-dropdown{ - margin-left: 20%; - margin-right: 20%; - margin-top: 40px; -} - -.program-dropdown > button{ - width: 100%; -} - -.program-dropdown > .dropdown-menu{ - width: 100%; -} - -.no-events-day{ +.no-events-day { color: #D8D8D8 !important; } -.schedule-label{ - height: 14px; - line-height: 11px; - overflow: hidden; - display: inline-block; - white-space: normal; - padding-left: 1px; - padding-right: 1px; - font-size: 7px; -} - .non_schedulable{ opacity: 0.5; } -/* Small devices (tablets, 768px and up) */ -@media (min-width: 768px) { - .room, .event-title{ - font-size: 12px; - } - - td.room{ - width: 12%; - max-width:12%; - } - - th.date{ - font-size: 13px; - } - - td.event{ - padding: 3px 3px !important; - } - - .schedule-label{ - padding-left: 2px; - padding-right: 3px; - } -} - -/* Medium devices (desktops, 992px and up) */ -@media (min-width: 992px) { - .room, .event-title{ - font-size: 13px; - } - - td.room{ - width: 10%; - max-width: 10%; - } +.event-panel-title { + margin: auto 3px; + flex: 1; + line-height: 1.4; - .schedule-label{ - height: 15px; - line-height: 12px; - font-size: 7px; + // Override some bootstrap + small { + line-height: 1.6; } } -/* Large devices (large desktops, 1200px and up) */ -@media (min-width: 1200px) { - .room, .event-title{ - font-size: 14px; - } - - .schedule-label{ - padding-left: 2px; - padding-right: 2px; - font-size: 8px; - } - - td.room{ - width: 9%; - max-width: 9%; - } - - td.event{ - padding: 3px 6px !important; - } - - th.date{ - font-size: 14px; - } +// Override Boostrap setting. +h3.event-panel-title small { + line-height: 1.4; } diff --git a/app/assets/stylesheets/osem-splash.scss b/app/assets/stylesheets/osem-splash.scss index 35a27dca5..15354cfc2 100644 --- a/app/assets/stylesheets/osem-splash.scss +++ b/app/assets/stylesheets/osem-splash.scss @@ -1,23 +1,37 @@ @import "breakpoints.scss"; +@import "osem-variables.scss"; #splash { // Counter the general padding for #content - margin-bottom: -60px; + margin-bottom: -65px; + section { padding-top: 60px; padding-bottom: 60px; + + .trapezoid { + top: 60px + $trapezoid-height; + } } section:nth-child(even) { - background: none repeat scroll 0 0 #e0e0e0; - color: #555; + background: none repeat scroll 0 0 #eee; + color: #333; + + .trapezoid { + border-top-color: #eee; + } } section:nth-child(odd) { background: none repeat scroll 0 0 #ffffff; - color: #7b7b7b; + color: #333; .thumbnail { background: none repeat scroll 0 0 #e0e0e0; } + + .trapezoid { + border-top-color: #fff; + } } .cta-button { @@ -32,11 +46,15 @@ padding-top: 100px; padding-bottom: 100px; .container { - #header { + #header-no-image { background-color: rgba(224, 224, 224,.80); padding-top: 20px; padding-bottom: 20px; } + + #header-image { + color: #FFF; + } } } @@ -67,6 +85,10 @@ #venue-pic { margin-top: 20px; } + // The venue section has less padding. + .trapezoid { + top: $trapezoid-height; + } } #lodging { @@ -93,6 +115,14 @@ } } + #tickets { + a { + &:hover, &:focus { + text-decoration: none; + } + } + } + #sponsors { .img-sponsor { max-height: 100px; @@ -115,15 +145,18 @@ } } + .social-media.trapezoid { + border-top-color: #0C3559; // TODO: Use @conference color? + } + #social-media{ - background: none repeat scroll 0 0 #2f2f2f; - padding-top: 60px; - padding-bottom: 60px; + background: none repeat scroll 0 0 #0C3559; + padding: 50px 20px; i{ - color: #4a4a4a; + color: #FFF; } i:hover{ - color: #16a085; + color: #F2E205; } a{ padding-left: 100px; @@ -164,11 +197,11 @@ i.fa-solid { line-height: inherit; } - &.show { - visibility:visible; - cursor:pointer; - opacity: 1.0; - } + // &.show { + // visibility:visible; + // cursor:pointer; + // opacity: 1.0; + // } a { color: white; } diff --git a/app/assets/stylesheets/osem-variables.scss b/app/assets/stylesheets/osem-variables.scss new file mode 100644 index 000000000..1ae23c039 --- /dev/null +++ b/app/assets/stylesheets/osem-variables.scss @@ -0,0 +1,14 @@ +// This file is included *early* in CSS order! +// These variables change bootstrap defaults. + +$navbar-default-bg: #0C3559; +$navbar-default-link-active-bg: rgb(8,8,8); +$navbar-default-border: 1px solid #d4d4d4; +$navbar-default-color: #FFF; +$navbar-default-link-color: #FFF; +$navbar-default-link-hover-color: #F2E205; +$navbar-default-link-active-hover-color: #FFF; + + +// The hiegh of the section tabs that are like Snap! blocks. +$trapezoid-height: 14px; diff --git a/app/assets/stylesheets/osem.scss b/app/assets/stylesheets/osem.scss index 04da1a377..999d9f0f4 100644 --- a/app/assets/stylesheets/osem.scss +++ b/app/assets/stylesheets/osem.scss @@ -1,3 +1,4 @@ +@import "osem-variables"; @import "bootstrap/mixins"; html { @@ -6,16 +7,33 @@ html { } body { + // Specifically remove Helevtica Neue from BS3 stack. + font-family: sans-serif; /* Margin bottom by 2 times the footer height */ - margin-bottom: 60px; - /* Margin bottom by navbar height */ + margin-bottom: 85px; padding-top: 60px; + font-size: 16px; } #content { padding-bottom: 60px; } +// Designed to be the last element in a section / nav +// Makes a little "puzzle piece" connector. +.trapezoid { + position: relative; + margin-left: 60px; + border-top: $trapezoid-height solid; + border-left: 9px solid transparent; + border-right: 9px solid transparent; + height: 0; + width: 64px; + margin-top: -1 * $trapezoid-height; + top: $trapezoid-height; + z-index: 1000; +} + #footer { position: absolute; bottom: 0; @@ -125,3 +143,8 @@ p.comment-body { .word_break { word-wrap: break-word; } + +// Override bootstrap 3... +hr { + border-top: 1px solid #333; +} diff --git a/app/assets/stylesheets/strap-on.scss b/app/assets/stylesheets/strap-on.scss index 571161e6d..bd2d3b38b 100644 --- a/app/assets/stylesheets/strap-on.scss +++ b/app/assets/stylesheets/strap-on.scss @@ -1,10 +1,5 @@ -$navbar-default-bg: #299a0b; -$navbar-default-link-active-bg: #DFE0DF; -$navbar-default-border: 1px solid #d4d4d4; -$navbar-default-color: #ffffff; -$navbar-default-link-color: #ffffff; -$navbar-default-link-hover-color: #000000; - +// Place bootstrap variable customizations in the file below. +@import "osem-variables"; @import 'bootstrap-datetimepicker'; @import "bootstrap-sprockets"; @@ -56,7 +51,6 @@ $navbar-default-link-hover-color: #000000; @import "bootstrap/modals"; @import "bootstrap/tooltip"; @import "bootstrap/popovers"; -@import "bootstrap/carousel"; // Utility classes @import "bootstrap/utilities"; diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 824744266..2229d983d 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -16,12 +16,13 @@ def current_ability end def verify_user_admin - if (current_user.nil?) + if current_user.nil? redirect_to sign_in_path return false end unless (current_user.has_cached_role? :organizer, :any) || (current_user.has_cached_role? :cfp, :any) || - (current_user.has_cached_role? :info_desk, :any) || (current_user.has_cached_role? :organization_admin, :any) || + (current_user.has_cached_role? :info_desk, + :any) || (current_user.has_cached_role? :organization_admin, :any) || (current_user.has_cached_role? :volunteers_coordinator, :any) || (current_user.has_cached_role? :track_organizer, :any) || current_user.is_admin raise CanCan::AccessDenied.new('You are not authorized to access this page.') diff --git a/app/controllers/admin/booths_controller.rb b/app/controllers/admin/booths_controller.rb index 0a8999213..533fe23c6 100644 --- a/app/controllers/admin/booths_controller.rb +++ b/app/controllers/admin/booths_controller.rb @@ -59,8 +59,8 @@ def update redirect_to admin_conference_booths_path, notice: "Successfully updated #{t 'booth'} for #{@booth.title}." else - flash.now[:error] = "An error prohibited the #{t'booth'} for #{@booth.title} "\ - "#{@booth.errors.full_messages.join('. ')}." + flash.now[:error] = "An error prohibited the #{t 'booth'} for #{@booth.title} " \ + "#{@booth.errors.full_messages.join('. ')}." render :edit end end @@ -73,7 +73,7 @@ def accept Mailbot.conference_booths_acceptance_mail(@booth).deliver_later end redirect_to admin_conference_booths_path(conference_id: @conference.short_title), - notice: "#{(t'booth').capitalize} successfully accepted!" + notice: "#{(t 'booth').capitalize} successfully accepted!" else redirect_to admin_conference_booths_path(conference_id: @conference.short_title) flash[:error] = "#{(t 'booth').capitalize} could not be accepted. #{@booth.errors.full_messages.to_sentence}." @@ -81,11 +81,11 @@ def accept end def to_accept - update_state(:to_accept, "#{(t'booth').capitalize} to accept") + update_state(:to_accept, "#{(t 'booth').capitalize} to accept") end def to_reject - update_state(:to_reject, "#{(t'booth').capitalize} to reject") + update_state(:to_reject, "#{(t 'booth').capitalize} to reject") end def reject @@ -96,7 +96,7 @@ def reject Mailbot.conference_booths_rejection_mail(@booth).deliver_later end redirect_to admin_conference_booths_path(conference_id: @conference.short_title), - notice: "#{(t'booth').capitalize} successfully rejected." + notice: "#{(t 'booth').capitalize} successfully rejected." else redirect_to admin_conference_booths_path(conference_id: @conference.short_title) flash[:error] = "#{(t 'booth').capitalize} could not be rejected. #{@booth.errors.full_messages.to_sentence}." @@ -125,7 +125,7 @@ def update_state(transition, notice) redirect_back_or_to(admin_conference_booths_path(conference_id: @conference.short_title)) && return else flash[:error] = alert - return redirect_back_or_to(admin_conference_booths_path(conference_id: @conference.short_title)) && return + redirect_back_or_to(admin_conference_booths_path(conference_id: @conference.short_title)) && return end end diff --git a/app/controllers/admin/cfps_controller.rb b/app/controllers/admin/cfps_controller.rb index cb43bf565..f7ac1f3ca 100644 --- a/app/controllers/admin/cfps_controller.rb +++ b/app/controllers/admin/cfps_controller.rb @@ -49,8 +49,8 @@ def destroy if @cfp.destroy redirect_to admin_conference_program_cfps_path, notice: 'Call for Papers was successfully deleted.' else - redirect_to admin_conference_program_cfps_path, error: 'An error prohibited this Call for Papers from being destroyed: '\ - "#{@cfp.errors.full_messages.join('. ')}." + redirect_to admin_conference_program_cfps_path, error: 'An error prohibited this Call for Papers from being destroyed: ' \ + "#{@cfp.errors.full_messages.join('. ')}." end end diff --git a/app/controllers/admin/comments_controller.rb b/app/controllers/admin/comments_controller.rb index 16c07a6d2..d9fcd4cd0 100644 --- a/app/controllers/admin/comments_controller.rb +++ b/app/controllers/admin/comments_controller.rb @@ -19,12 +19,20 @@ def index # Returning all available comments, ordered by created_at: :desc and by event.title def accessible_ordered_comments - Comment.accessible_by(current_ability).joins('INNER JOIN events ON commentable_id = events.id').order('events.title', 'comments.created_at DESC') + Comment.accessible_by(current_ability).joins('INNER JOIN events ON commentable_id = events.id').order( + 'events.title', 'comments.created_at DESC' + ) end -# Grouping all comments by conference, and by event. It returns {:conference => {:event => [{comment_2}, {comment_1 }]}} + # Grouping all comments by conference, and by event. It returns {:conference => {:event => [{comment_2}, {comment_1 }]}} def grouped_comments(remarks) - remarks.group_by{ |comment| comment.commentable.program.conference }.map {|conference, comments| [conference, comments.group_by{|comment| comment.commentable}]}.to_h + remarks.group_by do |comment| + comment.commentable.program.conference + end.map do |conference, comments| + [conference, comments.group_by do |comment| + comment.commentable + end] + end.to_h end end end diff --git a/app/controllers/admin/commercials_controller.rb b/app/controllers/admin/commercials_controller.rb index 66a1d8d77..073e135e4 100644 --- a/app/controllers/admin/commercials_controller.rb +++ b/app/controllers/admin/commercials_controller.rb @@ -3,7 +3,7 @@ module Admin class CommercialsController < Admin::BaseController load_and_authorize_resource :conference, find_by: :short_title - load_and_authorize_resource through: :conference, except: [:new, :create] + load_and_authorize_resource through: :conference, except: %i[new create] def index @commercials = @conference.commercials @@ -17,11 +17,11 @@ def create if @commercial.save redirect_to admin_conference_commercials_path, - notice: 'Commercial was successfully created.' + notice: 'Materials were successfully created.' else redirect_to admin_conference_commercials_path, - error: 'An error prohibited this Commercial from being saved: '\ - "#{@commercial.errors.full_messages.join('. ')}." + error: 'An error prohibited materials from being saved: ' \ + "#{@commercial.errors.full_messages.join('. ')}." end end @@ -29,23 +29,23 @@ def create def update if @commercial.update(commercial_params) redirect_to admin_conference_commercials_path, - notice: 'Commercial was successfully updated.' + notice: 'Materials were successfully updated.' else redirect_to admin_conference_commercials_path, - error: 'An error prohibited this Commercial from being saved: '\ - "#{@commercial.errors.full_messages.join('. ')}." + error: 'An error prohibited materials from being saved: ' \ + "#{@commercial.errors.full_messages.join('. ')}." end end def destroy @commercial.destroy - redirect_to admin_conference_commercials_path, notice: 'Commercial was successfully destroyed.' + redirect_to admin_conference_commercials_path, notice: 'Materials were successfully removed.' end def render_commercial result = Commercial.render_from_url(params[:url]) if result[:error] - render plain: result[:error], status: 400 + render plain: result[:error], status: :bad_request else render plain: result[:html] end @@ -60,13 +60,15 @@ def mass_upload errors = Commercial.read_file(params[:file]) if params[:file] if !params[:file] - flash[:error] = 'Empty file detected while adding commercials to Event' + flash[:error] = 'Empty file detected while adding materials to Event' elsif errors.all? { |_k, v| v.blank? } - flash[:notice] = 'Successfully added commercials.' + flash[:notice] = 'Successfully added materials.' else errors_text = '' errors_text += 'Unable to find event with ID: ' + errors[:no_event].join(', ') + '. ' if errors[:no_event].any? - errors_text += 'There were some errors: ' + errors[:validation_errors].join('. ') if errors[:validation_errors].any? + if errors[:validation_errors].any? + errors_text += 'There were some errors: ' + errors[:validation_errors].join('. ') + end flash[:error] = errors_text end @@ -76,7 +78,7 @@ def mass_upload private def commercial_params - params.require(:commercial).permit(:url) + params.require(:commercial).permit(:title, :url) end end end diff --git a/app/controllers/admin/conferences_controller.rb b/app/controllers/admin/conferences_controller.rb index 80e36f47c..fc149b54f 100644 --- a/app/controllers/admin/conferences_controller.rb +++ b/app/controllers/admin/conferences_controller.rb @@ -24,11 +24,11 @@ def index @total_withdrawn = Event.where(state: :withdrawn).count @new_withdrawn = Event - .where('state = ? and created_at > ?', 'withdrawn', current_user.last_sign_in_at).count + .where('state = ? and created_at > ?', 'withdrawn', current_user.last_sign_in_at).count @active_conferences = Conference.get_active_conferences_for_dashboard # pending or the last two @deactive_conferences = Conference - .get_conferences_without_active_for_dashboard(@active_conferences) # conferences without active + .get_conferences_without_active_for_dashboard(@active_conferences) # conferences without active @conferences = @active_conferences + @deactive_conferences @recent_users = User.limit(5).order(created_at: :desc) @@ -97,7 +97,7 @@ def update else redirect_to edit_admin_conference_path(id: short_title), error: 'Updating conference failed. ' \ - "#{@conference.errors.full_messages.join('. ')}." + "#{@conference.errors.full_messages.join('. ')}." end end @@ -111,20 +111,19 @@ def show @all_events = @program.events @total_submissions = @all_events.count - @new_submissions = @all_events - .where('created_at > ?', current_user.last_sign_in_at).count + # @new_submissions = @all_events + # .where('created_at > ?', current_user.last_sign_in_at).count @program_length = @conference.current_program_hours - @new_program_length = @conference.new_program_hours(current_user.last_sign_in_at) + # @new_program_length = @conference.new_program_hours(current_user.last_sign_in_at) @total_withdrawn = @all_events.where(state: :withdrawn).count - @new_withdrawn = @all_events.where(state: :withdrawn).where( - 'events.created_at > ?', - current_user.last_sign_in_at - ).count + # @new_withdrawn = @all_events.where(state: :withdrawn).where( + # 'events.created_at > ?', + # current_user.last_sign_in_at + # ).count - # Step by step list - @conference_progress = @conference.get_status + @conference_todo_list = @conference.get_status # Line charts @registrations = @conference.get_registrations_per_week @@ -133,22 +132,23 @@ def show # Doughnut charts @event_type_distribution = @conference.event_type_distribution - @event_type_distribution_confirmed = @conference.event_type_distribution(:confirmed) - @event_type_distribution_withdrawn = @conference.event_type_distribution(:withdrawn) + # @event_type_distribution_confirmed = @conference.event_type_distribution(:confirmed) + # @event_type_distribution_withdrawn = @conference.event_type_distribution(:withdrawn) @difficulty_levels_distribution = @conference.difficulty_levels_distribution - @difficulty_levels_distribution_confirmed = @conference - .difficulty_levels_distribution(:confirmed) - @difficulty_levels_distribution_withdrawn = @conference - .difficulty_levels_distribution(:withdrawn) + # @difficulty_levels_distribution_confirmed = @conference + # .difficulty_levels_distribution(:confirmed) + # @difficulty_levels_distribution_withdrawn = @conference + # .difficulty_levels_distribution(:withdrawn) @tracks_distribution = @conference.tracks_distribution - @tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed) - @tracks_distribution_withdrawn = @conference.tracks_distribution(:withdrawn) + # @tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed) + # @tracks_distribution_withdrawn = @conference.tracks_distribution(:withdrawn) # Recent actions information - @recent_events = @conference.program.events.limit(5).order(created_at: :desc) - @recent_registrations = @conference.registrations.limit(5).order(created_at: :desc) + @recent_events = @conference.program.events.includes(%i[submitter_event_user submitter + program]).limit(5).order(created_at: :desc) + @recent_registrations = @conference.registrations.includes([:user]).limit(5).order(created_at: :desc) @top_submitter = @conference.get_top_submitter @@ -181,7 +181,7 @@ def conference_params :vpositions_attributes, :use_volunteers, :color, :sponsorship_levels_attributes, :sponsors_attributes, :registration_limit, :organization_id, :ticket_layout, - :booth_limit) + :booth_limit, :custom_css) end end end diff --git a/app/controllers/admin/contacts_controller.rb b/app/controllers/admin/contacts_controller.rb index d5f6ddcd3..dabb871aa 100644 --- a/app/controllers/admin/contacts_controller.rb +++ b/app/controllers/admin/contacts_controller.rb @@ -26,7 +26,8 @@ def update def contact_params params.require(:contact).permit( :social_tag, :email, :facebook, :googleplus, :twitter, :instagram, - :mastodon, :public, :sponsor_email, :youtube, :blog) + :mastodon, :public, :sponsor_email, :youtube, :blog + ) end end end diff --git a/app/controllers/admin/currency_conversions_controller.rb b/app/controllers/admin/currency_conversions_controller.rb new file mode 100644 index 000000000..e74738223 --- /dev/null +++ b/app/controllers/admin/currency_conversions_controller.rb @@ -0,0 +1,58 @@ +module Admin + class CurrencyConversionsController < Admin::BaseController + load_and_authorize_resource :conference, find_by: :short_title + load_and_authorize_resource :currency_conversion, through: :conference + + # GET /currency_conversions + def index; end + + # GET /currency_conversions/1 + def show; end + + # GET /currency_conversions/new + def new + @currency_conversion = @conference.currency_conversions.new(conference_id: @conference.short_title) + end + + # GET /currency_conversions/1/edit + def edit; end + + # POST /currency_conversions + def create + @currency_conversion = @conference.currency_conversions.new(currency_conversion_params) + + if @currency_conversion.save + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully created.' + else + flash.now[:error] = 'Creating currency conversion failed.' + render :new + end + end + + # PATCH/PUT /currency_conversions/1 + def update + if @currency_conversion.update(currency_conversion_params) + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully updated.' + else + flash.now[:error] = 'Updating currency conversion failed.' + render :edit + end + end + + # DELETE /currency_conversions/1 + def destroy + if @currency_conversion.destroy + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Currency conversion was successfully deleted.' + else + redirect_to admin_conference_currency_conversions_path(@conference.short_title), notice: 'Deleting currency conversion failed.' + end + end + + private + + # Only allow a list of trusted parameters through. + def currency_conversion_params + params.require(:currency_conversion).permit(:from_currency, :to_currency, :rate) + end + end +end diff --git a/app/controllers/admin/difficulty_levels_controller.rb b/app/controllers/admin/difficulty_levels_controller.rb index e5717f7e8..009399f30 100644 --- a/app/controllers/admin/difficulty_levels_controller.rb +++ b/app/controllers/admin/difficulty_levels_controller.rb @@ -7,7 +7,7 @@ class DifficultyLevelsController < Admin::BaseController load_and_authorize_resource through: :program def index -# authorize! :index, DifficultyLevel.new(program_id: @program.id) + # authorize! :index, DifficultyLevel.new(program_id: @program.id) end def edit; end @@ -43,8 +43,8 @@ def destroy notice: 'Difficulty level successfully deleted.' else redirect_to admin_conference_program_difficulty_levels_path(conference_id: @conference.short_title), - error: 'Deleting difficulty level type failed! '\ - "#{@difficulty_level.errors.full_messages.join('. ')}." + error: 'Deleting difficulty level type failed! ' \ + "#{@difficulty_level.errors.full_messages.join('. ')}." end end diff --git a/app/controllers/admin/emails_controller.rb b/app/controllers/admin/emails_controller.rb index ba456efe3..b232a2638 100644 --- a/app/controllers/admin/emails_controller.rb +++ b/app/controllers/admin/emails_controller.rb @@ -8,11 +8,13 @@ class EmailsController < Admin::BaseController def update if @conference.email_settings.update(email_params) redirect_to admin_conference_emails_path( - @conference.short_title), + @conference.short_title + ), notice: 'Email settings have been successfully updated.' else redirect_to admin_conference_emails_path( - @conference.short_title), + @conference.short_title + ), error: "Updating email settings failed. #{@conference.email_settings.errors.to_a.join('. ')}." end end diff --git a/app/controllers/admin/event_schedules_controller.rb b/app/controllers/admin/event_schedules_controller.rb index 8068b41e9..a29f9098e 100644 --- a/app/controllers/admin/event_schedules_controller.rb +++ b/app/controllers/admin/event_schedules_controller.rb @@ -9,7 +9,8 @@ def create if @event_schedule.save render json: { event_schedule_id: @event_schedule.id } else - render json: { errors: "The event couldn't be scheduled. #{@event_schedule.errors.full_messages.join('. ')}" }, status: 422 + render json: { errors: "The event couldn't be scheduled. #{@event_schedule.errors.full_messages.join('. ')}" }, + status: :unprocessable_entity end end @@ -17,7 +18,8 @@ def update if @event_schedule.update(event_schedule_params) render json: { event_schedule_id: @event_schedule.id } else - render json: { errors: "The event couldn't be scheduled. #{@event_schedule.errors.full_messages.join('. ')}" }, status: 422 + render json: { errors: "The event couldn't be scheduled. #{@event_schedule.errors.full_messages.join('. ')}" }, + status: :unprocessable_entity end end @@ -25,7 +27,8 @@ def destroy if @event_schedule.destroy render json: {} else - render json: { errors: "The event couldn't be unscheduled. #{@event_schedule.errors.full_messages.join('. ')}" }, status: 422 + render json: { errors: "The event couldn't be unscheduled. #{@event_schedule.errors.full_messages.join('. ')}" }, + status: :unprocessable_entity end end diff --git a/app/controllers/admin/event_types_controller.rb b/app/controllers/admin/event_types_controller.rb index a171b4278..0cd2eed20 100644 --- a/app/controllers/admin/event_types_controller.rb +++ b/app/controllers/admin/event_types_controller.rb @@ -41,15 +41,16 @@ def destroy notice: 'Event type successfully deleted.' else redirect_to admin_conference_program_event_types_path(conference_id: @conference.short_title), - error: 'Destroying event type failed! '\ - "#{@event_type.errors.full_messages.join('. ')}." + error: 'Destroying event type failed! ' \ + "#{@event_type.errors.full_messages.join('. ')}." end end private def event_type_params - params.require(:event_type).permit(:title, :length, :minimum_abstract_length, :maximum_abstract_length, :color, :conference_id, :description) + params.require(:event_type).permit(:title, :length, :minimum_abstract_length, :maximum_abstract_length, + :submission_template, :color, :conference_id, :description) end end end diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index 7187940ec..adf1bde7a 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -2,6 +2,7 @@ module Admin class EventsController < Admin::BaseController + before_action :load_events_with_data, only: :index load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :event, through: :program @@ -9,7 +10,7 @@ class EventsController < Admin::BaseController # For some reason this doesn't work, so a workaround is used # load_and_authorize_resource :track, through: :program, only: [:index, :show, :edit] - before_action :assign_tracks, only: [:index, :show, :edit] + before_action :assign_tracks, only: %i[index show edit] def index @difficulty_levels = @program.difficulty_levels @@ -20,7 +21,7 @@ def index @scheduled_event_distribution = @conference.scheduled_event_distribution @file_name = "events_for_#{@conference.short_title}" @event_export_option = params[:event_export_option] - @export_formats = [:pdf, :csv, :xlsx] + @export_formats = %i[pdf csv xlsx] respond_to do |format| format.html @@ -45,10 +46,15 @@ def show @votes = @event.votes.includes(:user) @difficulty_levels = @program.difficulty_levels @versions = @event.versions | - PaperTrail::Version.where(item_type: 'Commercial').where('object LIKE ?', "%commercialable_id: #{@event.id}\ncommercialable_type: Event%") | - PaperTrail::Version.where(item_type: 'Commercial').where('object_changes LIKE ?', "%commercialable_id:\n- \n- #{@event.id}\ncommercialable_type:\n- \n- Event%") | - PaperTrail::Version.where(item_type: 'Vote').where('object_changes LIKE ?', "%\nevent_id:\n- \n- #{@event.id}\n%") | - PaperTrail::Version.where(item_type: 'Vote').where('object LIKE ?', "%\nevent_id: #{@event.id}\n%") + PaperTrail::Version.where(item_type: 'Commercial').where('object LIKE ?', + "%commercialable_id: #{@event.id}\ncommercialable_type: Event%") | + PaperTrail::Version.where(item_type: 'Commercial').where('object_changes LIKE ?', + "%commercialable_id:\n- \n- #{@event.id}\ncommercialable_type:\n- \n- Event%") | + PaperTrail::Version.where(item_type: 'Vote').where('object_changes LIKE ?', + "%\nevent_id:\n- \n- #{@event.id}\n%") | + PaperTrail::Version.where(item_type: 'Vote').where('object LIKE ?', "%\nevent_id: #{@event.id}\n%") | + PaperTrail::Version.where(item_type: 'EventUser').where('object_changes LIKE ?', + "%\nevent_id:\n-\n- #{@event.id}\n%") end def edit @@ -58,6 +64,7 @@ def edit @user = @event.submitter @url = admin_conference_program_event_path(@conference.short_title, @event) @languages = @program.languages_list + @superevents = @program.events.where(superevent: true) end def comment @@ -78,7 +85,7 @@ def update render js: 'index' else flash[:notice] = "Successfully updated event with ID #{@event.id}." - redirect_back_or_to(admin_conference_program_event_path(@conference.short_title, @event)) + redirect_to admin_conference_program_event_path(@conference.short_title, @event) end else @url = admin_conference_program_event_path(@conference.short_title, @event) @@ -91,9 +98,11 @@ def create @url = admin_conference_program_events_path(@conference.short_title, @event) @languages = @program.languages_list @event.submitter = current_user + @superevents = @program.events.where(superevent: true) if @event.save - redirect_to admin_conference_program_events_path(@conference.short_title), notice: 'Event was successfully submitted.' + redirect_to admin_conference_program_events_path(@conference.short_title), + notice: 'Event was successfully submitted.' else flash.now[:error] = "Could not submit proposal: #{@event.errors.full_messages.join(', ')}" render action: 'new' @@ -103,6 +112,7 @@ def create def new @url = admin_conference_program_events_path(@conference.short_title, @event) @languages = @program.languages_list + @superevents = @program.events.where(superevent: true) end def accept @@ -118,8 +128,10 @@ def confirm def cancel update_state(:cancel, 'Event canceled!') selected_schedule = @event.program.selected_schedule - event_schedule = EventSchedule.unscoped.where(event: @event).find_by(schedule: selected_schedule) if selected_schedule - Rails.logger.debug "schedule: #{selected_schedule.inspect} and event_schedule #{event_schedule.inspect}" + if selected_schedule + event_schedule = EventSchedule.unscoped.where(event: @event).find_by(schedule: selected_schedule) + end + Rails.logger.debug { "schedule: #{selected_schedule.inspect} and event_schedule #{event_schedule.inspect}" } if selected_schedule && event_schedule event_schedule.enabled = false event_schedule.save @@ -174,13 +186,15 @@ def toggle_attendance def event_params params.require(:event).permit( - # Set also in proposals controller - :title, :subtitle, :event_type_id, :abstract, :description, :require_registration, :difficulty_level_id, - # Set only in admin/events controller - :track_id, :state, :language, :is_highlight, :max_attendees, - # Not used anymore? - :proposal_additional_speakers, :user, :users_attributes, - speaker_ids: []) + # Set also in proposals controller + :title, :subtitle, :event_type_id, :abstract, :submission_text, :description, :require_registration, :difficulty_level_id, + :committee_review, :superevent, :parent_id, :presentation_mode, + # Set only in admin/events controller + :track_id, :state, :language, :is_highlight, :max_attendees, + # Not used anymore? + :proposal_additional_speakers, :user, :users_attributes, + speaker_ids: [], volunteer_ids: [] + ) end def comment_params @@ -195,12 +209,20 @@ def update_state(transition, notice, mail = false, subject = false, send_mail = redirect_back_or_to(admin_conference_program_events_path(conference_id: @conference.short_title)) && return else flash[:error] = alert - return redirect_back_or_to(admin_conference_program_events_path(conference_id: @conference.short_title)) && return + redirect_back_or_to(admin_conference_program_events_path(conference_id: @conference.short_title)) && return end end def assign_tracks @tracks = Track.accessible_by(current_ability).where(program: @program).confirmed end + + def load_events_with_data + @events = Event.where(program: Program.find_by(conference: Conference.find_by(short_title: params[:conference_id]))).includes( + :submitter, :submitter_event_user, :speakers, :speaker_event_users, :volunteers, + :volunteer_event_users, :program, :event_type, :track, :voters, + votes: [:user] + ) + end end end diff --git a/app/controllers/admin/lodgings_controller.rb b/app/controllers/admin/lodgings_controller.rb index 639e0544e..7e6d265c8 100644 --- a/app/controllers/admin/lodgings_controller.rb +++ b/app/controllers/admin/lodgings_controller.rb @@ -5,8 +5,7 @@ class LodgingsController < Admin::BaseController load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :lodging, through: :conference - def index - end + def index; end def new @lodging = @conference.lodgings.new @@ -42,7 +41,7 @@ def destroy else redirect_to admin_conference_lodgings_path(conference_id: @conference.short_title), error: 'Deleting lodging failed.' \ - "#{@lodging.errors.full_messages.join('. ')}." + "#{@lodging.errors.full_messages.join('. ')}." end end diff --git a/app/controllers/admin/organizations_controller.rb b/app/controllers/admin/organizations_controller.rb index 14ff040d3..f885edb8d 100644 --- a/app/controllers/admin/organizations_controller.rb +++ b/app/controllers/admin/organizations_controller.rb @@ -3,7 +3,7 @@ module Admin class OrganizationsController < Admin::BaseController load_and_authorize_resource :organization - before_action :verify_user, only: [:assign_org_admins, :unassign_org_admins] + before_action :verify_user, only: %i[assign_org_admins unassign_org_admins] def index @organizations = Organization.all @@ -85,7 +85,7 @@ def verify_user unless @user redirect_to admins_admin_organization_path(@organization), error: 'Could not find user. Please provide a valid email!' - return + nil end end diff --git a/app/controllers/admin/programs_controller.rb b/app/controllers/admin/programs_controller.rb index d47d4d1de..83cb6b1ce 100644 --- a/app/controllers/admin/programs_controller.rb +++ b/app/controllers/admin/programs_controller.rb @@ -12,7 +12,10 @@ def edit; end def update authorize! :update, @conference.program @program = @conference.program - params['program']['languages'] = params['program']['languages'].join(',') if params['program']['languages'].present? + if params['program']['languages'].present? + params['program']['languages'] = + params['program']['languages'].join(',') + end @program.assign_attributes(program_params) send_mail_on_schedule_public = @program.notify_on_schedule_public? event_schedules_count_was = @program.event_schedules.count @@ -22,7 +25,9 @@ def update respond_to do |format| format.html do notice = 'The program was successfully updated.' - notice += ' You changed schedule interval and some events were unscheduled.' if @program.event_schedules.count != event_schedules_count_was + if @program.event_schedules.count != event_schedules_count_was + notice += ' You changed schedule interval and some events were unscheduled.' + end redirect_to admin_conference_program_path(@conference.short_title), notice: notice end format.js { render json: {} } @@ -33,7 +38,10 @@ def update flash.now[:error] = "Updating program failed. #{@program.errors.to_a.join('. ')}." render :new end - format.js { render json: { errors: "The selected schedule couldn't be updated #{@program.errors.to_a.join('. ')}" }, status: 422 } + format.js do + render json: { errors: "The selected schedule couldn't be updated #{@program.errors.to_a.join('. ')}" }, + status: :unprocessable_entity + end end end end @@ -41,7 +49,8 @@ def update private def program_params - params.require(:program).permit(:rating, :schedule_public, :schedule_interval, :schedule_fluid, :blind_voting, :voting_start_date, :voting_end_date, :selected_schedule_id, :languages) + params.require(:program).permit(:rating, :schedule_public, :schedule_interval, :schedule_fluid, :blind_voting, + :voting_start_date, :voting_end_date, :selected_schedule_id, :languages) end end end diff --git a/app/controllers/admin/questions_controller.rb b/app/controllers/admin/questions_controller.rb index b6451c92d..75d2c0bc1 100644 --- a/app/controllers/admin/questions_controller.rb +++ b/app/controllers/admin/questions_controller.rb @@ -3,7 +3,7 @@ module Admin class QuestionsController < Admin::BaseController load_and_authorize_resource :conference, find_by: :short_title - load_and_authorize_resource except: [:new, :create] + load_and_authorize_resource except: %i[new create] def index authorize! :index, Question.new(conference_id: @conference.id) @@ -34,7 +34,10 @@ def create if @conference.save format.html { redirect_to admin_conference_questions_path, notice: 'Question was successfully created.' } else - format.html { redirect_to admin_conference_questions_path, error: "Oops, couldn't save Question. #{@question.errors.full_messages.join('. ')}" } + format.html do + redirect_to admin_conference_questions_path, + error: "Oops, couldn't save Question. #{@question.errors.full_messages.join('. ')}" + end end end end @@ -42,16 +45,19 @@ def create # GET questions/1/edit def edit if @question.global - redirect_to admin_conference_questions_path(conference_id: @conference.short_title), error: 'Sorry, you cannot edit global questions. Create a new one.' + redirect_to admin_conference_questions_path(conference_id: @conference.short_title), + error: 'Sorry, you cannot edit global questions. Create a new one.' end end # PUT questions/1 def update if @question.update(question_params) - redirect_to admin_conference_questions_path(conference_id: @conference.short_title), notice: "Question '#{@question.title}' for #{@conference.short_title} successfully updated." + redirect_to admin_conference_questions_path(conference_id: @conference.short_title), + notice: "Question '#{@question.title}' for #{@conference.short_title} successfully updated." else - redirect_to admin_conference_questions_path(conference_id: @conference.short_title), notice: "Update of questions for #{@conference.short_title} failed. #{@question.errors.full_messages.join('. ')}" + redirect_to admin_conference_questions_path(conference_id: @conference.short_title), + notice: "Update of questions for #{@conference.short_title} failed. #{@question.errors.full_messages.join('. ')}" end end @@ -59,9 +65,11 @@ def update def update_conference authorize! :update, Question.new(conference_id: @conference.id) if @conference.update(conference_params) - redirect_to admin_conference_questions_path(conference_id: @conference.short_title), notice: "Questions for #{@conference.short_title} successfully updated." + redirect_to admin_conference_questions_path(conference_id: @conference.short_title), + notice: "Questions for #{@conference.short_title} successfully updated." else - redirect_to admin_conference_questions_path(conference_id: @conference.short_title), notice: "Update of questions for #{@conference.short_title} failed." + redirect_to admin_conference_questions_path(conference_id: @conference.short_title), + notice: "Update of questions for #{@conference.short_title} failed." end end @@ -75,12 +83,13 @@ def destroy # Delete question and its answers begin Question.transaction do - @question.destroy @question.answers.each do |a| a.destroy end - flash[:notice] = "Deleted question: #{@question.title} and its answers: #{@question.answers.map {|a| a.title}.join ','}" + flash[:notice] = "Deleted question: #{@question.title} and its answers: #{@question.answers.map do |a| + a.title + end.join ','}" end rescue ActiveRecord::RecordInvalid flash[:error] = 'Could not delete question.' @@ -97,7 +106,8 @@ def destroy private def question_params - params.require(:question).permit(:title, :global, :answer_ids, :question_type_id, :conference_id, answers_attributes: [:id, :title]) + params.require(:question).permit(:title, :global, :answer_ids, :question_type_id, :conference_id, + answers_attributes: %i[id title]) end def conference_params diff --git a/app/controllers/admin/registration_periods_controller.rb b/app/controllers/admin/registration_periods_controller.rb index d08847197..f963aa789 100644 --- a/app/controllers/admin/registration_periods_controller.rb +++ b/app/controllers/admin/registration_periods_controller.rb @@ -18,7 +18,8 @@ def create redirect_to admin_conference_registration_period_path(@conference.short_title), notice: 'Registration Period successfully updated.' else - flash.now[:error] = "An error prohibited the Registration Period from being saved: #{@registration_period.errors.full_messages.join('. ')}." + flash.now[:error] = + "An error prohibited the Registration Period from being saved: #{@registration_period.errors.full_messages.join('. ')}." render :new end end @@ -33,7 +34,7 @@ def update notice: 'Registration Period successfully updated.' else flash.now[:error] = 'An error prohibited the Registration Period from being saved: ' \ - "#{@registration_period.errors.full_messages.join('. ')}." + "#{@registration_period.errors.full_messages.join('. ')}." render :edit end end diff --git a/app/controllers/admin/registrations_controller.rb b/app/controllers/admin/registrations_controller.rb index 88027bbd2..03b5662fe 100644 --- a/app/controllers/admin/registrations_controller.rb +++ b/app/controllers/admin/registrations_controller.rb @@ -44,8 +44,8 @@ def update redirect_to admin_conference_registrations_path(@conference.short_title), notice: "Successfully updated registration for #{@registration.user.email}!" else - flash.now[:error] = "An error prohibited the Registration for #{@registration.user.email}: "\ - "#{@registration.errors.full_messages.join('. ')}." + flash.now[:error] = "An error prohibited the Registration for #{@registration.user.email}: " \ + "#{@registration.errors.full_messages.join('. ')}." render :edit end end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 28f4f94f0..3f92ed547 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -8,17 +8,18 @@ class ReportsController < Admin::BaseController # load_and_authorize_resource :event, through: :program def index - @events = Event.accessible_by(current_ability).where(program: @program) + @events = Event.accessible_by(current_ability).where(program: @program, + state: %i[confirmed unconfirmed]) @events_commercials = Commercial.where(commercialable_type: 'Event', commercialable_id: @events.pluck(:id)) @events_missing_commercial = @events.where.not(id: @events_commercials.pluck(:commercialable_id)) @events_with_requirements = @events.where.not(description: ['', nil]) attended_registrants_ids = @conference.registrations.where(attended: true).pluck(:user_id) @missing_event_speakers = EventUser.joins(:event) - .where('event_role = ? and program_id = ?', 'speaker', @program.id) - .where.not(user_id: attended_registrants_ids) - .where(event_id: @events.pluck(:id)) - .includes(:user, :event) + .where('event_role = ? and program_id = ?', 'speaker', @program.id) + .where.not(user_id: attended_registrants_ids) + .where(event_id: @events.pluck(:id)) + .includes(:user, :event) end end end diff --git a/app/controllers/admin/resources_controller.rb b/app/controllers/admin/resources_controller.rb index a6e51e99f..88db23bd5 100644 --- a/app/controllers/admin/resources_controller.rb +++ b/app/controllers/admin/resources_controller.rb @@ -3,7 +3,7 @@ module Admin class ResourcesController < Admin::BaseController load_and_authorize_resource :conference, find_by: :short_title - load_and_authorize_resource :resource, only: [:show, :edit, :update, :destroy] + load_and_authorize_resource :resource, only: %i[show edit update destroy] def index; end @@ -41,7 +41,7 @@ def destroy else redirect_to admin_conference_resources_path(conference_id: @conference.short_title), error: 'Resource was successfully destroyed.' \ - "#{@resource.errors.full_messages.join('. ')}." + "#{@resource.errors.full_messages.join('. ')}." end end diff --git a/app/controllers/admin/rooms_controller.rb b/app/controllers/admin/rooms_controller.rb index af83d5cb2..a2578136a 100644 --- a/app/controllers/admin/rooms_controller.rb +++ b/app/controllers/admin/rooms_controller.rb @@ -48,7 +48,9 @@ def destroy private def room_params - params.require(:room).permit(:name, :size) + params.require(:room) + .permit(:name, :size, :url, :order, :discussion_url) + .each_value { |value| value.try(:strip!) } end end end diff --git a/app/controllers/admin/schedules_controller.rb b/app/controllers/admin/schedules_controller.rb index 71e7382a7..f3176245c 100644 --- a/app/controllers/admin/schedules_controller.rb +++ b/app/controllers/admin/schedules_controller.rb @@ -6,7 +6,7 @@ class SchedulesController < Admin::BaseController # the schedule of a conference, which should not be accessed in the first place load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :program, through: :conference, singleton: true - load_and_authorize_resource :schedule, through: :program, except: [:new, :create] + load_and_authorize_resource :schedule, through: :program, except: %i[new create] load_resource :event_schedules, through: :schedule load_resource :selected_schedule, through: :program, singleton: true load_resource :venue, through: :conference, singleton: true @@ -37,9 +37,10 @@ def show :difficulty_level, :track, :event_type, - event_users: :user + { event_users: :user } ] ) + @event_types = @program.event_types || [] if @schedule.track track = @schedule.track @@ -51,7 +52,7 @@ def show @event_schedules += t.selected_schedule.event_schedules if t.selected_schedule end self_organized_tracks_events = Event.eager_load(event_users: :user).confirmed.where(track: @program.tracks.self_organized.confirmed) - @unscheduled_events = @program.events.confirmed - @schedule.events - self_organized_tracks_events + @unscheduled_events = (@program.events.confirmed + @program.events.unconfirmed) - @schedule.events - self_organized_tracks_events @dates = @conference.start_date..@conference.end_date @rooms = @conference.venue.rooms if @conference.venue end diff --git a/app/controllers/admin/splashpages_controller.rb b/app/controllers/admin/splashpages_controller.rb index 6b40d0d14..7e50bb22b 100644 --- a/app/controllers/admin/splashpages_controller.rb +++ b/app/controllers/admin/splashpages_controller.rb @@ -37,8 +37,8 @@ def destroy if @splashpage.destroy redirect_to admin_conference_splashpage_path, notice: 'Splashpage was successfully destroyed.' else - redirect_to admin_conference_splashpage_path, error: 'An error prohibited this Splashpage from being destroyed: '\ - "#{@splashpage.errors.full_messages.join('. ')}." + redirect_to admin_conference_splashpage_path, error: 'An error prohibited this Splashpage from being destroyed: ' \ + "#{@splashpage.errors.full_messages.join('. ')}." end end @@ -46,11 +46,12 @@ def destroy def splashpage_params params.require(:splashpage).permit(:public, + :banner_photo, :banner_photo_cache, :include_tracks, :include_program, :include_cfp, :include_venue, :include_registrations, :include_tickets, :include_lodgings, :include_sponsors, :include_social_media, - :include_booths) + :include_booths, :include_happening_now) end end end diff --git a/app/controllers/admin/sponsors_controller.rb b/app/controllers/admin/sponsors_controller.rb index bc61d8342..7aca6794f 100644 --- a/app/controllers/admin/sponsors_controller.rb +++ b/app/controllers/admin/sponsors_controller.rb @@ -4,7 +4,7 @@ module Admin class SponsorsController < Admin::BaseController load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :sponsor, through: :conference - before_action :sponsorship_level_required, only: [:index, :new] + before_action :sponsorship_level_required, only: %i[index new] def index authorize! :index, Sponsor.new(conference_id: @conference.id) @@ -30,7 +30,8 @@ def create def update if @sponsor.update(sponsor_params) redirect_to admin_conference_sponsors_path( - conference_id: @conference.short_title), + conference_id: @conference.short_title + ), notice: 'Sponsor successfully updated.' else flash.now[:error] = "Update sponsor failed: #{@sponsor.errors.full_messages.join('. ')}." @@ -45,14 +46,15 @@ def destroy else redirect_to admin_conference_sponsors_path(conference_id: @conference.short_title), error: 'Deleting sponsor failed! ' \ - "#{@sponsor.errors.full_messages.join('. ')}." + "#{@sponsor.errors.full_messages.join('. ')}." end end private def sponsor_params - params.require(:sponsor).permit(:name, :description, :website_url, :picture, :picture_cache, :sponsorship_level_id, :conference_id) + params.require(:sponsor).permit(:name, :description, :website_url, :picture, :picture_cache, + :sponsorship_level_id, :conference_id) end def sponsorship_level_required diff --git a/app/controllers/admin/sponsorship_levels_controller.rb b/app/controllers/admin/sponsorship_levels_controller.rb index 443aa8b96..d7515187f 100644 --- a/app/controllers/admin/sponsorship_levels_controller.rb +++ b/app/controllers/admin/sponsorship_levels_controller.rb @@ -29,7 +29,8 @@ def create def update if @sponsorship_level.update(sponsorship_level_params) redirect_to admin_conference_sponsorship_levels_path( - conference_id: @conference.short_title), + conference_id: @conference.short_title + ), notice: 'Sponsorship level successfully updated.' else flash.now[:error] = "Update Sponsorship level failed: #{@sponsorship_level.errors.full_messages.join('. ')}." @@ -44,7 +45,7 @@ def destroy else redirect_to admin_conference_sponsorship_levels_path(conference_id: @conference.short_title), error: 'Deleting sponsorship level failed! ' \ - "#{@sponsorship_level.errors.full_messages.join('. ')}." + "#{@sponsorship_level.errors.full_messages.join('. ')}." end end diff --git a/app/controllers/admin/survey_questions_controller.rb b/app/controllers/admin/survey_questions_controller.rb index 61febf1da..187815848 100644 --- a/app/controllers/admin/survey_questions_controller.rb +++ b/app/controllers/admin/survey_questions_controller.rb @@ -14,7 +14,8 @@ def new def create @survey_question = @survey.survey_questions.new(survey_question_params) if @survey_question.save - redirect_to admin_conference_survey_path(@conference.short_title, @survey), notice: 'Successfully created Survey Question.' + redirect_to admin_conference_survey_path(@conference.short_title, @survey), + notice: 'Successfully created Survey Question.' else @url = admin_conference_survey_survey_questions_path(@conference.short_title, @survey) render :new @@ -29,7 +30,8 @@ def edit # PUT questions/1 def update if @survey_question.update(survey_question_params) - redirect_to admin_conference_survey_path(@conference.short_title, @survey), notice: 'Successfully updated Survey Question.' + redirect_to admin_conference_survey_path(@conference.short_title, @survey), + notice: 'Successfully updated Survey Question.' else @url = admin_conference_survey_survey_question_path(@conference.short_title, @survey, @survey_question) render :edit @@ -39,9 +41,11 @@ def update # DELETE questions/1 def destroy if @survey_question.destroy - redirect_to admin_conference_survey_path(@conference.short_title, @survey), notice: 'Successfully deleted Survey Question.' + redirect_to admin_conference_survey_path(@conference.short_title, @survey), + notice: 'Successfully deleted Survey Question.' else - redirect_to admin_conference_survey_path(@conference.short_title, @survey), error: "Can't delete this Survey Question" + redirect_to admin_conference_survey_path(@conference.short_title, @survey), + error: "Can't delete this Survey Question" end end diff --git a/app/controllers/admin/surveys_controller.rb b/app/controllers/admin/surveys_controller.rb index 8054cc446..e63c56517 100644 --- a/app/controllers/admin/surveys_controller.rb +++ b/app/controllers/admin/surveys_controller.rb @@ -17,9 +17,11 @@ def new def create @survey = Survey.new(survey_params) if @survey.save - redirect_to new_admin_conference_survey_survey_question_path(@conference.short_title, @survey), notice: 'Successfully created survey' + redirect_to new_admin_conference_survey_survey_question_path(@conference.short_title, @survey), + notice: 'Successfully created survey' else - redirect_to new_admin_conference_survey_path(@conference.short_title, survey: { surveyable_type: survey_params['surveyable_type'], surveyable_id: survey_params['surveyable_id'] }), error: 'Could not create survey.' + @survey.errors.full_messages.to_sentence + redirect_to new_admin_conference_survey_path(@conference.short_title, survey: { surveyable_type: survey_params['surveyable_type'], surveyable_id: survey_params['surveyable_id'] }), + error: 'Could not create survey.' + @survey.errors.full_messages.to_sentence end end @@ -44,7 +46,8 @@ def destroy private def survey_params - params.require(:survey).permit(:title, :description, :target, :start_date, :end_date, :surveyable_type, :surveyable_id) + params.require(:survey).permit(:title, :description, :target, :start_date, :end_date, :surveyable_type, + :surveyable_id) end end end diff --git a/app/controllers/admin/ticket_scannings_controller.rb b/app/controllers/admin/ticket_scannings_controller.rb index f66371116..82b8be0b1 100644 --- a/app/controllers/admin/ticket_scannings_controller.rb +++ b/app/controllers/admin/ticket_scannings_controller.rb @@ -4,14 +4,20 @@ module Admin class TicketScanningsController < Admin::BaseController before_action :authenticate_user! load_resource :physical_ticket, find_by: :token - # We authorize manually in these actions skip_authorize_resource only: [:create] def create + if !@physical_ticket && params[:physical_ticket] + @physical_ticket = PhysicalTicket.find_by(token: params[:physical_ticket][:token]) + end @ticket_scanning = TicketScanning.new(physical_ticket: @physical_ticket) authorize! :create, @ticket_scanning @ticket_scanning.save - redirect_to conferences_path, + dest_path = conferences_path + if request.referer&.match?(%r{admin/conferences}) + dest_path = admin_conference_physical_tickets_path(conference_id: @conference.short_title) + end + redirect_to dest_path, notice: "Ticket with token #{@physical_ticket.token} successfully scanned." end end diff --git a/app/controllers/admin/tickets_controller.rb b/app/controllers/admin/tickets_controller.rb index e0e5b1868..74299c89c 100644 --- a/app/controllers/admin/tickets_controller.rb +++ b/app/controllers/admin/tickets_controller.rb @@ -38,21 +38,66 @@ def update end end + def give + message = '' + ticket_purchase = @ticket.ticket_purchases.new(gift_ticket_params) + recipient = ticket_purchase.user + old_ticket_purchases = TicketPurchase.unpaid.by_conference(@conference) + .where( + user_id: gift_ticket_params[:user_id], + ticket_id: @conference.registration_tickets + ) + # We need to cancel any in progress ticket purchases + # TODO-SNAPCON: Add tests, update existing DB records? Add pluralize + if old_ticket_purchases.any? + message = "(Removed #{old_ticket_purchases.count} unpaid ticket)." + old_ticket_purchases.destroy_all + end + if ticket_purchase.save + # We must pay for a ticket purchase to create a physical ticket. + # Because there is no CC xact, the Payment does not need to be saved. + ticket_purchase.pay(Payment.new) + registration = @conference.register_user(recipient) if @ticket.registration_ticket? + redirect_to( + admin_conference_ticket_path(@conference.short_title, @ticket), + notice: "#{recipient.name} was given a #{@ticket.title} ticket #{if registration + 'and registered' + end}. #{message}" + ) + else + redirect_back( + fallback_location: admin_conference_ticket_path(@conference.short_title, @ticket), + error: "Unable to give #{recipient.name} a #{@ticket.title} ticket: " + + ticket_purchase.errors.full_messages.to_sentence + ) + end + end + def destroy if @ticket.destroy redirect_to admin_conference_tickets_path(conference_id: @conference.short_title), - notice: 'Ticket successfully destroyed.' + notice: 'Ticket successfully deleted.' else redirect_to admin_conference_tickets_path(conference_id: @conference.short_title), - error: 'Ticket was successfully destroyed.' \ - "#{@ticket.errors.full_messages.join('. ')}." + error: 'Deleting ticket failed! ' \ + "#{@ticket.errors.full_messages.join('. ')}." end end private def ticket_params - params.require(:ticket).permit(:conference, :title, :url, :description, :conference_id, :price_cents, :price_currency, :price, :registration_ticket) + params.require(:ticket).permit( + :conference, :conference_id, + :title, :url, :description, :email_subject, :email_body, + :price_cents, :price_currency, :price, + :registration_ticket, :visible + ) + end + + def gift_ticket_params + response = params.require(:ticket_purchase).permit(:user_id) + response.merge(paid: true, amount_paid: 0, conference: @conference) end end end diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb index 77de53f49..fc30e9f20 100644 --- a/app/controllers/admin/tracks_controller.rb +++ b/app/controllers/admin/tracks_controller.rb @@ -128,7 +128,10 @@ def update_selected_schedule end else respond_to do |format| - format.js { render json: { errors: "The selected schedule couldn't be updated #{@track.errors.to_a.join('. ')}" }, status: 422 } + format.js do + render json: { errors: "The selected schedule couldn't be updated #{@track.errors.to_a.join('. ')}" }, + status: :unprocessable_entity + end end end end @@ -136,7 +139,8 @@ def update_selected_schedule private def track_params - params.require(:track).permit(:name, :description, :color, :short_name, :cfp_active, :start_date, :end_date, :room_id) + params.require(:track).permit(:name, :description, :color, :short_name, :cfp_active, :start_date, :end_date, + :room_id) end def update_state(transition, notice) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9820cef16..fe260bc58 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -43,32 +43,43 @@ def show # Variable @show_attributes holds the attributes that are visible for the 'show' action # If you want to change the attributes that are shown in the 'show' action of users # add/remove the attributes in the following string array - @show_attributes = %w(name email username nickname affiliation biography registered attended roles created_at + @show_attributes = %w[name email username nickname affiliation biography + profile_picture registered attended roles created_at updated_at sign_in_count current_sign_in_at last_sign_in_at - current_sign_in_ip last_sign_in_ip) + current_sign_in_ip last_sign_in_ip] end def update message = '' - if params[:user] && !params[:user][:email].nil? - if (new_email = params[:user][:email]) != @user.email - message = " Confirmation email sent to #{new_email}. The new email needs to be confirmed before it can be used." - end + if params[:user] && !params[:user][:email].nil? && ((new_email = params[:user][:email]) != @user.email) + message = " Confirmation email sent to #{new_email}. The new email needs to be confirmed before it can be used." end if @user.update(user_params) redirect_to admin_users_path, notice: "Updated #{@user.name} (#{@user.email})!" + message else - redirect_to admin_users_path, error: "Could not update #{@user.name} (#{@user.email}). #{@user.errors.full_messages.join('. ')}." + redirect_to admin_users_path, + error: "Could not update #{@user.name} (#{@user.email}). #{@user.errors.full_messages.join('. ')}." end end def edit; end + def destroy + if @user.destroy + redirect_to admin_users_path, + notice: "User #{@user.id} (#{@user.email}) deleted." + else + redirect_to admin_users_path, + error: "User #{@user.id} (#{@user.emai}) could not be deleted. #{@user.full_messages.join(',')}" + end + end + private def user_params - params.require(:user).permit(:email, :name, :email_public, :biography, :nickname, :affiliation, :is_admin, + params.require(:user).permit(:email, :name, :email_public, :biography, :nickname, + :affiliation, :is_admin, :picture, :picture_cache, :username, :login, :is_disabled, :tshirt, :mobile, :volunteer_experience, :languages, :to_confirm, :password, role_ids: []) end diff --git a/app/controllers/admin/venue_commercials_controller.rb b/app/controllers/admin/venue_commercials_controller.rb index ddd13aa9c..11a87d199 100644 --- a/app/controllers/admin/venue_commercials_controller.rb +++ b/app/controllers/admin/venue_commercials_controller.rb @@ -12,11 +12,11 @@ def create if @commercial.save redirect_to admin_conference_venue_path, - notice: 'Commercial was successfully created.' + notice: 'Materials successfully created.' else redirect_to admin_conference_venue_path, - error: 'An error prohibited this Commercial from being saved: '\ - "#{@commercial.errors.full_messages.join('. ')}." + error: 'An error prohibited materials from being saved: ' \ + "#{@commercial.errors.full_messages.join('. ')}." end end @@ -24,23 +24,23 @@ def create def update if @commercial.update(commercial_params) redirect_to admin_conference_venue_path, - notice: 'Commercial was successfully updated.' + notice: 'Materials successfully updated.' else redirect_to admin_conference_venue_path, - error: 'An error prohibited this Commercial from being saved: '\ - "#{@commercial.errors.full_messages.join('. ')}." + error: 'An error prohibited materials from being saved: ' \ + "#{@commercial.errors.full_messages.join('. ')}." end end def destroy @commercial.destroy - redirect_to admin_conference_venue_path, notice: 'Commercial was successfully destroyed.' + redirect_to admin_conference_venue_path, notice: 'Materials successfully destroyed.' end def render_commercial result = Commercial.render_from_url(params[:url]) if result[:error] - render plain: result[:error], status: 400 + render plain: result[:error], status: :bad_request else render plain: result[:html] end @@ -49,7 +49,7 @@ def render_commercial private def commercial_params - params.require(:commercial).permit(:url) + params.require(:commercial).permit(:title, :url) end end end diff --git a/app/controllers/admin/venues_controller.rb b/app/controllers/admin/venues_controller.rb index 17ba4c992..a39d18058 100644 --- a/app/controllers/admin/venues_controller.rb +++ b/app/controllers/admin/venues_controller.rb @@ -38,15 +38,16 @@ def destroy if @venue.destroy redirect_to admin_conference_venue_path, notice: 'Venue was successfully deleted.' else - redirect_to admin_conference_venue_path, error: 'An error prohibited this Venue from being destroyed: '\ - "#{@venue.errors.full_messages.join('. ')}." + redirect_to admin_conference_venue_path, error: 'An error prohibited this Venue from being destroyed: ' \ + "#{@venue.errors.full_messages.join('. ')}." end end private def venue_params - params.require(:venue).permit(:name, :street, :postalcode, :city, :country, :longitude, :latitude, :description, :website, :picture, :picture_cache, :lodgings_attributes, :conference_id) + params.require(:venue).permit(:name, :street, :postalcode, :city, :country, :longitude, :latitude, :description, + :website, :picture, :picture_cache, :lodgings_attributes, :conference_id) end end end diff --git a/app/controllers/admin/versions_controller.rb b/app/controllers/admin/versions_controller.rb index 1bc7ae2ab..723c1d972 100644 --- a/app/controllers/admin/versions_controller.rb +++ b/app/controllers/admin/versions_controller.rb @@ -6,10 +6,18 @@ class VersionsController < Admin::BaseController load_and_authorize_resource class: PaperTrail::Version def index - @conferences_with_role = current_user.is_admin? ? Conference.pluck(:short_title) : Conference.with_role([:organizer, :cfp, :info_desk], current_user).pluck(:short_title) + @conferences_with_role = if current_user.is_admin? + Conference.pluck(:short_title) + else + Conference.with_role( + %i[organizer cfp info_desk], current_user + ).pluck(:short_title) + end if current_user.has_cached_role? :organization_admin, :any - @conferences_with_role = Organization.with_role('organization_admin', current_user).map { |org| org.conferences.pluck :short_title }.flatten + @conferences_with_role = Organization.with_role('organization_admin', current_user).map do |org| + org.conferences.pluck :short_title + end.flatten end @conferences_with_role.uniq! @@ -19,7 +27,9 @@ def index end def revert_attribute - if params[:attribute] && @version.changeset.reject{ |_, values| values[0].blank? && values[1].blank? }.keys.include?(params[:attribute]) + if params[:attribute] && @version.changeset.reject do |_, values| + values[0].blank? && values[1].blank? + end.keys.include?(params[:attribute]) if @version.item[params[:attribute]] == @version.changeset[params[:attribute]][0] flash[:error] = 'The item is already in the state that you are trying to revert it back to' @@ -28,7 +38,8 @@ def revert_attribute if @version.item.save flash[:notice] = 'The selected change was successfully reverted' else - flash[:error] = "An error prohibited this change from being reverted: #{@version.item.errors.full_messages.join('. ')}." + flash[:error] = + "An error prohibited this change from being reverted: #{@version.item.errors.full_messages.join('. ')}." end end @@ -44,7 +55,8 @@ def revert_object if @version.reify.save flash[:notice] = 'The selected change was successfully reverted' else - flash[:error] = "An error prohibited this change from being reverted: #{@version.reify.errors.full_messages.join('. ')}." + flash[:error] = + "An error prohibited this change from being reverted: #{@version.reify.errors.full_messages.join('. ')}." end elsif @version.event == 'create' && @version.item diff --git a/app/controllers/admin/volunteers_controller.rb b/app/controllers/admin/volunteers_controller.rb index b0b85ffcd..c93ee9721 100644 --- a/app/controllers/admin/volunteers_controller.rb +++ b/app/controllers/admin/volunteers_controller.rb @@ -27,9 +27,11 @@ def show def update if @conference.update(conference_params) - redirect_to admin_conference_volunteers_info_path(conference_id: params[:conference_id]), notice: 'Volunteering options were successfully updated.' + redirect_to admin_conference_volunteers_info_path(conference_id: params[:conference_id]), + notice: 'Volunteering options were successfully updated.' else - redirect_to admin_conference_volunteers_info_path(conference_id: params[:conference_id]), error: "Volunteering options update failed: #{@conference.errors.full_messages.join '. '}" + redirect_to admin_conference_volunteers_info_path(conference_id: params[:conference_id]), + error: "Volunteering options update failed: #{@conference.errors.full_messages.join '. '}" end end diff --git a/app/controllers/api/v1/events_controller.rb b/app/controllers/api/v1/events_controller.rb index e818a780c..0577eb417 100644 --- a/app/controllers/api/v1/events_controller.rb +++ b/app/controllers/api/v1/events_controller.rb @@ -12,9 +12,7 @@ class EventsController < Api::BaseController def index events = Event.includes(:track, :event_type, event_users: :user) - if @conference - events = events.where(program: @conference.program) - end + events = events.where(program: @conference.program) if @conference respond_with events.confirmed, callback: params[:callback] end diff --git a/app/controllers/api/v1/speakers_controller.rb b/app/controllers/api/v1/speakers_controller.rb index ea76a32bc..aae10079f 100644 --- a/app/controllers/api/v1/speakers_controller.rb +++ b/app/controllers/api/v1/speakers_controller.rb @@ -11,13 +11,13 @@ class SpeakersController < Api::BaseController def index if @conference - users = User.joins(event_users: { event: { program: :conference} }) + users = User.joins(event_users: { event: { program: :conference } }) users = users.where(conferences: { short_title: @conference.short_title }) else users = User.joins(:event_users) end - users = users.where(event_users: {event_role: :speaker}).uniq + users = users.where(event_users: { event_role: :speaker }).uniq render json: users, each_serializer: SpeakerSerializer, callback: params['callback'], diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 93105587a..672afae83 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,34 +3,31 @@ class ApplicationController < ActionController::Base before_action :set_paper_trail_whodunnit include ApplicationHelper + include Pagy::Backend add_flash_types :error protect_from_forgery with: :exception, prepend: true before_action :store_location + before_action :set_sentry_user # Ensure every controller authorizes resource or skips authorization (skip_authorization_check) check_authorization unless: :devise_controller? + skip_authorization_check if: - def store_location - # store last url - this is needed for post-login redirect to whatever the user last visited. - return unless request.get? + def store_location + # store last url - this is needed for post-login redirect to whatever the user last visited. + return unless request.get? && !request.xhr? - if (request.path != '/accounts/sign_in' && - request.path != '/accounts/sign_up' && - request.path != '/accounts/password/new' && - request.path != '/accounts/password/edit' && - request.path != '/accounts/confirmation' && - request.path != '/accounts/sign_out' && - request.path != '/users/ichain_registration/ichain_sign_up' && - !request.path.starts_with?(Devise.ichain_base_url) && - !request.xhr?) # don't store ajax calls - session[:return_to] = request.fullpath - end - end + if !request.path.starts_with?('/accounts') && + request.path != '/users/ichain_registration/ichain_sign_up' && + !request.path.starts_with?(Devise.ichain_base_url) + session[:return_to] = request.fullpath + end + end def after_sign_in_path_for(_resource) if (can? :view, Conference) && - (!session[:return_to] || - session[:return_to] && - session[:return_to] == root_path) + (!session[:return_to] || + (session[:return_to] && + session[:return_to] == root_path)) admin_conferences_path else session[:return_to] || root_path @@ -42,7 +39,7 @@ def current_ability end rescue_from CanCan::AccessDenied do |exception| - Rails.logger.debug "Access denied on #{exception.action} #{exception.subject.inspect}" + Rails.logger.debug { "Access denied on #{exception.action} #{exception.subject.inspect}" } message = exception.message message << ' Maybe you need to sign in?' unless @ignore_not_signed_in_user || current_user redirect_to root_path, alert: message @@ -59,10 +56,21 @@ def current_ability Rails.logger.debug('User is disabled!') sign_out(current_user) mail = User.admin.first ? User.admin.first.email : 'the admin!' - redirect_to User.ichain_logout_url, error: "This User is disabled. Please contact #{mail}!" + redirect_to User.ichain_logout_url, error: "This User is disabled. Please contact #{mail}!" end def not_found raise ActionController::RoutingError.new('Not Found') end + + skip_authorization_check only: :apple_pay + def apple_pay + render plain: ENV.fetch('OSEM_APPLE_PAY_ID', '') + end + + def set_sentry_user + return unless current_user + + Sentry.set_user(email: current_user.email, id: current_user.id, username: current_user.username) + end end diff --git a/app/controllers/booths_controller.rb b/app/controllers/booths_controller.rb index f80576377..92db93b16 100644 --- a/app/controllers/booths_controller.rb +++ b/app/controllers/booths_controller.rb @@ -4,7 +4,7 @@ class BoothsController < ApplicationController before_action :authenticate_user! load_resource :conference, find_by: :short_title load_and_authorize_resource through: :conference - skip_authorize_resource only: [:withdraw, :confirm, :restart] + skip_authorize_resource only: %i[withdraw confirm restart] def index @booths = current_user.booths.where(conference_id: @conference.id).uniq diff --git a/app/controllers/commercials_controller.rb b/app/controllers/commercials_controller.rb index aa48a4064..40f292159 100644 --- a/app/controllers/commercials_controller.rb +++ b/app/controllers/commercials_controller.rb @@ -12,33 +12,33 @@ def create if @commercial.save redirect_to edit_conference_program_proposal_path(conference_id: @conference.short_title, id: @event.id, anchor: 'commercials-content'), - notice: 'Commercial was successfully created.' + notice: 'Materials were successfully created.' else redirect_to edit_conference_program_proposal_path(conference_id: @conference.short_title, id: @event.id, anchor: 'commercials-content'), - error: "An error prohibited this Commercial from being saved: #{@commercial.errors.full_messages.join('. ')}." + error: "An error prohibited these materials from being saved: #{@commercial.errors.full_messages.join('. ')}." end end def update if @commercial.update(commercial_params) redirect_to edit_conference_program_proposal_path(conference_id: @conference.short_title, id: @event.id, anchor: 'commercials-content'), - notice: 'Commercial was successfully updated.' + notice: 'Materials were successfully updated.' else redirect_to edit_conference_program_proposal_path(conference_id: @conference.short_title, id: @event.id, anchor: 'commercials-content'), - error: "An error prohibited this Commercial from being saved: #{@commercial.errors.full_messages.join('. ')}." + error: "An error prohibited materials from being saved: #{@commercial.errors.full_messages.join('. ')}." end end def destroy @commercial.destroy redirect_to edit_conference_program_proposal_path(conference_id: @conference.short_title, id: @event.id), - notice: 'Commercial was successfully destroyed.' + notice: 'Materials were successfully destroyed.' end def render_commercial result = Commercial.render_from_url(params[:url]) if result[:error] - render plain: result[:error], status: 400 + render plain: result[:error], status: :bad_request else render plain: result[:html] end @@ -51,6 +51,6 @@ def set_event end def commercial_params - params.require(:commercial).permit(:url) + params.require(:commercial).permit(:title, :url) end end diff --git a/app/controllers/conference_registrations_controller.rb b/app/controllers/conference_registrations_controller.rb index 70cfc7362..8df31ea05 100644 --- a/app/controllers/conference_registrations_controller.rb +++ b/app/controllers/conference_registrations_controller.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true class ConferenceRegistrationsController < ApplicationController - before_action :authenticate_user!, except: [:new, :create] + before_action :authenticate_user!, except: %i[new create] load_resource :conference, find_by: :short_title - authorize_resource :conference_registrations, class: Registration, except: [:new, :create] - before_action :set_registration, only: [:edit, :update, :destroy, :show] + authorize_resource :conference_registrations, class: Registration, except: %i[new create] + before_action :set_registration, only: %i[edit update destroy show] def new @registration = Registration.new(conference_id: @conference.id) @@ -53,20 +53,23 @@ def create if @registration.save # Sign in the new user - unless current_user - sign_in(@registration.user) - end + sign_in(@registration.user) unless current_user + + MailblusterEditLeadJob.perform_later(@user.id, add_tags: ["snapcon-#{@conference.short_title}"]) - if @conference.tickets.any? && !current_user.supports?(@conference) + if @conference.tickets.visible.any? && !current_user.supports?(@conference) redirect_to conference_tickets_path(@conference.short_title), notice: 'You are now registered and will be receiving E-Mail notifications.' else redirect_to conference_conference_registration_path(@conference.short_title), notice: 'You are now registered and will be receiving E-Mail notifications.' end + elsif @conference.registration_ticket_required? && !current_user&.supports?(@conference) + redirect_to conference_tickets_path(@conference.short_title), + notice: 'You must buy a registration ticket before registering.' else - flash.now[:error] = "Could not create your registration for #{@conference.title}: "\ - "#{@registration.errors.full_messages.join('. ')}." + flash.now[:error] = "Could not create your registration for #{@conference.title}: " \ + "#{@registration.errors.full_messages.join('. ')}." render :new end end @@ -76,20 +79,21 @@ def update redirect_to conference_conference_registration_path(@conference.short_title), notice: 'Registration was successfully updated.' else - flash.now[:error] = "Could not update your registration for #{@conference.title}: "\ - "#{@registration.errors.full_messages.join('. ')}." + flash.now[:error] = "Could not update your registration for #{@conference.title}: " \ + "#{@registration.errors.full_messages.join('. ')}." render :edit end end def destroy if @registration.destroy + MailblusterEditLeadJob.perform_later(current_user.id, remove_tags: ["snapcon-#{@conference.short_title}"]) redirect_to root_path, notice: "You are not registered for #{@conference.title} anymore!" else redirect_to conference_conference_registration_path(@conference.short_title), - error: "Could not delete your registration for #{@conference.title}: "\ - "#{@registration.errors.full_messages.join('. ')}." + error: "Could not delete your registration for #{@conference.title}: " \ + "#{@registration.errors.full_messages.join('. ')}." end end @@ -109,14 +113,15 @@ def user_params def registration_params params.require(:registration) - .permit( - :conference_id, - :volunteer, :accepted_code_of_conduct, - vchoice_ids: [], qanswer_ids: [], - qanswers_attributes: [], - event_ids: [], - user_attributes: [ - :username, :email, :name, :password, :password_confirmation] - ) + .permit( + :conference_id, + :volunteer, :accepted_code_of_conduct, + vchoice_ids: [], qanswer_ids: [], + qanswers_attributes: [], + event_ids: [], + user_attributes: %i[ + username email name password password_confirmation + ] + ) end end diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index a9dcdd3ba..ef90c670c 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -1,18 +1,19 @@ # frozen_string_literal: true class ConferencesController < ApplicationController + include ConferenceHelper + protect_from_forgery with: :null_session before_action :respond_to_options load_and_authorize_resource find_by: :short_title, except: :show def index @current = Conference.upcoming.reorder(start_date: :asc) - @antiquated = Conference.past - if @antiquated.empty? && @current.empty? && User.empty? - render :new_install - end + @antiquated = Conference.past.select { |conf| conf.splashpage&.public? } + render :new_install if @antiquated.empty? && @current.empty? && User.empty? end + # rubocop:disable Metrics/CyclomaticComplexity def show # load conference with header content @conference = Conference.unscoped.eager_load( @@ -24,15 +25,17 @@ def show ).find_by!(conference_finder_conditions) authorize! :show, @conference # TODO: reduce the 10 queries performed here - splashpage = @conference.splashpage + @splashpage = @conference.splashpage - unless splashpage.present? - redirect_to admin_conference_splashpage_path(@conference.short_title) && return - end + redirect_to admin_conference_splashpage_path(@conference.short_title) && return unless @splashpage.present? + + # User messages at the top of the page. + @unpaid_tickets = current_user_has_unpaid_tickets? + @user_needs_to_register = current_user_needs_to_register? - @image_url = "#{request.protocol}#{request.host}#{@conference.picture}" + @image_url = @splashpage.banner_photo_url || @conference.picture_url - if splashpage.include_cfp + if @splashpage.include_cfp? cfps = @conference.program.cfps @call_for_events = cfps.find { |call| call.cfp_type == 'events' } if @call_for_events.try(:open?) @@ -42,30 +45,28 @@ def show @call_for_tracks = cfps.find { |call| call.cfp_type == 'tracks' } @call_for_booths = cfps.find { |call| call.cfp_type == 'booths' } end - if splashpage.include_program - @highlights = @conference.highlighted_events.eager_load(:speakers) - if splashpage.include_tracks + if @splashpage.include_program? + @highlights = @conference.highlighted_events.includes(:speakers, :speaker_event_users) + if @splashpage.include_tracks? @tracks = @conference.confirmed_tracks.eager_load( :room ).order('tracks.name') end - if splashpage.include_booths - @booths = @conference.confirmed_booths.order('title') - end - end - if splashpage.include_registrations || splashpage.include_tickets - @tickets = @conference.tickets.order('price_cents') + @booths = @conference.confirmed_booths.order('title') if @splashpage.include_booths? + load_happening_now if @splashpage.include_happening_now end - if splashpage.include_lodgings - @lodgings = @conference.lodgings.order('name') + if @splashpage.include_registrations? || @splashpage.include_tickets? + @tickets = @conference.tickets.visible.order('price_cents') end - if splashpage.include_sponsors + @lodgings = @conference.lodgings.order('id') if @splashpage.include_lodgings? + if @splashpage.include_sponsors? @sponsorship_levels = @conference.sponsorship_levels.eager_load( :sponsors ).order('sponsorship_levels.position ASC', 'sponsors.name') @sponsors = @conference.sponsors end end + # rubocop:enable Metrics/CyclomaticComplexity def calendar respond_to do |format| @@ -119,8 +120,23 @@ def conference_finder_conditions end def respond_to_options - respond_to do |format| - format.html { head :ok } - end if request.options? + if request.options? + respond_to do |format| + format.html { head :ok } + end + end + end + + def current_user_tickets + @current_user_tickets ||= current_user.ticket_purchases.by_conference(@conference) + end + + def current_user_needs_to_register? + current_user && !@conference.user_registered?(current_user) && + current_user_tickets.where(ticket: @conference.registration_tickets).paid.any? + end + + def current_user_has_unpaid_tickets? + current_user && current_user_tickets.unpaid.any? end end diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb index 5dae6d190..29317e210 100644 --- a/app/controllers/payments_controller.rb +++ b/app/controllers/payments_controller.rb @@ -12,10 +12,9 @@ def index def new @total_amount_to_pay = Ticket.total_price(@conference, current_user, paid: false) - if @total_amount_to_pay.zero? - raise CanCan::AccessDenied.new('Nothing to pay for!', :new, Payment) - end + raise CanCan::AccessDenied.new('Nothing to pay for!', :new, Payment) if @total_amount_to_pay.zero? + @has_registration_ticket = params[:has_registration_ticket] @unpaid_ticket_purchases = current_user.ticket_purchases.unpaid.by_conference(@conference) end @@ -24,8 +23,21 @@ def create if @payment.purchase && @payment.save update_purchased_ticket_purchases - redirect_to conference_physical_tickets_path, - notice: 'Thanks! Your ticket is booked successfully.' + + has_registration_ticket = params[:has_registration_ticket] + if has_registration_ticket == 'true' + registration = @conference.register_user(current_user) + if registration + redirect_to conference_physical_tickets_path, + notice: "Thanks! Your ticket is booked successfully and you have been registered for #{@conference.title}." + return + end + redirect_to new_conference_conference_registration_path(@conference.short_title), + notice: 'Thanks! Your ticket is booked successfully. Please register for the conference.' + else + redirect_to conference_physical_tickets_path, + notice: 'Thanks! Your ticket is booked successfully.' + end else @total_amount_to_pay = Ticket.total_price(@conference, current_user, paid: false) @unpaid_ticket_purchases = current_user.ticket_purchases.unpaid.by_conference(@conference) diff --git a/app/controllers/physical_tickets_controller.rb b/app/controllers/physical_tickets_controller.rb index bc917b6ef..60c5e4bd6 100644 --- a/app/controllers/physical_tickets_controller.rb +++ b/app/controllers/physical_tickets_controller.rb @@ -8,7 +8,10 @@ class PhysicalTicketsController < ApplicationController def index @physical_tickets = current_user.physical_tickets.by_conference(@conference) + @has_registration_ticket = current_user.ticket_purchases.where(ticket: @conference.registration_tickets, + paid: true).any? @unpaid_ticket_purchases = current_user.ticket_purchases.by_conference(@conference).unpaid + @user = current_user end def show diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 692b4db01..48d62c817 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -1,34 +1,42 @@ # frozen_string_literal: true class ProposalsController < ApplicationController - before_action :authenticate_user!, except: [:show, :new, :create] + include ConferenceHelper + before_action :authenticate_user!, except: %i[show new create] load_resource :conference, find_by: :short_title load_resource :program, through: :conference, singleton: true load_and_authorize_resource :event, parent: false, through: :program # We authorize manually in these actions - skip_authorize_resource :event, only: [:confirm, :restart, :withdraw] + skip_authorize_resource :event, only: %i[join confirm restart withdraw] def index @event = @program.events.new @event.event_users.new(user: current_user, event_role: 'submitter') @events = current_user.proposals(@conference) + @volunteer_events = current_user.volunteer_duties(@conference) end def show @event_schedule = @event.event_schedules.find_by(schedule_id: @program.selected_schedule_id) @speakers_ordered = @event.speakers_ordered @surveys_after_event = @event.surveys.after_event.select(&:active?) + # TODO: include when conference is in session. + @happening_now = !@conference.pending? && !@conference.ended? && + @conference.splashpage.include_happening_now + load_happening_now if @happening_now end def new @user = User.new @url = conference_program_proposals_path(@conference.short_title) @languages = @program.languages_list + @superevents = @program.super_events end def edit @url = conference_program_proposal_path(@conference.short_title, params[:id]) @languages = @program.languages_list + @superevents = @program.events.where(superevent: true) end def create @@ -62,7 +70,8 @@ def create if @event.save Mailbot.submitted_proposal_mail(@event).deliver_later if @conference.email_settings.send_on_submitted_proposal - redirect_to conference_program_proposals_path(@conference.short_title), notice: 'Proposal was successfully submitted.' + redirect_to conference_program_proposals_path(@conference.short_title), + notice: 'Proposal was successfully submitted.' else flash.now[:error] = "Could not submit proposal: #{@event.errors.full_messages.join(', ')}" render action: 'new' @@ -83,11 +92,41 @@ def update redirect_to conference_program_proposals_path(conference_id: @conference.short_title), notice: 'Proposal was successfully updated.' else - flash.now[:error] = "Could not update proposal: #{@event.errors.full_messages.join(', ')}" + flash[:error] = "Could not update proposal: #{@event.errors.full_messages.join(', ')}" render action: 'edit' end end + def toggle_favorite + users = @event.favourite_users + if users.include?(current_user) + @event.favourite_users.delete(current_user) + else + @event.favourite_users << current_user + end + # TODO: Remove cache busting? + @event.touch + @program.touch + render json: {} + end + + # Joining an event marks as user as attending the event, and redirects to room url. + # attendees can only join during the event time + def join + admin = current_user.roles.where(id: @conference.roles).any? + registered_happening_now = @conference.user_registered?(current_user) && @event.happening_now? + can_view_event = @event.url.present? && (admin || registered_happening_now) + + if can_view_event + current_user.mark_attendance_for_conference(@conference) + current_user.mark_attendance_for_event(@event) + redirect_to @event.url, allow_other_host: true + else + redirect_to conference_program_proposal_path(@conference, @event), + error: 'You cannot join this event yet. Please try again closer to the start of the event.' + end + end + def withdraw authorize! :update, @event @url = conference_program_proposal_path(@conference.short_title, params[:id]) @@ -96,7 +135,7 @@ def withdraw @event.withdraw selected_schedule = @event.program.selected_schedule event_schedule = @event.event_schedules.find_by(schedule: selected_schedule) if selected_schedule - Rails.logger.debug "schedule: #{selected_schedule.inspect} and event_schedule #{event_schedule.inspect}" + Rails.logger.debug { "schedule: #{selected_schedule.inspect} and event_schedule #{event_schedule.inspect}" } if selected_schedule && event_schedule event_schedule.enabled = false event_schedule.save @@ -168,11 +207,12 @@ def registrations; end private def event_params + # TODO-SNAPCON: Restrict committee review to admins. params.require(:event).permit(:event_type_id, :track_id, :difficulty_level_id, - :title, :subtitle, :abstract, :description, - :require_registration, :max_attendees, :language, - speaker_ids: [] - ) + :title, :subtitle, :abstract, :submission_text, :description, + :superevent, :parent_id, :require_registration, :max_attendees, + :language, :committee_review, :presentation_mode, + speaker_ids: [], volunteer_ids: []) end def user_params diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 0db33128e..c2e0a71bb 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -26,7 +26,7 @@ def sign_up_params end def account_update_params - user_attributes = [:email, :name, :password, :password_confirmation, :current_password, :email_public] + user_attributes = %i[email name password password_confirmation current_password email_public] user_attributes << :is_admin if current_user.is_admin? params.require(:user).permit(user_attributes) end diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb new file mode 100644 index 000000000..d4b0510e8 --- /dev/null +++ b/app/controllers/rooms_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class RoomsController < ApplicationController + include ConferenceHelper + before_action :authenticate_user! + protect_from_forgery with: :null_session + load_resource :conference, find_by: :short_title + + # TODO: This duplicates too much of the #join route. + def live_session + @room = Room.find(params[:room_id]) + user_registered = @conference.user_registered?(current_user) + can_view = user_registered || current_user.roles.where(id: @conference.roles).any? + + if @room.url.present? && can_view + current_user.mark_attendance_for_conference(@conference) + else + redirect_to conference_schedule_path(@conference), + error: 'You cannot join this room yet. Please try again closer to the start of the event.' + end + end +end diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index 5f68c27cd..0a6f121fb 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -1,16 +1,17 @@ # frozen_string_literal: true class SchedulesController < ApplicationController + include ConferenceHelper + load_and_authorize_resource before_action :respond_to_options + before_action :favourites load_resource :conference, find_by: :short_title load_resource :program, through: :conference, singleton: true, except: :index - before_action :load_withdrawn_event_schedules, only: [:show, :events] + before_action :load_withdrawn_event_schedules, only: %i[show events] def show - event_schedules = @program.selected_event_schedules( - includes: [{ event: %i[event_type speakers submitter] }] - ) + event_schedules = @program.event_schedule_for_fullcalendar unless event_schedules redirect_to events_conference_schedule_path(@conference.short_title) @@ -19,7 +20,7 @@ def show respond_to do |format| format.xml do - @events_xml = event_schedules.map(&:event).group_by{ |event| event.time.to_date } if event_schedules + @events_xml = event_schedules.map(&:event).group_by { |event| event.time.to_date } if event_schedules end format.ics do cal = Icalendar::Calendar.new @@ -29,53 +30,91 @@ def show end format.html do - @rooms = @conference.venue.rooms.order(:name) if @conference.venue - @dates = @conference.start_date..@conference.end_date - @step_minutes = @program.schedule_interval.minutes - @conf_start = @conference.start_hour - @conf_period = @conference.end_hour - @conf_start - + dates = @conference.start_date..@conference.end_date # the schedule takes you to today if it is a date of the schedule - @current_day = @conference.current_conference_day - @day = @current_day.present? ? @current_day : @dates.first - if @current_day - # the schedule takes you to the current time if it is beetween the start and the end time. - @hour_column = @conference.hours_from_start_time(@conf_start, @conference.end_hour) - end - # Ids of the @event_schedules of confrmed self_organized tracks along with the selected_schedule_id - @selected_schedules_ids = [@conference.program.selected_schedule_id] - @conference.program.tracks.self_organized.confirmed.each do |track| - @selected_schedules_ids << track.selected_schedule_id + current_day = @conference.current_conference_day + @day = current_day.presence || dates.first + + if current_user && @favourites + event_schedules = event_schedules.select { |e| e.event.planned_for_user?(current_user) } end - @selected_schedules_ids.compact! - @event_schedules_by_room_id = event_schedules.select { |s| @selected_schedules_ids.include?(s.schedule_id) }.group_by(&:room_id) + + @rooms = FullCalendarFormatter.rooms_to_resources(@conference.rooms) if @conference.rooms + @event_schedules = FullCalendarFormatter.event_schedules_to_resources(event_schedules) + @now = Time.now.in_time_zone(@conference.timezone).strftime('%FT%T%:z') end end end def events @dates = @conference.start_date..@conference.end_date - @events_schedules = @program.selected_event_schedules( - includes: [:room, { event: %i[track event_type speakers submitter] }] - ) - @events_schedules = [] unless @events_schedules + @events_schedules = @program.event_schedule_program_view || [] + + @unscheduled_events = if @program.selected_schedule + @program.events.confirmed - @events_schedules.map(&:event) + else + @program.events.confirmed + end + event_type = params[:event_type] + if event_type && event_type != 'All Events' + @events_schedules.select! { |event_schedule| event_schedule.event.event_type.title == event_type } + end - @unscheduled_events = @program.events.confirmed - @events_schedules.map(&:event) + event_ids = @events_schedules.map(&:event_id) + @unscheduled_events.map(&:id) + favourited_events(event_ids) + + if current_user && @favourites + @events_schedules.keep_if { |es| es.event.planned_for_user?(current_user) } + @unscheduled_events.keep_if { |e| e.planned_for_user?(current_user) } + end day = @conference.current_conference_day @tag = day.strftime('%Y-%m-%d') if day end + def happening_now + # TODO: Adapt to include happening next. + @events_schedules = get_happening_now_events_schedules(@conference) + @current_time = Time.now.in_time_zone(@conference.timezone) + + event_ids = @events_schedules.map { |es| es.event.id } + favourited_events(event_ids) + + respond_to do |format| + format.html + format.json { render json: @events_schedules.to_json(root: false, include: :event) } + end + end + + def vertical_schedule + redirect_to conference_schedule_path(@conference) + end + def app - @qr_code = RQRCode::QRCode.new(conference_schedule_url).as_svg(offset: 20, color: '000', shape_rendering: 'crispEdges', module_size: 11) + @qr_code = RQRCode::QRCode.new(conference_schedule_url).as_svg(offset: 20, color: '000', + shape_rendering: 'crispEdges', module_size: 11) end private + def favourites + @favourites = params[:favourites] == 'true' + end + + def favourited_events(event_ids = []) + return @favourited_events = [] unless current_user + + @favourited_events ||= FavouriteEvents.where( + user_id: current_user.id, event_id: event_ids + ).pluck(:event_id) + end + def respond_to_options - respond_to do |format| - format.html { head :ok } - end if request.options? + if request.options? + respond_to do |format| + format.html { head :ok } + end + end end def load_withdrawn_event_schedules diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb index cf00af6f4..22b24dd92 100644 --- a/app/controllers/subscriptions_controller.rb +++ b/app/controllers/subscriptions_controller.rb @@ -3,7 +3,7 @@ class SubscriptionsController < ApplicationController before_action :authenticate_user! load_resource :conference, find_by: :short_title - load_and_authorize_resource only: [:create, :destroy], through: :conference + load_and_authorize_resource only: %i[create destroy], through: :conference def create @subscription = current_user.subscriptions.build(conference_id: @conference.id) @@ -18,8 +18,10 @@ def destroy @subscription = current_user.subscriptions.find_by(conference_id: @conference.id) redirect_to(root_path, error: "You are not subscribed to #{@conference.title}.") && return unless @subscription + if @subscription.destroy - redirect_to root_path, notice: "You have unsubscribed and you will not be receiving email notifications for #{@conference.title}." + redirect_to root_path, + notice: "You have unsubscribed and you will not be receiving email notifications for #{@conference.title}." else redirect_to root_path, error: @subscription.errors.full_messages.to_sentence end diff --git a/app/controllers/ticket_purchases_controller.rb b/app/controllers/ticket_purchases_controller.rb index 632a72892..7a09477e4 100644 --- a/app/controllers/ticket_purchases_controller.rb +++ b/app/controllers/ticket_purchases_controller.rb @@ -8,23 +8,48 @@ class TicketPurchasesController < ApplicationController def create current_user.ticket_purchases.by_conference(@conference).unpaid.destroy_all + + # Create a ticket purchase which can be paid or unpaid + count_registration_tickets_before = current_user.count_registration_tickets(@conference) message = TicketPurchase.purchase(@conference, current_user, params[:tickets].try(:first)) - if message.blank? - if current_user.ticket_purchases.by_conference(@conference).unpaid.any? - redirect_to new_conference_payment_path, - notice: 'Please pay here to get tickets.' - elsif current_user.ticket_purchases.by_conference(@conference).paid.any? + # The new ticket_purchase has been added to the database. current_user.ticket_purchases contains the new one. + count_registration_tickets_after = current_user.count_registration_tickets(@conference) + + # Failed to create ticket purchase + if message.present? + redirect_to conference_tickets_path(@conference.short_title), + error: "Oops, something went wrong with your purchase! #{message}" + return + end + + # Current user already paid for a registration ticket and the current ticket purchase contains one + if count_registration_tickets_before == 1 && count_registration_tickets_after > 1 + redirect_to conference_physical_tickets_path, + error: 'You already have one registration ticket for the conference.' + return + end + + # User needs to pay for tickets if any of them is not free. + if current_user.ticket_purchases.by_conference(@conference).unpaid.any? + has_registration_ticket = count_registration_tickets_before.zero? && count_registration_tickets_after == 1 + redirect_to new_conference_payment_path(has_registration_ticket: has_registration_ticket), + notice: 'Please pay here to get tickets.' + return + end + + # Automatically register the user after purchasing a registration ticket. + if count_registration_tickets_before.zero? && count_registration_tickets_after == 1 + registration = @conference.register_user(current_user) + if registration redirect_to conference_physical_tickets_path, - notice: 'You have free tickets for the conference.' - elsif @conference.tickets.for_registration.any? - redirect_to conference_tickets_path(@conference.short_title), - error: 'Please get at least one ticket to continue.' + notice: "Thanks! Your ticket is booked successfully & you have been registered for #{@conference.title}" else - redirect_to conference_conference_registration_path(@conference.short_title) + redirect_to new_conference_conference_registration_path(@conference.short_title), + notice: 'Thanks! Your ticket is booked successfully. Please register for the conference.' end else - redirect_to conference_tickets_path(@conference.short_title), - error: "Oops, something went wrong with your purchase! #{message}" + redirect_to conference_physical_tickets_path, + notice: 'Thanks! Your ticket is booked successfully.' end end @@ -32,6 +57,14 @@ def index @unpaid_ticket_purchases = current_user.ticket_purchases.by_conference(@conference).unpaid end + def destroy + @ticket_purchase = TicketPurchase.find(params[:id]) + authorize! :delete, @ticket_purchase + @ticket_purchase.delete + redirect_to admin_conference_ticket_path(@conference, @ticket_purchase.ticket.id), + notice: "Ticket for user #{@ticket_purchase.user.name} successfully removed." + end + private def ticket_purchase_params diff --git a/app/controllers/tickets_controller.rb b/app/controllers/tickets_controller.rb index c62afc06a..6a3043739 100644 --- a/app/controllers/tickets_controller.rb +++ b/app/controllers/tickets_controller.rb @@ -3,15 +3,21 @@ class TicketsController < ApplicationController before_action :authenticate_user! load_resource :conference, find_by: :short_title - load_resource :ticket, through: :conference + before_action :load_tickets + authorize_resource :ticket, through: :conference authorize_resource :conference_registrations, class: Registration before_action :check_load_resource, only: :index - def index; end + def index + # Clear out unpaid tickets so a user can reselect registration tickets. + current_user.ticket_purchases.unpaid.delete_all + end def check_load_resource - if @tickets.empty? - redirect_to root_path, notice: "There are no tickets available for #{@conference.title}!" - end + redirect_to root_path, notice: "There are no tickets available for #{@conference.title}!" if @tickets.empty? + end + + def load_tickets + @tickets = @conference.tickets.visible.order(:title) end end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 3cc3c6219..22760eb46 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -29,14 +29,12 @@ def handle(provider) begin user.save! - if openid.user != user - openid.user = user - end + openid.user = user if openid.user != user openid.save! sign_in user - redirect_to root_path, notice: "#{user.email} signed in successfully with #{provider}" - rescue => e + redirect_to session[:return_to] || root_path, notice: "#{user.email} signed in successfully with #{provider}" + rescue StandardError => e flash[:error] = e.message redirect_back_or_to new_user_registration_path end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f68ea51f3..d661a9b3e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -10,31 +10,44 @@ def show end # GET /users/1/edit - def edit - end + def edit; end # PATCH/PUT /users/1 def update if @user.update(user_params) - redirect_to @user, notice: 'User was successfully updated.' + redirect_to user_path(@user), notice: 'User was successfully updated.' else - flash.now[:error] = "An error prohibited your Profile from being saved: #{@user.errors.full_messages.join('. ')}." + flash.now[:error] = "An error prohibited your profile from being saved: #{@user.errors.full_messages.join('. ')}." render :edit end end def search + fields = %i[username id name] + fields << :email if current_user.is_admin? respond_to do |format| format.json do - render json: { users: User.active.where('username like ?', "%#{params[:query]}%").select(:username, :id) } + render json: { users: + User.active.where( + 'username ILIKE :search OR email ILIKE :search OR name ILIKE :search', + search: "%#{params[:query]}%" + ).as_json(only: fields, methods: :dropdwon_display) } end end end private - # Only allow a trusted parameter "white list" through. - def user_params - params.require(:user).permit(:name, :biography, :nickname, :affiliation) - end + def user_params + params[:user][:timezone] = params[:user][:timezone].presence || nil + params.require(:user).permit(:name, :biography, :nickname, :affiliation, + :picture, :picture_cache, :timezone) + end + + # Somewhat of a hack: users/current/edit + # rubocop:disable Naming/MemoizedInstanceVariableName + def load_user + @user ||= (params[:id] && params[:id] != 'current' && User.find(params[:id])) || current_user + end + # rubocop:enable Naming/MemoizedInstanceVariableName end diff --git a/app/datatables/registration_datatable.rb b/app/datatables/registration_datatable.rb index 533a2bf06..1639ef422 100644 --- a/app/datatables/registration_datatable.rb +++ b/app/datatables/registration_datatable.rb @@ -17,6 +17,7 @@ def view_columns name: { source: 'User.name' }, email: { source: 'User.email' }, accepted_code_of_conduct: { source: 'Registration.accepted_code_of_conduct', searchable: false }, + ticket_type: { source: 'Ticket.title' }, actions: { source: 'Registration.id', searchable: false, orderable: false } } end @@ -41,6 +42,7 @@ def data roles: conference_role_titles(record.user), email: record.email, accepted_code_of_conduct: !!record.accepted_code_of_conduct, # rubocop:disable Style/DoubleNegation + ticket_type: record.user.tickets.where(conference: conference).pluck(:title), edit_url: edit_admin_conference_registration_path(conference, record), DT_RowId: dom_id(record) } @@ -48,7 +50,7 @@ def data end def get_raw_records # rubocop:disable Naming/AccessorMethodName - conference.registrations.includes(user: :roles).references(:users, :roles).distinct + conference.registrations.includes(user: %i[roles tickets]).references(:users, :roles).distinct end # override upstream santitation, which converts everything to strings diff --git a/app/datatables/user_datatable.rb b/app/datatables/user_datatable.rb index 8ee1acb69..55d1e2c38 100644 --- a/app/datatables/user_datatable.rb +++ b/app/datatables/user_datatable.rb @@ -21,9 +21,11 @@ def view_columns confirmed_at: { source: 'User.confirmed_at', searchable: false }, email: { source: 'User.email' }, name: { source: 'User.name' }, + username: { source: 'User.username' }, attended: { source: 'attended_count', searchable: false }, roles: { source: 'Role.name' }, - actions: { source: 'User.id', searchable: false, orderable: false } + actions: { source: 'User.id', searchable: false, orderable: false }, + confirmed: { source: 'User.confirmed_at', searchable: false } } end @@ -36,11 +38,13 @@ def data confirmed_at: record.confirmed_at, email: record.email, name: record.name, + username: record.username, attended: record.attended_count, roles: record.roles.any? ? show_roles(record.get_roles) : 'None', view_url: admin_user_path(record), edit_url: edit_admin_user_path(record), - DT_RowId: dom_id(record) + DT_RowId: dom_id(record), + confirmed: record.confirmed_at.present? } end end @@ -48,9 +52,9 @@ def data # rubocop:disable Naming/AccessorMethodName def get_raw_records User.left_outer_joins(:registrations, :roles) - .distinct - .select("users.*, COUNT(CASE WHEN registrations.attended = 't' THEN 1 END) AS attended_count") - .group('users.id') + .distinct + .select("users.*, COUNT(CASE WHEN registrations.attended = 't' THEN 1 END) AS attended_count") + .group('users.id') end # rubocop:enable Naming/AccessorMethodName diff --git a/app/helpers/admin/tickets_helper.rb b/app/helpers/admin/tickets_helper.rb new file mode 100644 index 000000000..5db9f82de --- /dev/null +++ b/app/helpers/admin/tickets_helper.rb @@ -0,0 +1,14 @@ +module Admin + module TicketsHelper + def default_ticket_email_template + { + subject_input_id: 'ticket_email_subject', + subject_text: '{conference} | Ticket Confirmation and PDF!', + body_input_id: 'ticket_email_body', + body_text: "Dear {name},\n\nThanks! You have successfully booked {ticket_quantity} {ticket_title} +ticket(s) for the event {conference}. Your transaction id is {ticket_purchase_id}.\nPlease, +find the ticket(s) pdf attached.\n\nBest wishes,\n{conference} Team" + } + end + end +end diff --git a/app/helpers/admin/volunteers_helper.rb b/app/helpers/admin/volunteers_helper.rb index 0833c7713..cf0ed7129 100644 --- a/app/helpers/admin/volunteers_helper.rb +++ b/app/helpers/admin/volunteers_helper.rb @@ -3,7 +3,8 @@ module Admin module VolunteersHelper def can_manage_volunteers?(conference) - current_user.has_cached_role?(:organizer, conference) || current_user.has_cached_role?(:volunteers_coordinator, conference) + current_user.has_cached_role?(:organizer, + conference) || current_user.has_cached_role?(:volunteers_coordinator, conference) end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e3474111f..6edc74c84 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true +DEFAULT_LOGO = Rails.configuration.conference[:default_logo_filename] + +# TODO-SNAPCON: Refactor this module. Move chunks to a dates_help, some events_helper module ApplicationHelper + include Pagy::Frontend # Returns a string build from the start and end date of the given conference. # # If the conference is only one day long @@ -31,8 +35,7 @@ def date_string(start_date, end_date) endstr = end_date.strftime('%B %d, %Y') end - result = startstr + endstr - result + startstr + endstr end # Returns time with conference timezone @@ -46,15 +49,18 @@ def resource_name end def add_association_link(association_name, form_builder, div_class, html_options = {}) - link_to_add_association 'Add ' + association_name.to_s.singularize, form_builder, div_class, html_options.merge(class: 'assoc btn btn-success') + link_to_add_association 'Add ' + association_name.to_s.singularize, form_builder, div_class, + html_options.merge(class: 'assoc btn btn-success') end def remove_association_link(association_name, form_builder) - link_to_remove_association('Remove ' + association_name.to_s.singularize, form_builder, class: 'assoc btn btn-danger') + tag(:hr) + link_to_remove_association('Remove ' + association_name.to_s.singularize, form_builder, + class: 'assoc btn btn-danger') + tag.hr end def dynamic_association(association_name, title, form_builder, options = {}) - render 'shared/dynamic_association', association_name: association_name, title: title, f: form_builder, hint: options[:hint] + render 'shared/dynamic_association', association_name: association_name, title: title, f: form_builder, +hint: options[:hint] end def tracks(conference) @@ -75,24 +81,25 @@ def unread_notifications(user) # Output will be 'title, description and conference' def updated_attributes(version) version.changeset - .reject{ |_, values| values[0].blank? && values[1].blank? } - .keys.map{ |key| key.gsub('_id', '').tr('_', ' ')}.join(', ') - .reverse.sub(',', ' dna ').reverse + .reject { |_, values| values[0].blank? && values[1].blank? } + .keys.map { |key| key.gsub('_id', '').tr('_', ' ') }.join(', ') + .reverse.sub(',', ' dna ').reverse end def normalize_array_length(hashmap, length) hashmap.each_value do |value| - if value.length < length - value.fill(value[-1], value.length...length) - end + value.fill(value[-1], value.length...length) if value.length < length end end + # TODO: Move to the event model. def concurrent_events(event) return nil unless event.scheduled? && event.program.selected_event_schedules event_schedule = event.program.selected_event_schedules.find { |es| es.event == event } - other_event_schedules = event.program.selected_event_schedules.reject { |other_event_schedule| other_event_schedule == event_schedule } + other_event_schedules = event.program.selected_event_schedules.reject do |other_event_schedule| + other_event_schedule == event_schedule + end concurrent_events = [] event_time_range = (event_schedule.start_time.strftime '%Y-%m-%d %H:%M')...(event_schedule.end_time.strftime '%Y-%m-%d %H:%M') @@ -100,15 +107,19 @@ def concurrent_events(event) next unless other_event_schedule.event.confirmed? other_event_time_range = (other_event_schedule.start_time.strftime '%Y-%m-%d %H:%M')...(other_event_schedule.end_time.strftime '%Y-%m-%d %H:%M') - if (event_time_range.to_a & other_event_time_range.to_a).present? - concurrent_events << other_event_schedule.event - end + concurrent_events << other_event_schedule.event if (event_time_range.to_a & other_event_time_range.to_a).present? end - concurrent_events + concurrent_events.sort_by { |schedule| schedule.room&.order } end def speaker_links(event) - safe_join(event.speakers.map{ |speaker| link_to speaker.name, admin_user_path(speaker) }, ',') + safe_join(event.speakers.map { |speaker| link_to speaker.name, admin_user_path(speaker) }, ',') + end + + def volunteer_links(event) + safe_join(event.volunteers.map do |volunteer| + link_to(volunteer.name, admin_user_path(volunteer)) + end, ', ') end def event_types_sentence(conference) @@ -138,18 +149,37 @@ def hidden_if_conference_over(conference) 'hidden' if Date.today > conference.end_date end - def nav_root_link_for(conference) - link_text = ( - conference.try(:organization).try(:name) || ENV.fetch('OSEM_NAME', 'OSEM') - ) + # TODO-SNAPCON: Replace this with a search for a conference logo. + # TODO: If conference is defined, the alt text should be conference name. + def nav_root_link_for(conference = nil) + path = conference&.id.present? ? conference_path(conference) : root_path link_to( - link_text, - root_path, + image_tag(conference_logo_url(conference), alt: nav_link_text(conference)), + path, class: 'navbar-brand', - title: 'Open Source Event Manager' + title: nav_link_text(conference) ) end + # TODO-SNAPCON: This should be the conference title. + def nav_link_text(conference = nil) + conference.try(:organization).try(:name) || ENV.fetch('OSEM_NAME', 'OSEM') + end + + # TODO: Consider Renaming this? + # TODO: Allow passing in an organization + def conference_logo_url(conference = nil) + return DEFAULT_LOGO unless conference + + if conference.picture.present? + conference.picture.thumb.url + elsif conference.organization&.picture.present? + conference.organization.picture.thumb.url + else + DEFAULT_LOGO + end + end + # returns the url to be used for logo on basis of sponsorship level position def get_logo(object) if object.try(:sponsorship_level) @@ -164,4 +194,19 @@ def get_logo(object) object.picture.large.url end end + + # Embed links with a localized timezone URL + # Timestamps are stored at UTC but in the real timezone. + # We must convert then shift the time back to get the correct value. + # TODO: just take in an object? + def inyourtz(time, timezone, &) + time = time.in_time_zone(timezone) + time -= time.utc_offset + link_to("https://inyourtime.zone/t?#{time.to_i}", target: '_blank', rel: 'noopener', &) + end + + def visible_conference_links + @visible_conference_links ||= + Conference.all.select(:id, :organization_id, :title, :short_title, :start_date).includes(:splashpage, :organization).select { |conf| can?(:show, conf) }.group_by(&:organization) + end end diff --git a/app/helpers/chart_helper.rb b/app/helpers/chart_helper.rb index 7503e55b3..ac350dceb 100644 --- a/app/helpers/chart_helper.rb +++ b/app/helpers/chart_helper.rb @@ -2,11 +2,9 @@ module ChartHelper def chart_values(distribution_hash) - Hash[ - distribution_hash.collect do |key, data| - [key, data['value']] - end - ] + distribution_hash.collect do |key, data| + [key, data['value']] + end.to_h end def chart_colors(distribution_hash) diff --git a/app/helpers/conference_helper.rb b/app/helpers/conference_helper.rb index f685ed894..d393b6099 100644 --- a/app/helpers/conference_helper.rb +++ b/app/helpers/conference_helper.rb @@ -8,7 +8,7 @@ def one_call_open(*calls) # Return true if exactly two of those calls are open: call_for_papers , call_for_tracks , call_for_booths def two_calls_open(*calls) - calls.count{ |call| call.try(:open?) } == 2 + calls.count { |call| call.try(:open?) } == 2 end # URL for sponsorship emails @@ -22,12 +22,22 @@ def sponsorship_mailto(conference) ].join end + def short_ticket_description(ticket) + return unless ticket.description + + markdown(ticket.description.split("\n").first&.strip) + end + + def conference_color(conference) + conference.color.presence || Rails.configuration.conference[:default_color] + end + # adds events to icalendar for proposals in a conference def icalendar_proposals(calendar, proposals, conference) proposals.each do |proposal| calendar.event do |e| e.dtstart = proposal.time - e.dtend = proposal.time + proposal.event_type.length * 60 + e.dtend = proposal.time + (proposal.event_type.length * 60) e.duration = "PT#{proposal.event_type.length}M" e.created = proposal.created_at e.last_modified = proposal.updated_at @@ -45,9 +55,50 @@ def icalendar_proposals(calendar, proposals, conference) location += "#{v.country_name}, " if v.country_name e.location = location end - e.categories = conference.title, "Difficulty: #{proposal.difficulty_level.title}", "Track: #{proposal.track.name}" + e.categories = conference.title, "Difficulty: #{proposal.difficulty_level.title}", + "Track: #{proposal.track.name}" end end calendar end + + def get_happening_now_events_schedules(conference) + events_schedules = filter_events_schedules(conference, :happening_now?) + events_schedules ||= [] + events_schedules + end + + def get_happening_next_events_schedules(conference) + events_schedules = filter_events_schedules(conference, :happening_later?) + + return [] if events_schedules.empty? + + # events_schedules have been sorted by start_time in selected_event_schedules + happening_next_time = events_schedules[0].start_time + events_schedules.select { |s| s.start_time == happening_next_time } + end + + def load_happening_now + events_schedules_list = get_happening_now_events_schedules(@conference) + @is_happening_next = false + if events_schedules_list.empty? + events_schedules_list = get_happening_next_events_schedules(@conference) + @is_happening_next = true + end + @events_schedules_limit = Rails.configuration.conference[:events_per_page] + @events_schedules_length = events_schedules_list.length + @pagy, @events_schedules = pagy_array(events_schedules_list, + items: @events_schedules_limit, + link_extra: 'data-remote="true"') + end + + private + + # TODO: Move this to using the cached method on program/schedule + def filter_events_schedules(conference, filter) + conference.program.selected_event_schedules( + includes: [:event, :room, { event: + %i[event_type speakers speaker_event_users track program] }] + ).select(&filter) + end end diff --git a/app/helpers/date_time_helper.rb b/app/helpers/date_time_helper.rb index 420b086a8..4e3337286 100644 --- a/app/helpers/date_time_helper.rb +++ b/app/helpers/date_time_helper.rb @@ -21,9 +21,17 @@ def length_timestamp(length) def format_datetime(obj) return unless obj + obj = DateTime.parse(obj) unless obj.respond_to?(:strftime) + obj.strftime('%Y-%m-%d %H:%M') end + def format_all_timestamps(lst, conference) + lst.map do |ts| + "#{format_datetime(ts.in_time_zone(conference.timezone))} #{timezone_text(conference)}" + end.to_sentence + end + def show_time(length) return '0 h 0 min' if length.blank? diff --git a/app/helpers/event_types_helper.rb b/app/helpers/event_types_helper.rb index e200e33cc..ca7c3e511 100644 --- a/app/helpers/event_types_helper.rb +++ b/app/helpers/event_types_helper.rb @@ -8,6 +8,16 @@ module EventTypesHelper # ====Returns # * +String+ -> number of registrations / max allowed registrations def event_type_select_options(event_types = {}) - event_types.map { |type| ["#{type.title} - #{show_time(type.length)}", type.id] } + event_types.map do |type| + [ + "#{type.title} - #{show_time(type.length)}", + type.id, + { data: { + min_words: type.minimum_abstract_length, + max_words: type.maximum_abstract_length, + instructions: type.submission_template + } } + ] + end end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index ddb16e4ca..86f129806 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# TODO: Split this module into smaller modules +# rubocop:disable Metrics/ModuleLength module EventsHelper ## # Includes functions related to events @@ -13,6 +15,7 @@ def registered_text(event) "Registered: #{event.registrations.count}" end + # TODO-SNAPCON: Move to admin helper def rating_stars(rating, max, options = {}) Array.new(max) do |counter| content_tag( @@ -24,25 +27,27 @@ def rating_stars(rating, max, options = {}) end.join.html_safe end + # TODO-SNAPCON: Move to admin helper def rating_fraction(rating, max, options = {}) content_tag('span', "#{rating}/#{max}", **options) end - def replacement_event_notice(event_schedule) + def replacement_event_notice(event_schedule, styles: '') if event_schedule.present? && event_schedule.replacement?(@withdrawn_event_schedules) replaced_event = event_schedule.replaced_event_schedule.try(:event) content_tag :span do - concat content_tag :span, 'Please note that this talk replaces ' - concat link_to replaced_event.title, conference_program_proposal_path(@conference.short_title, replaced_event.id) + concat content_tag :span, 'Please note that this event replaces ' + concat link_to replaced_event.title, + conference_program_proposal_path(@conference.short_title, replaced_event.id), style: styles end end end def canceled_replacement_event_label(event, event_schedule, *label_classes) if event.state == 'canceled' || event.state == 'withdrawn' - content_tag :span, 'CANCELED', class: (['label', 'label-danger'] + label_classes) + content_tag :span, 'CANCELED', class: (%w[label label-danger] + label_classes) elsif event_schedule.present? && event_schedule.replacement?(@withdrawn_event_schedules) - content_tag :span, 'REPLACEMENT', class: (['label', 'label-info'] + label_classes) + content_tag :span, 'REPLACEMENT', class: (%w[label label-info] + label_classes) end end @@ -50,6 +55,7 @@ def rating_tooltip(event, max_rating) "#{event.average_rating}/#{max_rating}, #{pluralize(event.voters.length, 'vote')}" end + # TODO-SNAPCON: Move to admin helper def event_type_dropdown(event, event_types, conference_id) selection = event.event_type.try(:title) || 'Event Type' options = event_types.collect do |event_type| @@ -65,6 +71,7 @@ def event_type_dropdown(event, event_types, conference_id) active_dropdown(selection, options) end + # TODO-SNAPCON: Move to admin helper def track_dropdown(event, tracks, conference_id) selection = event.track.try(:name) || 'Track' options = tracks.collect do |track| @@ -80,6 +87,7 @@ def track_dropdown(event, tracks, conference_id) active_dropdown(selection, options) end + # TODO-SNAPCON: Move to admin helper def difficulty_dropdown(event, difficulties, conference_id) selection = event.difficulty_level.try(:title) || 'Difficulty' options = difficulties.collect do |difficulty| @@ -95,6 +103,7 @@ def difficulty_dropdown(event, difficulties, conference_id) active_dropdown(selection, options) end + # TODO-SNAPCON: Move to admin helper def state_dropdown(event, conference_id, email_settings) selection = event.state.humanize options = [] @@ -151,6 +160,7 @@ def state_dropdown(event, conference_id, email_settings) active_dropdown(selection, options) end + # TODO-SNAPCON: Move to admin helper def event_switch_checkbox(event, attribute, conference_id) check_box_tag( conference_id, @@ -165,8 +175,144 @@ def event_switch_checkbox(event, attribute, conference_id) ) end + def event_favourited?(event, current_user) + return false unless current_user + + event.favourite_users.exists?(current_user.id) + end + + # TODO-SNAPCON: These need to be refactored. + # It's not clear which should be an object vs when to use a tz string. + def display_timezone(user, conference) + return conference.timezone unless user + + user.timezone.presence || conference.timezone + end + + def timezone_offset(object) + Time.now.in_time_zone(object.timezone).utc_offset / 1.hour + end + + def timezone_text(object) + Time.now.in_time_zone(object.timezone).strftime('%Z') + end + + # timezone: Eastern Time (US & Canada) (UTC -5) + def timezone_mapping(timezone) + return unless timezone + + offset = Time.now.in_time_zone(timezone).utc_offset / 1.hour + text = Time.now.in_time_zone(timezone).strftime('%Z') + "#{text} (UTC #{offset})" + end + + def convert_timezone(date, old_timezone, new_timezone) + if date && old_timezone && new_timezone + date.strftime('%Y-%m-%dT%H:%M:%S').in_time_zone(old_timezone).in_time_zone(new_timezone) + end + end + + def join_event_link(event, event_schedule, current_user, small: false) + return if !event_schedule || event.ended? + + unless event_schedule.room_url.present? + return content_tag :span, 'In-person only', class: 'label label-default' + end + + unless current_user + return content_tag :span, 'Log in to view join link', class: 'label label-default' + end + + conference = event.conference + is_now = event_schedule.happening_now? # 30 minute threshold. + is_registered = conference.user_registered?(current_user) + admin = current_user.roles.where(id: conference.roles).any? || current_user.is_admin + # is_presenter = event.speakers.include?(current_user) || event.volunteers.include?(current_user) + + if admin || (is_now && is_registered) + join_btn = link_to("Join Event Now #{'(Early)' unless is_now}", + join_conference_program_proposal_path(conference, event), + target: '_blank', class: "btn btn-primary #{'btn-xs' if small}", + 'aria-label': "Join #{event.title}", rel: 'noopener') + if event_schedule.room.discussion_url.present? + discussion_link = link_to('Open Chat', + event_schedule.room.discussion_url, + target: '_blank', class: "btn btn-info #{'btn-xs' if small}", + 'aria-label': "Join #{event.title}", rel: 'noopener') + content_tag(:span, join_btn + discussion_link, class: 'btn-group') + else + join_btn + end + elsif is_registered + content_tag :span, class: 'label label-primary' do + 'Click to Join Online During Event' + end + else + link_to('Register for the conference to join this event.', + conference_conference_registration_path(conference), + class: 'btn btn-info btn-xs', + 'aria-label': "Register for #{event.title}") + end + end + + def calendar_timestamp(timestamp, _timezone) + timestamp = timestamp.in_time_zone('GMT') + timestamp -= timestamp.utc_offset + timestamp.strftime('%Y%m%dT%H%M%S') + end + + def google_calendar_link(event_schedule) + event = event_schedule.event + conference = event.conference + calendar_base = 'https://www.google.com/calendar/render' + start_timestamp = calendar_timestamp(event_schedule.start_time, conference.timezone) + end_timestamp = calendar_timestamp(event_schedule.end_time, conference.timezone) + event_details = { + action: 'TEMPLATE', + text: "#{event.title} at #{conference.title}", + details: calendar_event_text(event, event_schedule, conference), + location: "#{event_schedule.room.name} #{event_schedule.room_url}", + dates: "#{start_timestamp}/#{end_timestamp}", + ctz: event_schedule.timezone + } + "#{calendar_base}?#{event_details.to_param}" + end + + def css_background_color(color) + "background-color: #{color}; color: #{contrast_color(color)};" + end + + def user_options_for_dropdown(event, column) + users = event.send(column).pluck(:id, :name, :username, :email) + options_for_select(users.map { |u| ["#{u[1]} (#{u[2]} #{u[3]})", u[0]] }, users.map(&:first)) + end + + def committee_only_actions(user, conference, roles: %i[organizer cfp], &block) + return unless user + + role_map = roles.map { |role| { name: role, resource: conference } } + return unless user.is_admin || user.has_any_role?(*role_map) + + content_tag(:div, class: 'panel panel-info') do + concat content_tag(:div, 'Conference Organizers', class: 'panel-heading') + concat content_tag(:div, capture(&block), class: 'panel-body') + end + end + private + def calendar_event_text(event, event_schedule, conference) + <<~TEXT + #{conference.title} - #{event.title} + #{event_schedule.start_time.strftime('%Y %B %e - %H:%M')} #{event_schedule.timezone} + + More Info: #{conference_program_proposal_url(conference, event)} + Join: #{event.url} + + #{truncate(event.abstract, length: 200)} + TEXT + end + def active_dropdown(selection, options) # Consistent rendering of dropdown lists that submit patched changes # @@ -193,3 +339,4 @@ def active_dropdown(selection, options) end end end +# rubocop:enable Metrics/ModuleLength diff --git a/app/helpers/format_helper.rb b/app/helpers/format_helper.rb index 152ac239a..b6b492b4a 100644 --- a/app/helpers/format_helper.rb +++ b/app/helpers/format_helper.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true +require 'redcarpet/render_strip' + module FormatHelper - ## - # Includes functions related to formatting (like adding classes, colors) - ## def status_icon(object) case object.state when 'new', 'to_reject', 'to_accept' @@ -42,26 +41,24 @@ def variant_from_delta(delta, reverse: false) def target_progress_color(progress) progress = progress.to_i - result = - case - when progress >= 90 then 'green' - when progress < 90 && progress >= 80 then 'orange' - else 'red' + if progress >= 90 + 'green' + elsif progress < 90 && progress >= 80 + 'orange' + else + 'red' end - - result end def days_left_color(days_left) days_left = days_left.to_i if days_left > 30 - result = 'green' + 'green' elsif days_left < 30 && days_left > 10 - result = 'orange' + 'orange' else - result = 'red' + 'red' end - result end def bootstrap_class_for(flash_type) @@ -80,48 +77,32 @@ def bootstrap_class_for(flash_type) end def label_for(event_state) - result = '' case event_state when 'new' - result = 'label label-primary' - when 'withdrawn' - result = 'label label-danger' - when 'unconfirmed' - result = 'label label-success' - when 'confirmed' - result = 'label label-success' + 'label label-primary' + when 'withdrawn', 'cancelled' + 'label label-danger' + when 'unconfirmed', 'confirmed' + 'label label-success' when 'rejected' - result = 'label label-warning' - when 'canceled' - result = 'label label-danger' + 'label label-warning' end - result end def icon_for_todo(bool) - if bool - 'fa-solid fa-check' - else - 'fa-solid fa-xmark' - end + bool ? 'fa-solid fa-check' : 'fa-solid fa-xmark' end def class_for_todo(bool) - if bool - 'todolist-ok' - else - 'todolist-missing' - end + bool ? 'todolist-ok' : 'todolist-missing' end def word_pluralize(count, singular, plural = nil) - word = if (count == 1 || count =~ /^1(\.0+)?$/) - singular - else - plural || singular.pluralize - end - - word + if count.positive? && count < 2 + singular + else + plural || singular.pluralize + end end # Returns black or white deppending on what of them contrast more with the @@ -133,7 +114,7 @@ def contrast_color(hexcolor) g = hexcolor[3..4].to_i(16) b = hexcolor[5..6].to_i(16) yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000 - (yiq >= 128) ? 'black' : 'white' + yiq >= 128 ? 'black' : 'white' end def td_height(rooms) @@ -173,40 +154,43 @@ def speaker_width(rooms) speaker_height(rooms) - 4 end - def carousel_item_class(number, carousel_number, num_cols, col) - item_class = 'item' - item_class += ' first' if number == 0 - item_class += ' last' if number == (carousel_number - 1) - if (col && ((col / num_cols) == number)) || (!col && number == 0) - item_class += ' active' - end - item_class - end - def selected_scheduled?(schedule) - (schedule == @selected_schedule) ? 'Yes' : 'No' + schedule == @selected_schedule ? 'Yes' : 'No' end - def markdown(text, escape_html=true) + def markdown(text, escape_html = true) return '' if text.nil? markdown_options = { autolink: true, space_after_headers: true, - no_intra_emphasis: true, + # no_intra_emphasis: true, # SNAPCON fenced_code_blocks: true, - disable_indented_code_blocks: true + disable_indented_code_blocks: true, + tables: true, # SNAPCON + strikethrough: true, # SNAPCON + footnotes: true, # SNAPCON + superscript: true # SNAPCON } render_options = { escape_html: escape_html, safe_links_only: true } markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(render_options), markdown_options) - sanitize(sanitize(markdown.render(text)), scrubber: Loofah::Scrubbers::NoFollow.new) + rendered = sanitize(markdown.render(text)) + escape_html ? sanitize(rendered, scrubber: Loofah::Scrubbers::NoFollow.new) : rendered.html_safe + end + + def markdown_hint(text = '') + link = link_to('**Markdown Syntax**', + 'https://daringfireball.net/projects/markdown/syntax', + target: '_blank', rel: 'noopener') + markdown("#{text} Please look at #{link} to format your text", false) end - def markdown_hint(text='') - markdown("#{text} Please look at #{link_to '**Markdown Syntax**', 'https://daringfireball.net/projects/markdown/syntax', target: '_blank'} to format your text", false) + # Return a plain text markdown stripped of formatting. + def plain_text(content) + Redcarpet::Markdown.new(Redcarpet::Render::StripDown).render(content) end def quantity_left_of(resource) diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 906e6f3b8..11c455c2b 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -19,7 +19,8 @@ def omniauth_configured unless Rails.application.secrets.send(provider_key).blank? || Rails.application.secrets.send(provider_secret).blank? providers << provider end - providers << provider if ENV.fetch("OSEM_#{provider.upcase}_KEY", nil).present? && ENV.fetch("OSEM_#{provider.upcase}_SECRET", nil).present? + providers << provider if ENV.fetch("OSEM_#{provider.upcase}_KEY", + nil).present? && ENV.fetch("OSEM_#{provider.upcase}_SECRET", nil).present? end providers.uniq @@ -29,6 +30,6 @@ def omniauth_configured # Outputs the roles of a user, including the conferences for which the user has the roles # Eg. organizer(oSC13, oSC14), cfp(oSC12, oSC13) def show_roles(roles) - roles.map{ |x| x[0].titleize + ' (' + x[1].join(', ') + ')' }.join ', ' + roles.map { |x| x[0].titleize + ' (' + x[1].join(', ') + ')' }.join ', ' end end diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb index 0f355f5a0..04af38a1c 100644 --- a/app/helpers/versions_helper.rb +++ b/app/helpers/versions_helper.rb @@ -38,7 +38,7 @@ def link_to_user(user_id) link_to user.name, admin_user_path(id: user_id) else name = current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset['name'].second if PaperTrail::Version.where(item_type: 'User', item_id: user_id).any? - "#{name ? name : 'Unknown user'} with ID #{user_id}" + "#{name || 'Unknown user'} with ID #{user_id}" end end @@ -63,20 +63,36 @@ def current_or_last_object_state(model_name, id) end def subscription_change_description(version) - user_id = current_or_last_object_state(version.item_type, version.item_id).user_id + user_id = current_or_last_object_state(version.item_type, version.item_id)&.user_id + unless user_id + if version.event == 'create' + return 'subscribed (unkown user) to' + else + return 'unsubscribed (unknown user) from' + end + end user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second unless user_id.to_s == version.whodunnit version.event == 'create' ? "subscribed #{user_name} to" : "unsubscribed #{user_name} from" end + # rubocop:disable Metrics/CyclomaticComplexity: def registration_change_description(version) if version.item_type == 'Registration' - user_id = current_or_last_object_state(version.item_type, version.item_id).user_id + user_id = current_or_last_object_state(version.item_type, version.item_id)&.user_id elsif version.item_type == 'EventsRegistration' registration_id = current_or_last_object_state(version.item_type, version.item_id).registration_id - user_id = current_or_last_object_state('Registration', registration_id).user_id + user_id = current_or_last_object_state('Registration', registration_id)&.user_id end user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second + unless user_id + case version.event + when 'create' then return 'registered to (unkown user)' + when 'update' then return 'updated (unkown user) of the registration for' + when 'destroy' then return 'unregistered from' + end + end + if user_id.to_s == version.whodunnit case version.event when 'create' then 'registered to' @@ -91,6 +107,7 @@ def registration_change_description(version) end end end + # rubocop:enable Metrics/CyclomaticComplexity: def comment_change_description(version) user = current_or_last_object_state(version.item_type, version.item_id).user @@ -123,10 +140,10 @@ def general_change_description(version) end def event_change_description(version) - case - when version.event == 'create' then 'submitted new' + if version.event == 'create' + 'submitted new' - when version.changeset['state'] + elsif version.changeset['state'] case version.changeset['state'][1] when 'unconfirmed' then 'accepted' when 'withdrawn' then 'withdrew' diff --git a/app/jobs/mailbluster_create_lead_job.rb b/app/jobs/mailbluster_create_lead_job.rb new file mode 100644 index 000000000..a3934ce82 --- /dev/null +++ b/app/jobs/mailbluster_create_lead_job.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class MailblusterCreateLeadJob < ApplicationJob + queue_as :default + + def perform(user_id) + MailblusterManager.create_lead(User.find(user_id)) + end +end diff --git a/app/jobs/mailbluster_delete_lead_job.rb b/app/jobs/mailbluster_delete_lead_job.rb new file mode 100644 index 000000000..387d1cc00 --- /dev/null +++ b/app/jobs/mailbluster_delete_lead_job.rb @@ -0,0 +1,7 @@ +class MailblusterDeleteLeadJob < ApplicationJob + queue_as :default + + def perform(user) + MailblusterManager.delete_lead(user) + end +end diff --git a/app/jobs/mailbluster_edit_lead_job.rb b/app/jobs/mailbluster_edit_lead_job.rb new file mode 100644 index 000000000..ae159335f --- /dev/null +++ b/app/jobs/mailbluster_edit_lead_job.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class MailblusterEditLeadJob < ApplicationJob + queue_as :default + + def perform(user_id, add_tags: [], remove_tags: [], old_email: nil) + user = User.find(user_id) + MailblusterManager.edit_lead(user, add_tags: add_tags, remove_tags: remove_tags, old_email: old_email) + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 000000000..5a1e0bc83 --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,3 @@ +class ApplicationMailer < ActionMailer::Base + default from: ENV.fetch('OSEM_EMAIL_ADDRESS', 'no-reply@osem') +end diff --git a/app/mailers/mailbot.rb b/app/mailers/mailbot.rb index 52e15b800..32dfb7c2a 100644 --- a/app/mailers/mailbot.rb +++ b/app/mailers/mailbot.rb @@ -1,129 +1,163 @@ # frozen_string_literal: true -class Mailbot < ActionMailer::Base +YTLF_TICKET_ID = Rails.configuration.mailbot[:ytlf_ticket_id] + +class Mailbot < ApplicationMailer + helper ApplicationHelper + helper ConferenceHelper + + default bcc: Rails.configuration.mailbot[:bcc_address], + template_name: 'email_template', + content_type: 'text/html', + to: -> { @user.email }, + from: -> { @conference.contact.email } + def registration_mail(conference, user) - mail(to: user.email, - from: conference.contact.email, - subject: conference.email_settings.registration_subject, - body: conference.email_settings.generate_email_on_conf_updates(conference, - user, - conference.email_settings.registration_body)) + @user = user + @conference = conference + @email_body = @conference.email_settings.generate_email_on_conf_updates(@conference, @user, + @conference.email_settings.registration_body) + + mail(subject: @conference.email_settings.registration_subject) end def ticket_confirmation_mail(ticket_purchase) @ticket_purchase = ticket_purchase - @conference = ticket_purchase.conference @user = ticket_purchase.user + @conference = ticket_purchase.conference PhysicalTicket.last(ticket_purchase.quantity).each do |physical_ticket| - pdf = TicketPdf.new(@conference, @user, physical_ticket, @conference.ticket_layout.to_sym, "ticket_for_#{@conference.short_title}_#{physical_ticket.id}") + pdf = TicketPdf.new(@conference, @user, physical_ticket, @conference.ticket_layout.to_sym, + "ticket_for_#{@conference.short_title}_#{physical_ticket.id}") attachments["ticket_for_#{@conference.short_title}_#{physical_ticket.id}.pdf"] = pdf.render end - mail(to: @user.email, - from: @conference.contact.email, - template_name: 'ticket_confirmation_template', - subject: "#{@conference.title} | Ticket Confirmation and PDF!") + if @ticket_purchase.ticket_id == YTLF_TICKET_ID + template_name = 'young_thinkers_ticket_confirmation_template' + mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: template_name) + end + + # if email subject is empty, use custom template + if @ticket_purchase.ticket.email_subject.empty? && !@ticket_purchase.ticket.email_body.empty? + @ticket_purchase.ticket.email_body = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_body) + mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: 'custom_ticket_confirmation_template') + # if email body is empty, use default template with subject + elsif !@ticket_purchase.ticket.email_subject.empty? && @ticket_purchase.ticket.email_body.empty? + @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) + mail(subject: @ticket_purchase.ticket.email_subject, template_name: 'ticket_confirmation_template') + # if both exist, use custom + elsif !@ticket_purchase.ticket.email_subject.empty? && !@ticket_purchase.ticket.email_body.empty? + @ticket_purchase.ticket.email_body = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_body) + @ticket_purchase.ticket.email_subject = @ticket_purchase.generate_confirmation_mail(@ticket_purchase.ticket.email_subject) + mail(subject: @ticket_purchase.ticket.email_subject, template_name: 'custom_ticket_confirmation_template') + # if both empty, use default + else + mail(subject: "#{@conference.title} | Ticket Confirmation and PDF!", template_name: 'ticket_confirmation_template') + end end def acceptance_mail(event) - conference = event.program.conference + @user = event.submitter + @conference = event.program.conference + @speakers = event.speakers.map(&:email) + @email_body = @conference.email_settings.generate_event_mail(event, @conference.email_settings.accepted_body) - mail(to: event.submitter.email, - from: conference.contact.email, - subject: conference.email_settings.accepted_subject, - body: conference.email_settings.generate_event_mail(event, conference.email_settings.accepted_body)) + mail(subject: @conference.email_settings.accepted_subject, cc: @speakers) end def submitted_proposal_mail(event) - conference = event.program.conference + @user = event.submitter + @speakers = event.speakers.map(&:email) + @conference = event.program.conference + @email_body = @conference.email_settings.generate_event_mail(event, + @conference.email_settings.submitted_proposal_body) - mail(to: event.submitter.email, - from: conference.contact.email, - subject: conference.email_settings.submitted_proposal_subject, - body: conference.email_settings.generate_event_mail(event, conference.email_settings.submitted_proposal_body)) + mail(subject: @conference.email_settings.submitted_proposal_subject, cc: @speakers) end def rejection_mail(event) - conference = event.program.conference + @user = event.submitter + @speakers = event.speakers.map(&:email) + @conference = event.program.conference + @email_body = @conference.email_settings.generate_event_mail(event, @conference.email_settings.rejected_body) - mail(to: event.submitter.email, - from: conference.contact.email, - subject: conference.email_settings.rejected_subject, - body: conference.email_settings.generate_event_mail(event, conference.email_settings.rejected_body)) + mail(subject: @conference.email_settings.rejected_subject, cc: @speakers) end - def confirm_reminder_mail(event) - conference = event.program.conference + def confirm_reminder_mail(event, user: nil) + @user = user || event.submitter + @conference = event.program.conference + @email_body = @conference.email_settings.generate_event_mail(event, + @conference.email_settings.confirmed_without_registration_body) - mail(to: event.submitter.email, - from: conference.contact.email, - subject: conference.email_settings.confirmed_without_registration_subject, - body: conference.email_settings.generate_event_mail(event, - conference.email_settings.confirmed_without_registration_body)) + mail(subject: @conference.email_settings.confirmed_without_registration_subject) end def conference_date_update_mail(conference, user) - mail(to: user.email, - from: conference.contact.email, - subject: conference.email_settings.conference_dates_updated_subject, - body: conference.email_settings.generate_email_on_conf_updates(conference, - user, - conference.email_settings.conference_dates_updated_body)) + @user = user + @conference = conference + @email_body = @conference.email_settings.generate_email_on_conf_updates(@conference, @user, + @conference.email_settings.conference_dates_updated_body) + + mail(subject: @conference.email_settings.conference_dates_updated_subject) end def conference_registration_date_update_mail(conference, user) - mail(to: user.email, - from: conference.contact.email, - subject: conference.email_settings.conference_registration_dates_updated_subject, - body: conference.email_settings.generate_email_on_conf_updates(conference, - user, - conference.email_settings.conference_registration_dates_updated_body)) + @user = user + @conference = conference + @email_body = @conference.email_settings.generate_email_on_conf_updates(@conference, @user, + @conference.email_settings.conference_registration_dates_updated_body) + + mail(subject: @conference.email_settings.conference_registration_dates_updated_subject) end def conference_venue_update_mail(conference, user) - mail(to: user.email, - from: conference.contact.email, - subject: conference.email_settings.venue_updated_subject, - body: conference.email_settings.generate_email_on_conf_updates(conference, - user, - conference.email_settings.venue_updated_body)) + @user = user + @conference = conference + @email_body = @conference.email_settings.generate_email_on_conf_updates(@conference, @user, + @conference.email_settings.venue_updated_body) + + mail(subject: @conference.email_settings.venue_updated_subject) end def conference_schedule_update_mail(conference, user) - mail(to: user.email, - from: conference.contact.email, - subject: conference.email_settings.program_schedule_public_subject, - body: conference.email_settings.generate_email_on_conf_updates(conference, - user, - conference.email_settings.program_schedule_public_body)) + @user = user + @conference = conference + @email_body = @conference.email_settings.generate_email_on_conf_updates(@conference, @user, + @conference.email_settings.program_schedule_public_body) + + mail(bcc: nil, + subject: @conference.email_settings.program_schedule_public_subject) end def conference_cfp_update_mail(conference, user) - mail(to: user.email, - from: conference.contact.email, - subject: conference.email_settings.cfp_dates_updated_subject, - body: conference.email_settings.generate_email_on_conf_updates(conference, - user, - conference.email_settings.cfp_dates_updated_body)) + @user = user + @conference = conference + @email_body = @conference.email_settings.generate_email_on_conf_updates(@conference, @user, + @conference.email_settings.cfp_dates_updated_body) + + mail(bcc: nil, + subject: @conference.email_settings.cfp_dates_updated_subject) end def conference_booths_acceptance_mail(booth) - conference = booth.conference + @user = booth.submitter + @conference = booth.conference + @email_body = @conference.email_settings.generate_booth_mail(booth, + @conference.email_settings.booths_acceptance_body) - mail(to: booth.submitter.email, - from: conference.contact.email, - subject: conference.email_settings.booths_acceptance_subject, - body: conference.email_settings.generate_booth_mail(booth, conference.email_settings.booths_acceptance_body)) + mail(bcc: nil, + subject: @conference.email_settings.booths_acceptance_subject) end def conference_booths_rejection_mail(booth) - conference = booth.conference + @user = booth.submitter + @conference = booth.conference + @email_body = @conference.email_settings.generate_booth_mail(booth, + @conference.email_settings.booths_rejection_body) - mail(to: booth.submitter.email, - from: conference.contact.email, - subject: conference.email_settings.booths_rejection_subject, - body: conference.email_settings.generate_booth_mail(booth, conference.email_settings.booths_rejection_body)) + mail(bcc: nil, + subject: @conference.email_settings.booths_rejection_subject) end def event_comment_mail(comment, user) @@ -132,8 +166,7 @@ def event_comment_mail(comment, user) @conference = @event.program.conference @user = user - mail(to: @user.email, - from: @conference.contact.email, + mail(bcc: nil, template_name: 'comment_template', subject: "New comment has been posted for #{@event.title}") end diff --git a/app/models/.lodging.rb.swp b/app/models/.lodging.rb.swp deleted file mode 100644 index 70c3567d0..000000000 Binary files a/app/models/.lodging.rb.swp and /dev/null differ diff --git a/app/models/ability.rb b/app/models/ability.rb index 77746620d..0211ee8bc 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -17,7 +17,7 @@ def initialize(user) # Abilities for not signed in users (guests) def not_signed_in - can [:index, :conferences, :code_of_conduct], Organization + can %i[index conferences code_of_conduct], Organization can [:index], Conference can [:show], Conference do |conference| conference.splashpage&.public == true @@ -31,7 +31,7 @@ def not_signed_in event.state == 'confirmed' end - can [:show, :events, :app], Schedule do |schedule| + can [:show, :events, :happening_now, :app, :vertical_schedule], Schedule do |schedule| schedule.program.schedule_public end @@ -39,7 +39,7 @@ def not_signed_in can :show, Commercial, commercialable: Event.where(state: 'confirmed') can [:show, :create], User - can [:index, :show], Survey, surveyable_type: 'Conference' + can %i[index show], Survey, surveyable_type: 'Conference' # Things that are possible without ichain enabled that are **not*+ possible with ichain mode enabled. if ENV.fetch('OSEM_ICHAIN_ENABLED', nil) != 'true' @@ -68,6 +68,7 @@ def not_signed_in end # Abilities for signed in users + # TODO: Refactor into multiple functions def signed_in(user) # Abilities from not_signed_in user are also inherited not_signed_in @@ -87,10 +88,12 @@ def signed_in(user) end can :index, Organization - can :index, Ticket + can :index, Ticket do |ticket| + ticket.visible + end can :manage, TicketPurchase, user_id: user.id - can [:new, :create], Payment, user_id: user.id - can [:index, :show], PhysicalTicket, user: user + can %i[new create], Payment, user_id: user.id + can %i[index show], PhysicalTicket, user: user can [:new, :create], Booth do |booth| booth.new_record? && booth.conference.program.cfps.for_booths.try(:open?) @@ -100,7 +103,7 @@ def signed_in(user) booth.users.include?(user) end - can [:create, :destroy], Subscription, user_id: user.id + can %i[create destroy], Subscription, user_id: user.id can [:new, :create], Event do |event| event.program.cfp_open? && event.new_record? @@ -110,12 +113,17 @@ def signed_in(user) event.users.include?(user) end + can :toggle_favorite, Event do |event| + event.scheduled? + end + # can manage the commercials of their own events can :manage, Commercial, commercialable_type: 'Event', commercialable_id: user.events.pluck(:id) # can view and reply to a survey - can [:index, :show, :reply], Survey, surveyable_type: 'Conference' - can [:index, :show, :reply], Survey, surveyable_type: 'Registration', surveyable_id: user.registrations.pluck(:conference_id) + can %i[index show reply], Survey, surveyable_type: 'Conference' + can %i[index show reply], Survey, surveyable_type: 'Registration', + surveyable_id: user.registrations.pluck(:conference_id) # TODO: this needs to check for more, eg. # if survey target is after_conference, check whether or not the conference is over @@ -130,7 +138,7 @@ def signed_in(user) track.new_record? && track.program.cfps.for_tracks.try(:open?) end - can [:index, :show, :restart, :confirm, :withdraw], Track, submitter_id: user.id + can %i[index show restart confirm withdraw], Track, submitter_id: user.id can [:edit, :update], Track do |track| user == track.submitter && !(track.accepted? || track.confirmed?) diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 6100455c0..07e72ec20 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -27,15 +27,15 @@ def common_abilities_for_roles(user) can [:new, :create], Registration do |registration| conference = registration.conference - conference.registration_open? && !conference.registration_limit_exceeded? || conference.program.speakers.confirmed.include?(user) + (conference.registration_open? && !conference.registration_limit_exceeded?) || conference.program.speakers.confirmed.include?(user) end - can [:index, :admins], Organization + can %i[index admins], Organization can :index, Ticket can :manage, TicketPurchase, user_id: user.id - can [:new, :create], Payment, user_id: user.id + can %i[new create], Payment, user_id: user.id - can [:create, :destroy], Subscription, user_id: user.id + can %i[create destroy], Subscription, user_id: user.id can [:new, :create], Event do |event| event.program.cfp_open? && event.new_record? @@ -47,13 +47,13 @@ def common_abilities_for_roles(user) can [:destroy], Openid can :access, Admin can :index, Commercial, commercialable_type: 'Conference' - cannot [:edit, :update, :destroy], Question, global: true + cannot %i[edit update destroy], Question, global: true # for admins can :manage, :all if user.is_admin # even admin cannot create new users with ICHAIN enabled - cannot [:new, :create], User if ENV.fetch('OSEM_ICHAIN_ENABLED', nil) == 'true' + cannot %i[new create], User if ENV.fetch('OSEM_ICHAIN_ENABLED', nil) == 'true' cannot :revert_object, PaperTrail::Version do |version| - (version.event == 'create' && %w[Conference User Event].include?(version.item_type)) + version.event == 'create' && %w[Conference User Event].include?(version.item_type) end cannot :revert_attribute, PaperTrail::Version do |version| version.event != 'update' || version.item.nil? @@ -94,10 +94,14 @@ def signed_in_with_organization_admin_role(user) org_ids_for_organization_admin = Organization.with_role(:organization_admin, user).pluck(:id) conf_ids_for_organization_admin = Conference.where(organization_id: org_ids_for_organization_admin).pluck(:id) - can [:read, :update, :destroy, :assign_org_admins, :unassign_org_admins, :admins], Organization, id: org_ids_for_organization_admin + can %i[read update destroy assign_org_admins unassign_org_admins admins], Organization, + id: org_ids_for_organization_admin can :new, Conference can :manage, Conference, organization_id: org_ids_for_organization_admin - can [:index, :show], Role + can %i[index show], Role + + can %i[index revert_object revert_attribute], PaperTrail::Version, + organization_id: org_ids_for_organization_admin signed_in_with_organizer_role(user, conf_ids_for_organization_admin) end @@ -110,7 +114,7 @@ def signed_in_with_organizer_role(user, conf_ids_for_organization_admin = []) track_ids = Track.joins(:program).where('programs.conference_id IN (?)', conf_ids).pluck(:id) can :manage, Resource, conference_id: conf_ids - can [:read, :update, :destroy], Conference, id: conf_ids + can %i[read update destroy], Conference, id: conf_ids can :manage, Splashpage, conference_id: conf_ids can :manage, Contact, conference_id: conf_ids can :manage, EmailSettings, conference_id: conf_ids @@ -160,12 +164,12 @@ def signed_in_with_organizer_role(user, conf_ids_for_organization_admin = []) end can [:edit, :update, :toggle_user], Role do |role| - role.resource_type == 'Conference' && (conf_ids.include? role.resource_id) || - role.resource_type == 'Track' && (track_ids.include? role.resource_id) + (role.resource_type == 'Conference' && (conf_ids.include? role.resource_id)) || + (role.resource_type == 'Track' && (track_ids.include? role.resource_id)) end - can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'User' - can [:index, :revert_object, :revert_attribute], PaperTrail::Version, conference_id: conf_ids + can %i[index revert_object revert_attribute], PaperTrail::Version, item_type: 'User' + can %i[index revert_object revert_attribute], PaperTrail::Version, conference_id: conf_ids end def signed_in_with_cfp_role(user) @@ -175,7 +179,7 @@ def signed_in_with_cfp_role(user) can :show, Conference do |conf| conf_ids_for_cfp.include?(conf.id) end - can [:index, :show, :update], Resource, conference_id: conf_ids_for_cfp + can %i[index show update], Resource, conference_id: conf_ids_for_cfp can :manage, Booth, conference_id: conf_ids_for_cfp can :manage, Event, program: { conference_id: conf_ids_for_cfp } can :manage, EventType, program: { conference_id: conf_ids_for_cfp } @@ -185,7 +189,8 @@ def signed_in_with_cfp_role(user) can :manage, Schedule, program: { conference_id: conf_ids_for_cfp } can :manage, Room, venue: { conference_id: conf_ids_for_cfp } can :show, Venue, conference_id: conf_ids_for_cfp - can :show, Commercial, commercialable_type: 'Venue', commercialable_id: Venue.where(conference_id: conf_ids_for_cfp).pluck(:id) + can :show, Commercial, commercialable_type: 'Venue', + commercialable_id: Venue.where(conference_id: conf_ids_for_cfp).pluck(:id) can :manage, Cfp, program: { conference_id: conf_ids_for_cfp } can :manage, Program, conference_id: conf_ids_for_cfp can :manage, Commercial, commercialable_type: 'Event', @@ -204,7 +209,7 @@ def signed_in_with_cfp_role(user) (Conference.with_role(:cfp, user).pluck(:id).include? role.resource_id) end - can [:index, :revert_object, :revert_attribute], PaperTrail::Version, + can %i[index revert_object revert_attribute], PaperTrail::Version, item_type: %w[Event EventType Track DifficultyLevel EmailSettings Room Cfp Program Comment], conference_id: conf_ids_for_cfp can [:index, :revert_object, :revert_attribute], PaperTrail::Version, ["item_type = 'Commercial' AND conference_id IN (?) AND (object LIKE '%Event%' OR object_changes LIKE '%Event%')", conf_ids_for_cfp] do |version| @@ -220,7 +225,7 @@ def signed_in_with_info_desk_role(user) can :show, Conference do |conf| conf_ids_for_info_desk.include?(conf.id) end - can [:index, :show, :update], Resource, conference_id: conf_ids_for_info_desk + can %i[index show update], Resource, conference_id: conf_ids_for_info_desk can :manage, Registration, conference_id: conf_ids_for_info_desk can :manage, Question, conference_id: conf_ids_for_info_desk can :manage, Question do |question| @@ -241,7 +246,8 @@ def signed_in_with_info_desk_role(user) role.resource_type == 'Conference' && role.name == 'info_desk' && (Conference.with_role(:info_desk, user).pluck(:id).include? role.resource_id) end - can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'Registration', conference_id: conf_ids_for_info_desk + can %i[index revert_object revert_attribute], PaperTrail::Version, item_type: 'Registration', + conference_id: conf_ids_for_info_desk end def signed_in_with_volunteers_coordinator_role(user) @@ -251,7 +257,7 @@ def signed_in_with_volunteers_coordinator_role(user) can :show, Conference do |conf| conf_ids_for_volunteers_coordinator.include?(conf.id) end - can [:index, :show, :update], Resource, conference_id: conf_ids_for_volunteers_coordinator + can %i[index show update], Resource, conference_id: conf_ids_for_volunteers_coordinator can :manage, Vposition, conference_id: conf_ids_for_volunteers_coordinator can :manage, Vday, conference_id: conf_ids_for_volunteers_coordinator diff --git a/app/models/answer.rb b/app/models/answer.rb index abada979b..31c32bf19 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -1,5 +1,14 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: answers +# +# id :bigint not null, primary key +# title :string +# created_at :datetime +# updated_at :datetime +# class Answer < ApplicationRecord has_many :qanswers has_many :questions, through: :qanswers diff --git a/app/models/booth.rb b/app/models/booth.rb index 85c6c7a84..b2cd0ff3b 100644 --- a/app/models/booth.rb +++ b/app/models/booth.rb @@ -1,5 +1,21 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: booths +# +# id :bigint not null, primary key +# description :text +# logo_link :string +# reasoning :text +# state :string +# submitter_relationship :text +# title :string +# website_url :string +# created_at :datetime not null +# updated_at :datetime not null +# conference_id :integer +# class Booth < ApplicationRecord include ActiveRecord::Transitions has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } @@ -43,28 +59,28 @@ class Booth < ApplicationRecord state :confirmed event :restart do - transitions to: :new, from: [:withdrawn, :rejected, :canceled] + transitions to: :new, from: %i[withdrawn rejected canceled] end event :withdraw do - transitions to: :withdrawn, from: [:new, :to_accept, :accepted, :to_reject, :rejected, :confirmed] + transitions to: :withdrawn, from: %i[new to_accept accepted to_reject rejected confirmed] end event :confirm do transitions to: :confirmed, from: [:accepted] end event :to_accept do - transitions to: :to_accept, from: [:new, :to_reject] + transitions to: :to_accept, from: %i[new to_reject] end event :to_reject do - transitions to: :to_reject, from: [:new, :to_accept] + transitions to: :to_reject, from: %i[new to_accept] end event :accept do - transitions to: :accepted, from: [:new, :to_accept] + transitions to: :accepted, from: %i[new to_accept] end event :reject do - transitions to: :rejected, from: [:new, :to_reject] + transitions to: :rejected, from: %i[new to_reject] end event :cancel do - transitions to: :canceled, from: [:accepted, :rejected, :to_accept, :to_reject, :confirmed] + transitions to: :canceled, from: %i[accepted rejected to_accept to_reject confirmed] end end diff --git a/app/models/booth_request.rb b/app/models/booth_request.rb index 5cce46a17..b7ce7b954 100644 --- a/app/models/booth_request.rb +++ b/app/models/booth_request.rb @@ -1,5 +1,21 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: booth_requests +# +# id :bigint not null, primary key +# role :string +# created_at :datetime not null +# updated_at :datetime not null +# booth_id :integer +# user_id :integer +# +# Indexes +# +# index_booth_requests_on_booth_id (booth_id) +# index_booth_requests_on_user_id (user_id) +# class BoothRequest < ApplicationRecord belongs_to :booth belongs_to :user diff --git a/app/models/cfp.rb b/app/models/cfp.rb index 6571e7011..87cb70cd1 100644 --- a/app/models/cfp.rb +++ b/app/models/cfp.rb @@ -1,9 +1,22 @@ # frozen_string_literal: true -# cannot delete program if there are events submitted +# == Schema Information +# +# Table name: cfps +# +# id :bigint not null, primary key +# cfp_type :string +# description :text +# enable_registrations :boolean default(FALSE) +# end_date :date not null +# start_date :date not null +# created_at :datetime +# updated_at :datetime +# program_id :integer +# class Cfp < ApplicationRecord - TYPES = %w(events booths tracks).freeze + TYPES = %w[events booths tracks].freeze has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } belongs_to :program @@ -29,11 +42,11 @@ class Cfp < ApplicationRecord # * +True+ -> If cfp dates is updated and all other parameters are set # * +False+ -> Either cfp date is not updated or one or more parameter is not set def notify_on_cfp_date_update? - !end_date.blank? && !start_date.blank?\ - && (start_date_changed? || end_date_changed?)\ - && program.conference.email_settings.send_on_cfp_dates_updated\ - && !program.conference.email_settings.cfp_dates_updated_subject.blank?\ - && !program.conference.email_settings.cfp_dates_updated_body.blank? + end_date.present? && start_date.present? \ + && (start_date_changed? || end_date_changed?) \ + && program.conference.email_settings.send_on_cfp_dates_updated \ + && program.conference.email_settings.cfp_dates_updated_subject.present? \ + && program.conference.email_settings.cfp_dates_updated_body.present? end ## @@ -124,8 +137,10 @@ def before_end_of_conference end def start_after_end_date - errors - .add(:start_date, "can't be after the end date") if start_date && end_date && start_date > end_date + if start_date && end_date && start_date > end_date + errors + .add(:start_date, "can't be after the end date") + end end def conference_id diff --git a/app/models/comment.rb b/app/models/comment.rb index ee1a07a8e..976c54d89 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,21 +1,44 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: comments +# +# id :bigint not null, primary key +# body :text +# commentable_type :string +# lft :integer +# rgt :integer +# subject :string +# title :string(50) default("") +# created_at :datetime +# updated_at :datetime +# commentable_id :integer +# parent_id :integer +# user_id :integer +# +# Indexes +# +# index_comments_on_commentable_id (commentable_id) +# index_comments_on_commentable_type (commentable_type) +# index_comments_on_user_id (user_id) +# class Comment < ApplicationRecord - acts_as_nested_set scope: %i(commentable_id commentable_type) + acts_as_nested_set scope: %i[commentable_id commentable_type] validates :body, presence: true validates :user, presence: true after_create :send_notification # NOTE: install the acts_as_votable plugin if you # want user to vote on the quality of comments. - #acts_as_votable + # acts_as_votable belongs_to :commentable, counter_cache: true, polymorphic: true # NOTE: Comments belong to a user belongs_to :user - has_paper_trail on: %i(create destroy), meta: { conference_id: :conference_id } + has_paper_trail on: %i[create destroy], meta: { conference_id: :conference_id } # Helper class method that allows you to build a comment # by passing a commentable object, a user_id, and comment text @@ -27,7 +50,7 @@ def self.build_from(obj, user_id, comment) user_id: user_id end - #helper method to check if a comment has children + # helper method to check if a comment has children def has_children? children.any? end diff --git a/app/models/commercial.rb b/app/models/commercial.rb index 7bb2a0812..d5ac9a463 100644 --- a/app/models/commercial.rb +++ b/app/models/commercial.rb @@ -1,14 +1,28 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: commercials +# +# id :bigint not null, primary key +# commercial_type :string +# commercialable_type :string +# title :string +# url :string +# created_at :datetime +# updated_at :datetime +# commercial_id :string +# commercialable_id :integer +# class Commercial < ApplicationRecord require 'oembed' - belongs_to :commercialable, polymorphic: true + belongs_to :commercialable, polymorphic: true, touch: true has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } validates :url, presence: true, uniqueness: { scope: :commercialable } - validates :url, format: URI::regexp(%w(http https)) + validates :url, format: URI::DEFAULT_PARSER.make_regexp(%w[https]) validate :valid_url @@ -17,11 +31,16 @@ def self.render_from_url(url) begin resource = OEmbed::Providers.get(url, maxwidth: 560, maxheight: 315) { html: resource.html.html_safe } - rescue StandardError => exception - { error: exception.message } + rescue StandardError + { html: iframe_fallback(url) } + # { error: exception.message } end end + def self.iframe_fallback(url) + "".html_safe + end + def self.read_file(file) errors = {} errors[:no_event] = [] @@ -35,11 +54,11 @@ def self.read_file(file) event = Event.find_by(id: id) # Go to next event, if the event is not found - errors[:no_event] << id && next unless event + (errors[:no_event] << id) && next unless event commercial = event.commercials.new(url: url) unless commercial.save - errors[:validation_errors] << "Could not create commercial for event with ID #{event.id} (" + commercial.errors.full_messages.to_sentence + ')' + errors[:validation_errors] << ("Could not create materials for event with ID #{event.id} (" + commercial.errors.full_messages.to_sentence + ')') end end errors @@ -49,9 +68,7 @@ def self.read_file(file) def valid_url result = Commercial.render_from_url(url) - if result[:error] - errors.add(:base, result[:error]) - end + errors.add(:base, result[:error]) if result[:error] end def self.register_provider @@ -60,13 +77,17 @@ def self.register_provider speakerdeck << 'http://speakerdeck.com/*' OEmbed::Providers.register( - OEmbed::Providers::Youtube, - OEmbed::Providers::Vimeo, - OEmbed::Providers::Slideshare, - OEmbed::Providers::Flickr, - OEmbed::Providers::Instagram, - speakerdeck + OEmbed::Providers::Youtube, + OEmbed::Providers::Vimeo, + OEmbed::Providers::Slideshare, + OEmbed::Providers::Flickr, + OEmbed::Providers::Instagram, + speakerdeck ) + # OEmbed::Providers.register_fallback( + # OEmbed::ProviderDiscovery, + # OEmbed::Providers::Noembed + # ) end def conference_id diff --git a/app/models/concerns/track_saved_changes.rb b/app/models/concerns/track_saved_changes.rb new file mode 100644 index 000000000..db75e6257 --- /dev/null +++ b/app/models/concerns/track_saved_changes.rb @@ -0,0 +1,48 @@ +# https://github.com/ccmcbeck/after-commit +module TrackSavedChanges + extend ActiveSupport::Concern + + included do + # expose the details if consumer wants to do more + # attr_reader :ts_saved_changes_history, :ts_saved_changes_unfiltered + after_initialize :ts_reset_saved_changes + after_save :ts_track_saved_changes + end + + # on initalize, but useful for fine grain control + def ts_reset_saved_changes + @ts_saved_changes_unfiltered = {} + @ts_saved_changes_history = [] + end + + # filter out any changes that result in the original value + def ts_saved_changes + @ts_saved_changes_unfiltered.reject { |_k, v| v[0] == v[1] } + end + + private + + # on save + def ts_track_saved_changes + # maintain an array of ActiveModel::Dirty.changes + @ts_saved_changes_history << previous_changes.dup + # accumulate the most recent changes + @ts_saved_changes_history.last.each_pair { |k, v| ts_track_saved_change k, v } + end + + # v is an an array of [prev, current] + def ts_track_saved_change(key, value) + if @ts_saved_changes_unfiltered.key? key + @ts_saved_changes_unfiltered[key][1] = ts_track_saved_value value[1] + else + @ts_saved_changes_unfiltered[key] = value.dup + end + end + + # type safe dup inspred by http://stackoverflow.com/a/20955038 + def ts_track_saved_value(value) + value.dup + rescue TypeError + value + end +end diff --git a/app/models/conference.rb b/app/models/conference.rb index cac3a8493..dcf525754 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -1,5 +1,41 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: conferences +# +# id :bigint not null, primary key +# booth_limit :integer default(0) +# color :string +# custom_css :text +# custom_domain :string +# description :text +# end_date :date not null +# end_hour :integer default(20) +# events_per_week :text +# guid :string not null +# logo_file_name :string +# picture :string +# registration_limit :integer default(0) +# revision :integer default(0), not null +# short_title :string not null +# start_date :date not null +# start_hour :integer default(9) +# ticket_layout :integer default("portrait") +# timezone :string not null +# title :string not null +# use_vdays :boolean default(FALSE) +# use_volunteers :boolean +# use_vpositions :boolean default(FALSE) +# created_at :datetime +# updated_at :datetime +# organization_id :integer +# +# Indexes +# +# index_conferences_on_organization_id (organization_id) +# +# rubocop:disable Metrics/ClassLength class Conference < ApplicationRecord include RevisionCount require 'uri' @@ -9,13 +45,13 @@ class Conference < ApplicationRecord resourcify :roles, dependent: :delete_all default_scope { order('start_date DESC') } - scope :upcoming, (-> { where('end_date >= ?', Date.current) }) - scope :past, (-> { where('end_date < ?', Date.current) }) + scope :upcoming, -> { where('end_date >= ?', Date.current) } + scope :past, -> { where('end_date < ?', Date.current) } belongs_to :organization delegate :code_of_conduct, to: :organization - has_paper_trail ignore: %i(updated_at guid revision events_per_week), meta: { conference_id: :id } + has_paper_trail ignore: %i[updated_at guid revision events_per_week], meta: { conference_id: :id } has_and_belongs_to_many :questions @@ -25,7 +61,8 @@ class Conference < ApplicationRecord has_one :email_settings, dependent: :destroy has_one :program, dependent: :destroy has_one :venue, dependent: :destroy - delegate :city, :country_name, to: :venue, allow_nil: true + + delegate :city, :country_name, :rooms, to: :venue, allow_nil: true delegate :name, :street, to: :venue, prefix: true, allow_nil: true has_many :ticket_purchases, dependent: :destroy @@ -33,6 +70,7 @@ class Conference < ApplicationRecord has_many :payments, dependent: :destroy has_many :supporters, through: :ticket_purchases, source: :user has_many :tickets, dependent: :destroy + has_many :registration_tickets, -> { for_registration }, class_name: 'Ticket' has_many :resources, dependent: :destroy has_many :booths, dependent: :destroy has_many :confirmed_booths, -> { where(state: 'confirmed') }, class_name: 'Booth' @@ -55,6 +93,7 @@ class Conference < ApplicationRecord through: :program, source: :events has_many :event_types, through: :program + has_many :currency_conversions has_many :surveys, as: :surveyable, dependent: :destroy do def for_registration @@ -100,7 +139,7 @@ def after_conference after_create :create_free_ticket after_update :delete_event_schedules - enum ticket_layout: [:portrait, :landscape] + enum ticket_layout: { portrait: 0, landscape: 1 } ## # Checks if the user is registered to the conference @@ -110,8 +149,35 @@ def after_conference # ====Returns # * +false+ -> If the user is registered # * +true+ - If the user isn't registered - def user_registered? user - user.present? && registrations.where(user_id: user.id).count > 0 + def user_registered?(user) + user.present? && registrations.where(user_id: user.id).present? + end + + ## + # True when there is at least one ticket marked as "registration" + # A user must get a registration ticket before registering. + def registration_ticket_required? + registration_tickets.any? + end + + ## + # Creates a registration to the conference for the user + # + # ====Args + # * +user+ -> The user we check for + # ====Returns + # * +false+ -> If the user is registered + # * +true+ - If the user isn't registered + def register_user(user) + registration = registrations.new + registration.user = user + if registration.save + MailblusterEditLeadJob.perform_later( + user.id, add_tags: ["#{organization.name}-#{short_title}"] + ) + return registration + end + false end ## @@ -121,8 +187,8 @@ def delete_event_schedules if saved_change_to_start_hour? || saved_change_to_end_hour? event_schedules = program.event_schedules.select do |event_schedule| event_schedule.start_time.hour < start_hour || - event_schedule.end_time.hour > end_hour || - (event_schedule.end_time.hour == end_hour && event_schedule.end_time.minute > 0) + event_schedule.end_time.hour > end_hour || + (event_schedule.end_time.hour == end_hour && event_schedule.end_time.min > 0) end event_schedules.each(&:destroy) end @@ -201,9 +267,9 @@ def get_submissions_data # * +Array+ -> e.g. [0, 3, 3, 5] -> first week 0, second week 3 registrations def get_registrations_per_week return [] unless registrations && - registration_period && - registration_period.start_date && - registration_period.end_date + registration_period && + registration_period.start_date && + registration_period.end_date reg = registrations.group(:week).order(:week).count start_week = get_registration_start_week @@ -269,9 +335,9 @@ def registration_weeks result = 0 weeks = 0 if registration_period&.start_date && - registration_period&.end_date + registration_period&.end_date weeks = Date.new(registration_period.start_date.year, 12, 31) - .strftime('%W').to_i + .strftime('%W').to_i result = get_registration_end_week - get_registration_start_week + 1 end @@ -368,8 +434,8 @@ def self.get_top_submitter(limit = 5) # * +hash+ -> user: submissions def get_top_submitter(limit = 5) submitter = EventUser.joins(:event).select(:user_id) - .where('event_role = ? and program_id = ?', 'submitter', Conference.find(id).program.id) - .limit(limit).group(:user_id) + .where('event_role = ? and program_id = ?', 'submitter', Conference.find(id).program.id) + .limit(limit).group(:user_id) counter = submitter.order('count_all desc').count(:all) Conference.calculate_user_submission_hash(submitter, counter) end @@ -579,12 +645,12 @@ def tracks_distribution(state = nil) # * +ActiveRecord+ def self.get_active_conferences_for_dashboard result = Conference.where('start_date > ?', Time.now) - .select('id, short_title, color, start_date, organization_id') + .select('id, short_title, color, start_date, organization_id') if result.empty? result = Conference - .select('id, short_title, color, start_date, organization_id').limit(2) - .order(start_date: :desc) + .select('id, short_title, color, start_date, organization_id').limit(2) + .order(start_date: :desc) end result end @@ -626,9 +692,7 @@ def self.write_event_distribution_to_db result[state.name] = count end - unless conference.events_per_week - conference.events_per_week = {} - end + conference.events_per_week = {} unless conference.events_per_week # Write to database conference.events_per_week[week] = result @@ -648,7 +712,7 @@ def notify_on_dates_changed? return false unless saved_change_to_start_date? || saved_change_to_end_date? # do not notify unless the mail content is set up - (email_settings.conference_dates_updated_subject.present? && email_settings.conference_dates_updated_body.present?) + email_settings.conference_dates_updated_subject.present? && email_settings.conference_dates_updated_body.present? end ## @@ -665,7 +729,7 @@ def notify_on_registration_dates_changed? return false unless registration_period.saved_change_to_start_date? || registration_period.saved_change_to_end_date? # do not notify unless the mail content is set up - (email_settings.conference_registration_dates_updated_subject.present? && email_settings.conference_registration_dates_updated_body.present?) + email_settings.conference_registration_dates_updated_subject.present? && email_settings.conference_registration_dates_updated_body.present? end ## @@ -752,8 +816,8 @@ def next_color(i) # the color. We make use of big prime numbers to avoid repetition and to make # consecutive colors clearly different. def next_color_component(component, i) - big_prime_numbers = {r: 113, g: 67, b: 151} - ((i * big_prime_numbers[component]) % 239 + 16).to_s(16) + big_prime_numbers = { r: 113, g: 67, b: 151 } + (((i * big_prime_numbers[component]) % 239) + 16).to_s(16) end after_create do @@ -767,7 +831,8 @@ def next_color_component(component, i) # after the conference has been successfully created # Will create 1 new record for 'free' ticket def create_free_ticket - tickets.where(title: 'Free Access', price_cents: 0).first_or_create!(description: 'Get free access tickets for the conference.') + tickets.where(title: 'Free Access', + price_cents: 0).first_or_create!(description: 'Get free access tickets for the conference.') end ## @@ -775,10 +840,12 @@ def create_free_ticket # after the conference has been successfully created # Will create 4 new records for roles def create_roles - Role.where(name: 'organizer', resource: self).first_or_create(description: 'For the organizers of the conference (who shall have full access)') + Role.where(name: 'organizer', + resource: self).first_or_create(description: 'For the organizers of the conference (who shall have full access)') Role.where(name: 'cfp', resource: self).first_or_create(description: 'For the members of the CfP team') Role.where(name: 'info_desk', resource: self).first_or_create(description: 'For the members of the Info Desk team') - Role.where(name: 'volunteers_coordinator', resource: self).first_or_create(description: 'For the people in charge of volunteers') + Role.where(name: 'volunteers_coordinator', + resource: self).first_or_create(description: 'For the people in charge of volunteers') end ## @@ -827,13 +894,12 @@ def get_events_per_week_by_state # Completed weeks events_per_week.each do |week, values| + week = Date.parse(week) unless week.respond_to?(:strftime) values.each do |state, value| - if %i(confirmed unconfirmed).include?(state) - unless result[state.to_s.capitalize] - result[state.to_s.capitalize] = {} - end - result[state.to_s.capitalize][week.strftime('%W').to_i] = value - end + next unless %i[confirmed unconfirmed].include?(state) + + result[state.to_s.capitalize] = {} unless result[state.to_s.capitalize] + result[state.to_s.capitalize][week.strftime('%W').to_i] = value end end @@ -890,9 +956,7 @@ def cumulative_sum(array) # * +Array+ def pad_left(first_week, start_week) left = [] - if first_week > start_week - left = Array.new(first_week - start_week - 1, 0) - end + left = Array.new(first_week - start_week - 1, 0) if first_week > start_week left end @@ -905,11 +969,9 @@ def pad_left(first_week, start_week) def assert_keys_are_continuously(hash) keys = hash.keys (keys.min..keys.max).each do |key| - unless hash[key] - hash[key] = 0 - end + hash[key] = 0 unless hash[key] end - Hash[hash.sort] + hash.sort.to_h end ## @@ -1015,12 +1077,12 @@ def calculate_distribution_hash(grouped, counter, symbol) grouped.each do |event| object = event.send(symbol) - if object - result[object.title] = { - 'value' => counter[object.id], - 'color' => object.color - } - end + next unless object + + result[object.title] = { + 'value' => counter[object.id], + 'color' => object.color + } end result end @@ -1034,12 +1096,12 @@ def calculate_distribution_hash(grouped, counter, symbol) def calculate_track_distribution_hash(tracks_grouped, tracks_counter) result = {} tracks_grouped.each do |event| - if event.track - result[event.track.name] = { - 'value' => tracks_counter[event.track_id], - 'color' => event.track.color - } - end + next unless event.track + + result[event.track.name] = { + 'value' => tracks_counter[event.track_id], + 'color' => event.track.color + } end result end @@ -1093,12 +1155,10 @@ def self.calculate_user_distribution_hash(active_user, unconfirmed_user, dead_us def self.calculate_event_distribution_hash(counts) return {} if counts.values.sum == 0 - Hash[ - Event.state_machine.states.collect do |state| - state_name = state.name.to_s - [state_name.capitalize, counts[state_name] || 0] - end - ] + Event.state_machine.states.collect do |state| + state_name = state.name.to_s + [state_name.capitalize, counts[state_name] || 0] + end.to_h end ## @@ -1111,9 +1171,7 @@ def self.calculate_user_submission_hash(submitters, counter) counter.each do |key, value| # make PG happy by including the user_id in ORDER submitter = submitters.where(user_id: key).order(:user_id).first - if submitter - result[submitter.user] = value - end + result[submitter.user] = value if submitter end result end @@ -1130,9 +1188,9 @@ def create_email_settings # def generate_guid guid = SecureRandom.urlsafe_base64 -# begin -# guid = SecureRandom.urlsafe_base64 -# end while User.where(:guid => guid).exists? + # begin + # guid = SecureRandom.urlsafe_base64 + # end while User.where(:guid => guid).exists? self.guid = guid end @@ -1140,19 +1198,17 @@ def generate_guid # Adds a random color to the conference # def add_color - unless color - self.color = get_color - end + self.color = get_color unless color end def get_color - %w( - #000000 #0000FF #00FF00 #FF0000 #FFFF00 #9900CC - #CC0066 #00FFFF #FF00FF #C0C0C0 #00008B #FFD700 - #FFA500 #FF1493 #FF00FF #F0FFFF #EE82EE #D2691E - #C0C0C0 #A52A2A #9ACD32 #9400D3 #8B008B #8B0000 - #87CEEB #808080 #800080 #008B8B #006400 - ).sample + %w[ + #000000 #0000FF #00FF00 #FF0000 #FFFF00 #9900CC + #CC0066 #00FFFF #FF00FF #C0C0C0 #00008B #FFD700 + #FFA500 #FF1493 #FF00FF #F0FFFF #EE82EE #D2691E + #C0C0C0 #A52A2A #9ACD32 #9400D3 #8B008B #8B0000 + #87CEEB #808080 #800080 #008B8B #006400 + ].sample end # Calculates items per week from a hash. @@ -1171,9 +1227,7 @@ def calculate_items_per_week(start_week, weeks, items) items.each do |key, value| # Padding - if last_key < (key.to_i - 1) - result += Array.new(key.to_i - last_key - 1, sum) - end + result += Array.new(key.to_i - last_key - 1, sum) if last_key < (key.to_i - 1) sum += value result.push(sum) @@ -1181,16 +1235,13 @@ def calculate_items_per_week(start_week, weeks, items) end # Padding right - if result.length < weeks - result += Array.new(weeks - result.length, sum) - end + result += Array.new(weeks - result.length, sum) if result.length < weeks add_week_indices(result) end def add_week_indices(values) - Hash[ - values.collect.with_index { |value, index| ["Wk #{index + 1}", value] } - ] + values.collect.with_index { |value, index| ["Wk #{index + 1}", value] }.to_h end end +# rubocop:enable Metrics/ClassLength diff --git a/app/models/contact.rb b/app/models/contact.rb index e69f9dbbc..4984f9d45 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -1,5 +1,24 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: contacts +# +# id :bigint not null, primary key +# blog :string +# email :string +# facebook :string +# googleplus :string +# instagram :string +# mastodon :string +# social_tag :string +# sponsor_email :string +# twitter :string +# youtube :string +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class Contact < ApplicationRecord has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } @@ -8,10 +27,12 @@ class Contact < ApplicationRecord validates :conference, presence: true # Conferences only have one contact validates :facebook, :twitter, :googleplus, :instagram, :mastodon, - format: URI::regexp(%w(http https)), allow_blank: true + format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true def has_social_media? - return true if facebook.present? || twitter.present? || googleplus.present? || instagram.present? || mastodon.present? || email.present? + if facebook.present? || twitter.present? || googleplus.present? || instagram.present? || mastodon.present? || email.present? + return true + end false end diff --git a/app/models/currency_conversion.rb b/app/models/currency_conversion.rb new file mode 100644 index 000000000..c675b5a27 --- /dev/null +++ b/app/models/currency_conversion.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: currency_conversions +# +# rate :decimal +# from_currency :string +# to_currency :string +# conference_id :integer +# +class CurrencyConversion < ApplicationRecord + belongs_to :conference + validates :rate, numericality: { greater_than: 0 } + validates :from_currency, uniqueness: { scope: :to_currency }, on: :create +end diff --git a/app/models/difficulty_level.rb b/app/models/difficulty_level.rb index 981866932..5006f877d 100644 --- a/app/models/difficulty_level.rb +++ b/app/models/difficulty_level.rb @@ -1,5 +1,17 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: difficulty_levels +# +# id :bigint not null, primary key +# color :string +# description :text +# title :string +# created_at :datetime +# updated_at :datetime +# program_id :integer +# class DifficultyLevel < ApplicationRecord belongs_to :program, touch: true has_many :events, dependent: :nullify diff --git a/app/models/email_settings.rb b/app/models/email_settings.rb index 053a82dbb..bd2debddb 100644 --- a/app/models/email_settings.rb +++ b/app/models/email_settings.rb @@ -1,84 +1,72 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: email_settings +# +# id :bigint not null, primary key +# accepted_body :text +# accepted_subject :string +# booths_acceptance_body :text +# booths_acceptance_subject :string +# booths_rejection_body :text +# booths_rejection_subject :string +# cfp_dates_updated_body :text +# cfp_dates_updated_subject :string +# conference_dates_updated_body :text +# conference_dates_updated_subject :string +# conference_registration_dates_updated_body :text +# conference_registration_dates_updated_subject :string +# confirmed_without_registration_body :text +# confirmed_without_registration_subject :string +# program_schedule_public_body :text +# program_schedule_public_subject :string +# registration_body :text +# registration_subject :string +# rejected_body :text +# rejected_subject :string +# send_on_accepted :boolean default(FALSE) +# send_on_booths_acceptance :boolean default(FALSE) +# send_on_booths_rejection :boolean default(FALSE) +# send_on_cfp_dates_updated :boolean default(FALSE) +# send_on_conference_dates_updated :boolean default(FALSE) +# send_on_conference_registration_dates_updated :boolean default(FALSE) +# send_on_confirmed_without_registration :boolean default(FALSE) +# send_on_program_schedule_public :boolean default(FALSE) +# send_on_registration :boolean default(FALSE) +# send_on_rejected :boolean default(FALSE) +# send_on_submitted_proposal :boolean default(FALSE) +# send_on_venue_updated :boolean default(FALSE) +# submitted_proposal_body :text +# submitted_proposal_subject :string +# venue_updated_body :text +# venue_updated_subject :string +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class EmailSettings < ApplicationRecord belongs_to :conference has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } def get_values(conference, user, event = nil, booth = nil) - h = { - 'email' => user.email, - 'name' => user.name, - 'conference' => conference.title, - 'conference_start_date' => conference.start_date, - 'conference_end_date' => conference.end_date, - 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000')), - 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000')), - - 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000')) - } - - if conference.program.cfp - h['cfp_start_date'] = conference.program.cfp.start_date - h['cfp_end_date'] = conference.program.cfp.end_date - else - h['cfp_start_date'] = 'Unknown' - h['cfp_end_date'] = 'Unknown' - end - - if conference.venue - h['venue'] = conference.venue.name - h['venue_address'] = conference.venue.address - else - h['venue'] = 'Unknown' - h['venue_address'] = 'Unknown' - end - - if conference.registration_period - h['registration_start_date'] = conference.registration_period.start_date - h['registration_end_date'] = conference.registration_period.end_date - end - - if event - h['eventtitle'] = event.title - h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url( - conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000')) - end - - if booth - h['booth_title'] = booth.title - end - h + parser = EmailTemplateParser.new(conference, user) + parser.retrieve_values(event, booth) end def generate_event_mail(event, event_template) values = get_values(event.program.conference, event.submitter, event) - parse_template(event_template, values) + EmailTemplateParser.parse_template(event_template, values) end def generate_email_on_conf_updates(conference, user, conf_update_template) values = get_values(conference, user) - parse_template(conf_update_template, values) + EmailTemplateParser.parse_template(conf_update_template, values) end def generate_booth_mail(booth, booth_template) - values = get_values(booth.conference, booth.submitter, nil, booth) - parse_template(booth_template, values) - end - - private - - def parse_template(text, values) - values.each do |key, value| - if value.kind_of?(Date) - text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') unless text.blank? - else - text = text.gsub "{#{key}}", value unless text.blank? || value.blank? - end - end - text + values = get_values(conference: booth.conference, user: booth.submitter, booth: booth) + EmailTemplateParser.parse_template(booth_template, values) end end diff --git a/app/models/event.rb b/app/models/event.rb index dc7202c28..ea9ab7fbf 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,13 +1,56 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: events +# +# id :bigint not null, primary key +# abstract :text +# comments_count :integer default(0), not null +# committee_review :text +# description :text +# guid :string not null +# is_highlight :boolean default(FALSE) +# language :string +# max_attendees :integer +# presentation_mode :integer +# progress :string default("new"), not null +# proposal_additional_speakers :text +# public :boolean default(TRUE) +# require_registration :boolean +# start_time :datetime +# state :string default("new"), not null +# submission_text :text +# subtitle :string +# superevent :boolean +# title :string not null +# week :integer +# created_at :datetime +# updated_at :datetime +# difficulty_level_id :integer +# event_type_id :integer +# parent_id :integer +# program_id :integer +# room_id :integer +# track_id :integer +# +# Foreign Keys +# +# fk_rails_... (parent_id => events.id) +# +# rubocop:disable Metrics/ClassLength class Event < ApplicationRecord include ActionView::Helpers::NumberHelper # for number_with_precision + include ActionView::Helpers::SanitizeHelper include ActiveRecord::Transitions include RevisionCount - has_paper_trail on: [:create, :update], ignore: [:updated_at, :guid, :week], meta: { conference_id: :conference_id } + include FormatHelper + + has_paper_trail on: %i[create update], ignore: %i[updated_at guid week], meta: { conference_id: :conference_id } acts_as_commentable + before_create :generate_guid after_create :set_week has_many :event_users, dependent: :destroy @@ -17,7 +60,10 @@ class Event < ApplicationRecord has_many :speakers, through: :speaker_event_users, source: :user has_one :submitter_event_user, -> { where(event_role: 'submitter') }, class_name: 'EventUser' - has_one :submitter, through: :submitter_event_user, source: :user + has_one :submitter, through: :submitter_event_user, source: :user + + has_many :volunteer_event_users, -> { where(event_role: 'volunteer') }, class_name: 'EventUser' + has_many :volunteers, through: :volunteer_event_users, source: :user has_many :votes, dependent: :destroy has_many :voters, through: :votes, source: :user @@ -28,18 +74,30 @@ class Event < ApplicationRecord has_many :events_registrations has_many :registrations, through: :events_registrations has_many :event_schedules, dependent: :destroy + has_many :subevents, class_name: 'Event', foreign_key: :parent_id belongs_to :track belongs_to :difficulty_level - belongs_to :program + belongs_to :program, touch: true + belongs_to :room + delegate :url, to: :room, allow_nil: true + + # Multiple events can be contained within a larger parent event. + belongs_to :parent_event, class_name: 'Event', foreign_key: :parent_id, touch: true + + has_one :conference, through: :program + delegate :timezone, to: :conference + + # Stored on the events_users table, notably not event_users + has_and_belongs_to_many :favourite_users, class_name: 'User' accepts_nested_attributes_for :event_users, allow_destroy: true accepts_nested_attributes_for :speakers, allow_destroy: true accepts_nested_attributes_for :users - - before_create :generate_guid + accepts_nested_attributes_for :favourite_users validate :abstract_limit + validate :submission_limit validate :before_end_of_conference, on: :create validates :title, presence: true validates :abstract, presence: true @@ -51,10 +109,13 @@ class Event < ApplicationRecord validate :valid_track scope :confirmed, -> { where(state: 'confirmed') } + scope :unconfirmed, -> { where(state: 'unconfirmed') } scope :canceled, -> { where(state: 'canceled') } scope :withdrawn, -> { where(state: 'withdrawn') } scope :highlighted, -> { where(is_highlight: true) } + enum :presentation_mode, { in_person: 0, online: 1 } + state_machine initial: :new do state :new state :withdrawn @@ -64,10 +125,10 @@ class Event < ApplicationRecord state :rejected event :restart do - transitions to: :new, from: [:rejected, :withdrawn, :canceled] + transitions to: :new, from: %i[rejected withdrawn canceled] end event :withdraw do - transitions to: :withdrawn, from: [:new, :unconfirmed, :confirmed] + transitions to: :withdrawn, from: %i[new unconfirmed confirmed] end event :accept do transitions to: :unconfirmed, from: [:new], on_transition: :process_acceptance @@ -76,7 +137,7 @@ class Event < ApplicationRecord transitions to: :confirmed, from: :unconfirmed, on_transition: :process_confirmation end event :cancel do - transitions to: :canceled, from: [:unconfirmed, :confirmed] + transitions to: :canceled, from: %i[unconfirmed confirmed] end event :reject do transitions to: :rejected, from: [:new], on_transition: :process_rejection @@ -107,6 +168,16 @@ def registration_possible? registrations.count < max_attendees end + ## + # Used to determine a user's personal schedule. + # Has the user favourited the event, or are the assigned to present (speaking or volunteering)? + # "submitter" is excluded as a check, primarily due to admin users. + # =====Returns + # * +boolean+ -> if the user should attend the event + def planned_for_user?(user) + speakers.include?(user) || volunteers.include?(user) || favourite_users.include?(user) + end + ## # Finds the rating of the user for the event # ====Returns @@ -121,7 +192,7 @@ def user_rating(user) # ====Returns # * +true+ -> If the event has votes (optionally, by the user) # * +false+ -> If the event does not have any votes (optionally, by the user) - def voted?(user=nil) + def voted?(user = nil) return votes.where(user: user).any? if user votes.any? @@ -133,7 +204,12 @@ def average_rating @total_rating += vote.rating end @total = votes.size - @total_rating > 0 ? number_with_precision(@total_rating / @total.to_f, precision: 2, strip_insignificant_zeros: true) : 0 + if @total_rating > 0 + number_with_precision(@total_rating / @total.to_f, precision: 2, + strip_insignificant_zeros: true) + else + 0 + end end # get event speakers with the event sumbmitter at the first position @@ -141,9 +217,7 @@ def average_rating def speakers_ordered speakers_list = speakers.to_a - if speakers_list.reject! { |speaker| speaker == submitter } - speakers_list.unshift(submitter) - end + speakers_list.unshift(submitter) if speakers_list.reject! { |speaker| speaker == submitter } speakers_list end @@ -154,28 +228,28 @@ def transition_possible?(transition) def process_confirmation if program.conference.email_settings.send_on_confirmed_without_registration? && - program.conference.email_settings.confirmed_without_registration_body && - program.conference.email_settings.confirmed_without_registration_subject - if program.conference.registrations.where(user_id: submitter.id).first.nil? - Mailbot.confirm_reminder_mail(self).deliver_later - end + program.conference.email_settings.confirmed_without_registration_body && + program.conference.email_settings.confirmed_without_registration_subject + users = [submitter] + speakers + users.reject! { |user| user.registrations.for_conference(program.conference).present? } + users.each { |user| Mailbot.confirm_reminder_mail(self, user: user).deliver_later } end end def process_acceptance(options) if program.conference.email_settings.send_on_accepted && - program.conference.email_settings.accepted_body && - program.conference.email_settings.accepted_subject && - !options[:send_mail].blank? + program.conference.email_settings.accepted_body && + program.conference.email_settings.accepted_subject && + options[:send_mail].present? Mailbot.acceptance_mail(self).deliver_later end end def process_rejection(options) if program.conference.email_settings.send_on_rejected && - program.conference.email_settings.rejected_body && - program.conference.email_settings.rejected_subject && - !options[:send_mail].blank? + program.conference.email_settings.rejected_body && + program.conference.email_settings.rejected_subject && + options[:send_mail].present? Mailbot.rejection_mail(self).deliver_later end end @@ -184,34 +258,36 @@ def abstract_word_count abstract.to_s.split.size end + def submission_word_count + submission_text.to_s.split.size + end + def self.get_state_color(state) COLORS[state.to_sym] || '#00FFFF' # azure end def update_state(transition, mail = false, subject = false, send_mail = false, send_mail_param) alert = '' - if mail && send_mail_param && subject && send_mail - alert = 'Update Email Subject before Sending Mails' - end - begin - if mail - send(transition, - send_mail: send_mail_param) - else - send(transition) - end - save - # If the event was previously scheduled, and then withdrawn or cancelled - # its event_schedule will have enabled set to false - # If the event is now confirmed again, we want it to be available for scheduling - Rails.logger.debug "transition is #{transition}" - if transition == :confirm - Rails.logger.debug "schedules #{EventSchedule.unscoped.where(event: self, enabled: false)}" - EventSchedule.unscoped.where(event: self, enabled: false).destroy_all - end - rescue Transitions::InvalidTransition => e - alert = "Update state failed. #{e.message}" + alert = 'Update Email Subject before Sending Mails' if mail && send_mail_param && subject && send_mail + begin + if mail + send(transition, + send_mail: send_mail_param) + else + send(transition) + end + save + # If the event was previously scheduled, and then withdrawn or cancelled + # its event_schedule will have enabled set to false + # If the event is now confirmed again, we want it to be available for scheduling + Rails.logger.debug { "transition is #{transition}" } + if transition == :confirm + Rails.logger.debug { "schedules #{EventSchedule.unscoped.where(event: self, enabled: false)}" } + EventSchedule.unscoped.where(event: self, enabled: false).destroy_all end + rescue Transitions::InvalidTransition => e + alert = "Update state failed. #{e.message}" + end alert end @@ -224,6 +300,10 @@ def speaker_emails speakers.map(&:email).join(', ') end + def self.display_presentation_modes + presentation_modes.map { |key, _| [key.humanize.titlecase, key] } + end + ## # # Returns +Hash+ @@ -231,10 +311,10 @@ def progress_status { registered: speakers.all? { |speaker| program.conference.user_registered? speaker }, commercials: commercials.any?, - biographies: speakers.all? { |speaker| !speaker.biography.blank? }, - subtitle: !subtitle.blank?, - track: (!track.blank? unless program.tracks.empty?), - difficulty_level: !difficulty_level.blank?, + biographies: speakers.all? { |speaker| speaker.biography.present? }, + subtitle: subtitle.present?, + track: (track.present? unless program.tracks.empty?), + difficulty_level: (difficulty_level.present? unless program.difficulty_levels.empty?), title: true, abstract: true }.with_indifferent_access @@ -270,6 +350,20 @@ def time event_schedules.find_by(schedule_id: selected_schedule_id).try(:start_time) end + ## + # Returns the start time at which this event is scheduled + # + def happening_now? + event_schedules.find_by(schedule_id: selected_schedule_id).try(:happening_now?) + end + + ## + # Returns the list of subevents that can be displayed in the program + # + def program_subevents + subevents.confirmed.sort_by { |event| event.try(:time) || event.parent_event.try(:time) } + end + ## # Returns true or false, if the event is already over or not # @@ -277,14 +371,17 @@ def time # * +true+ -> If the event is over # * +false+ -> If the event is not over yet def ended? - event_schedule = event_schedules.find_by(schedule_id: selected_schedule_id) - return false unless event_schedule + event_schedules.find_by(schedule_id: selected_schedule_id).try(:ended?) + end - event_schedule.end_time < Time.current + delegate :conference, to: :program + + def <=>(other) + time <=> other.time end - def conference - program.conference + def serializable_hash(options = {}) + super(options).merge('rendered_abstract' => markdown(abstract)) end private @@ -294,19 +391,34 @@ def conference def max_attendees_no_more_than_room_size return unless room && max_attendees_changed? - errors.add(:max_attendees, "cannot be more than the room's capacity (#{room.size})") if max_attendees && (max_attendees > room.size) + if max_attendees && (max_attendees > room.size) + errors.add(:max_attendees, + "cannot be more than the room's capacity (#{room.size})") + end end - def abstract_limit - # If we don't have an event type, there is no need to count anything - return unless event_type && abstract + def word_limit(field) + # If we don't have an event type or the requested field, don't count + return unless event_type && respond_to?(field) && self[field] - len = abstract.split.size + len = self[field].split.size + # TODO: Use different limits for different text fields + # Uncomment the two lines below this when the separate word limits are implemented. + # max_words = event_type["maximum_#{field}_length"] + # min_words = event_type["minimum_#{field}_length"] max_words = event_type.maximum_abstract_length min_words = event_type.minimum_abstract_length - errors.add(:abstract, "cannot have less than #{min_words} words") if len < min_words - errors.add(:abstract, "cannot have more than #{max_words} words") if len > max_words + errors.add(field.to_sym, "cannot have less than #{min_words} words") if len < min_words + errors.add(field.to_sym, "cannot have more than #{max_words} words") if len > max_words + end + + def abstract_limit + word_limit(:abstract) + end + + def submission_limit + word_limit(:submission_text) end # TODO: create a module to be mixed into model to perform same operation @@ -325,13 +437,13 @@ def set_week end def before_end_of_conference - errors - .add(:created_at, "can't be after the conference end date!") if program.conference&.end_date && - (Date.today > program.conference.end_date) - end + return if submitter&.is_admin? - def conference_id - program.conference_id + if program.conference&.end_date && + (Date.today > program.conference.end_date) + errors + .add(:created_at, "can't be after the conference end date!") + end end ## @@ -356,3 +468,4 @@ def selected_schedule_id end end end +# rubocop:enable Metrics/ClassLength diff --git a/app/models/event_schedule.rb b/app/models/event_schedule.rb index 5358f0bc9..834971b02 100644 --- a/app/models/event_schedule.rb +++ b/app/models/event_schedule.rb @@ -1,10 +1,32 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: event_schedules +# +# id :bigint not null, primary key +# enabled :boolean default(TRUE) +# start_time :datetime +# created_at :datetime not null +# updated_at :datetime not null +# event_id :integer +# room_id :integer +# schedule_id :integer +# +# Indexes +# +# index_event_schedules_on_event_id (event_id) +# index_event_schedules_on_event_id_and_schedule_id (event_id,schedule_id) UNIQUE +# index_event_schedules_on_room_id (room_id) +# index_event_schedules_on_schedule_id (schedule_id) +# class EventSchedule < ApplicationRecord default_scope { where(enabled: true) } - belongs_to :schedule - belongs_to :event + + belongs_to :schedule, touch: true + belongs_to :event, touch: true belongs_to :room + delegate :url, to: :room, prefix: true, allow_nil: true has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } @@ -23,10 +45,41 @@ class EventSchedule < ApplicationRecord scope :canceled, -> { joins(:event).where('state = ?', 'canceled') } scope :withdrawn, -> { joins(:event).where('state = ?', 'withdrawn') } - scope :with_event_states, ->(*states){ joins(:event).where('events.state IN (?)', states) } + scope :with_event_states, ->(*states) { joins(:event).where('events.state IN (?)', states) } delegate :guid, to: :room, prefix: true + def timezone + event.conference.timezone + end + + ## + # True within `threshold` before and after the event. + # + def happening_now?(threshold = 15.minutes) + # TODO: Save start_time with local timezone info when making an event schedule + in_tz_start = start_time.in_time_zone(timezone) + in_tz_end = end_time.in_time_zone(timezone) + in_tz_start -= in_tz_start.utc_offset + in_tz_end -= in_tz_end.utc_offset + + return false if in_tz_end < Time.now + + begin_range = Time.now - threshold + end_range = Time.now + threshold + event_time_range = in_tz_start..in_tz_end + now_range = begin_range..end_range + event_time_range.overlaps?(now_range) + end + + def happening_later? + # TODO: Save start_time with local timezone info when making an event schedule + in_tz_start = start_time.in_time_zone(timezone) + in_tz_start -= in_tz_start.utc_offset + + in_tz_start >= Time.now + end + def self.withdrawn_or_canceled_event_schedules(schedule_ids) EventSchedule .unscoped @@ -41,6 +94,20 @@ def end_time start_time + event.event_type.length.minutes end + ## + # Returns if the event is in the past. + # + def ended? + !happening_now? && end_time_in_conference_timezone < time_in_conference_timezone(Time.current) + end + + ## + # Returns a time + room number string for sorting. + # + def sortable_timestamp + "#{start_time.to_i}-#{room&.order}" + end + ## # Returns event schedules that are scheduled in the same room and start_time as event # @@ -52,7 +119,7 @@ def intersecting_event_schedules end # event_schedule_source is a cached enumerable object that helps - # avoid repetitive EXISTS queries when rendering the schedule carousel partial + # avoid repetitive EXISTS queries when rendering the schedule partial def replacement?(event_schedule_source = nil) return false unless event.state == 'confirmed' return replaced_event_schedules.exists? unless event_schedule_source @@ -75,6 +142,14 @@ def intersects_with?(other) other.schedule_id == schedule_id end + def start_time_in_conference_timezone + time_in_conference_timezone(start_time) + end + + def end_time_in_conference_timezone + time_in_conference_timezone(end_time) + end + private def replaced_event_schedules @@ -84,13 +159,21 @@ def replaced_event_schedules def start_after_end_hour return unless event && start_time && event.program && event.program.conference && event.program.conference.end_hour - errors.add(:start_time, "can't be after the conference end hour (#{event.program.conference.end_hour})") if start_time.hour >= event.program.conference.end_hour + if start_time.hour >= event.program.conference.end_hour + errors.add(:start_time, + "can't be after the conference end hour (#{event.program.conference.end_hour})") + end end def start_before_start_hour - return unless event && start_time && event.program && event.program.conference && event.program.conference.start_hour + unless event && start_time && event.program && event.program.conference && event.program.conference.start_hour + return + end - errors.add(:start_time, "can't be before the conference start hour (#{event.program.conference.start_hour})") if start_time.hour < event.program.conference.start_hour + if start_time.hour < event.program.conference.start_hour + errors.add(:start_time, + "can't be before the conference start hour (#{event.program.conference.start_hour})") + end end def conference_id @@ -127,6 +210,15 @@ def during_track def valid_schedule return unless event.try(:track).try(:self_organized?) && schedule - errors.add(:schedule, "must be one of #{event.track.name} track's schedules") unless event.track.schedules.include?(schedule) + unless event.track.schedules.include?(schedule) + errors.add(:schedule, + "must be one of #{event.track.name} track's schedules") + end + end + + def time_in_conference_timezone(time) + time_in_tz = time.in_time_zone(timezone) + time_in_tz -= time_in_tz.utc_offset + time_in_tz end end diff --git a/app/models/event_type.rb b/app/models/event_type.rb index 691744c46..0d2943dc9 100644 --- a/app/models/event_type.rb +++ b/app/models/event_type.rb @@ -1,5 +1,21 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: event_types +# +# id :bigint not null, primary key +# color :string +# description :string +# length :integer default(30) +# maximum_abstract_length :integer default(500) +# minimum_abstract_length :integer default(0) +# submission_template :text +# title :string not null +# created_at :datetime +# updated_at :datetime +# program_id :integer +# class EventType < ApplicationRecord belongs_to :program, touch: true has_many :events, dependent: :restrict_with_error @@ -8,7 +24,7 @@ class EventType < ApplicationRecord ignore: %i[updated_at] validates :title, presence: true - validates :length, numericality: {greater_than: 0} + validates :length, numericality: { greater_than: 0 } validates :minimum_abstract_length, presence: true validates :maximum_abstract_length, presence: true validate :length_step @@ -24,7 +40,10 @@ class EventType < ApplicationRecord # Check if length is a divisor of program schedule cell size. Used as validation. # def length_step - errors.add(:length, "must be a divisor of #{program.schedule_interval}") if program && length % program.schedule_interval != 0 + if program && length % program.schedule_interval != 0 + errors.add(:length, + "must be a divisor of #{program.schedule_interval}") + end end def capitalize_color diff --git a/app/models/event_user.rb b/app/models/event_user.rb index 5e4d9b202..19cf04cee 100644 --- a/app/models/event_user.rb +++ b/app/models/event_user.rb @@ -1,9 +1,23 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: event_users +# +# id :bigint not null, primary key +# comment :string +# event_role :string default("participant"), not null +# created_at :datetime +# updated_at :datetime +# event_id :integer +# user_id :integer +# class EventUser < ApplicationRecord - # TODO: Do we need these roles? - ROLES = [%w[Speaker speaker], %w[Submitter submitter], %w[Moderator moderator]] + ROLES = [%w[Speaker speaker], %w[Submitter submitter], %w[Moderator moderator], + %w[Volunteer volunteer]] belongs_to :event, touch: true belongs_to :user + + has_paper_trail on: %i[create update], ignore: [:updated_at] end diff --git a/app/models/events_registration.rb b/app/models/events_registration.rb index 735df2a74..6e9a09725 100644 --- a/app/models/events_registration.rb +++ b/app/models/events_registration.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: events_registrations +# +# id :bigint not null, primary key +# attended :boolean default(FALSE), not null +# created_at :datetime +# event_id :integer +# registration_id :integer +# class EventsRegistration < ApplicationRecord belongs_to :registration belongs_to :event diff --git a/app/models/favourite_events.rb b/app/models/favourite_events.rb new file mode 100644 index 000000000..c2f1a6a35 --- /dev/null +++ b/app/models/favourite_events.rb @@ -0,0 +1,15 @@ +# == Schema Information +# +# Table name: events_users +# +# event_id :bigint +# user_id :bigint +# +# Indexes +# +# index_events_users_on_event_id (event_id) +# index_events_users_on_user_id (user_id) +# +class FavouriteEvents < ApplicationRecord + self.table_name = 'events_users' +end diff --git a/app/models/lodging.rb b/app/models/lodging.rb index 2f7a87ad0..822f38bd6 100644 --- a/app/models/lodging.rb +++ b/app/models/lodging.rb @@ -1,5 +1,22 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: lodgings +# +# id :bigint not null, primary key +# description :text +# name :string +# photo_content_type :string +# photo_file_name :string +# photo_file_size :integer +# photo_updated_at :datetime +# picture :string +# website_link :string +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class Lodging < ApplicationRecord belongs_to :conference diff --git a/app/models/openid.rb b/app/models/openid.rb index feaa6a9d9..043427f60 100644 --- a/app/models/openid.rb +++ b/app/models/openid.rb @@ -1,5 +1,17 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: openids +# +# id :bigint not null, primary key +# email :string +# provider :string +# uid :string +# created_at :datetime +# updated_at :datetime +# user_id :integer +# class Openid < ApplicationRecord belongs_to :user validates :provider, :uid, :email, presence: true diff --git a/app/models/organization.rb b/app/models/organization.rb index 877de9dff..d1930403e 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: organizations +# +# id :bigint not null, primary key +# code_of_conduct :text +# description :text +# name :string not null +# picture :string +# class Organization < ApplicationRecord resourcify :roles, dependent: :delete_all diff --git a/app/models/payment.rb b/app/models/payment.rb index e313f8dd0..d6319e422 100644 --- a/app/models/payment.rb +++ b/app/models/payment.rb @@ -1,12 +1,25 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: payments +# +# id :bigint not null, primary key +# amount :integer +# authorization_code :string +# last4 :string +# status :integer default("unpaid"), not null +# created_at :datetime not null +# updated_at :datetime not null +# conference_id :integer not null +# user_id :integer not null +# class Payment < ApplicationRecord has_many :ticket_purchases belongs_to :user belongs_to :conference - attr_accessor :stripe_customer_email - attr_accessor :stripe_customer_token + attr_accessor :stripe_customer_email, :stripe_customer_token validates :status, presence: true validates :user_id, presence: true @@ -22,10 +35,14 @@ def amount_to_pay Ticket.total_price(conference, user, paid: false).cents end + def stripe_description + "Tickets for #{conference.title} #{user.name} #{user.email}" + end + def purchase gateway_response = Stripe::Charge.create source: stripe_customer_token, receipt_email: stripe_customer_email, - description: "ticket purchases(#{user.username})", + description: stripe_description, amount: amount_to_pay, currency: conference.tickets.first.price_currency @@ -34,9 +51,8 @@ def purchase self.authorization_code = gateway_response[:id] self.status = 'success' true - - rescue Stripe::StripeError => error - errors.add(:base, error.message) + rescue Stripe::StripeError => e + errors.add(:base, e.message) self.status = 'failure' false end diff --git a/app/models/physical_ticket.rb b/app/models/physical_ticket.rb index 2493cbbea..a476888ab 100644 --- a/app/models/physical_ticket.rb +++ b/app/models/physical_ticket.rb @@ -1,5 +1,19 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: physical_tickets +# +# id :bigint not null, primary key +# token :string +# created_at :datetime not null +# updated_at :datetime not null +# ticket_purchase_id :integer not null +# +# Indexes +# +# index_physical_tickets_on_token (token) UNIQUE +# class PhysicalTicket < ApplicationRecord belongs_to :ticket_purchase has_one :ticket, through: :ticket_purchase diff --git a/app/models/program.rb b/app/models/program.rb index f367d1b2e..3adc2f744 100644 --- a/app/models/program.rb +++ b/app/models/program.rb @@ -1,6 +1,27 @@ # frozen_string_literal: true -# cannot delete program if there are events submitted +# == Schema Information +# +# Table name: programs +# +# id :bigint not null, primary key +# blind_voting :boolean default(FALSE) +# languages :string +# rating :integer default(0) +# schedule_fluid :boolean default(FALSE) +# schedule_interval :integer default(15), not null +# schedule_public :boolean default(FALSE) +# voting_end_date :datetime +# voting_start_date :datetime +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# selected_schedule_id :integer +# +# Indexes +# +# index_programs_on_selected_schedule_id (selected_schedule_id) +# class Program < ApplicationRecord has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } @@ -12,7 +33,6 @@ class Program < ApplicationRecord has_many :tracks, dependent: :destroy has_many :difficulty_levels, dependent: :destroy has_many :schedules, dependent: :destroy - has_many :event_schedules, through: :schedules belongs_to :selected_schedule, class_name: 'Schedule' has_many :events, dependent: :destroy do def require_registration @@ -20,7 +40,7 @@ def require_registration end def with_registration_open - select { |e| e if e.registration_possible? } + select { |e| e if e.registration_possible? }.sort end # All confirmed events of the conference with attribute require_registration @@ -44,7 +64,7 @@ def highlights has_many :event_schedules, through: :events has_many :event_users, through: :events - has_many :program_events_speakers, -> {where(event_role: 'speaker')}, through: :events, source: :event_users + has_many :program_events_speakers, -> { where(event_role: 'speaker') }, through: :events, source: :event_users has_many :speakers, -> { distinct }, through: :program_events_speakers, source: :user do def confirmed joins(:events).where(events: { state: :confirmed }) @@ -63,9 +83,9 @@ def unregistered(conference) accepts_nested_attributes_for :tracks, reject_if: proc { |r| r['name'].blank? }, allow_destroy: true accepts_nested_attributes_for :difficulty_levels, allow_destroy: true -# validates :conference_id, presence: true, uniqueness: true + # validates :conference_id, presence: true, uniqueness: true validates :rating, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 10, only_integer: true } - validates :schedule_interval, numericality: { greater_than_or_equal_to: 5, less_than_or_equal_to: 60 }, presence: true + validates :schedule_interval, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 60 }, presence: true validate :schedule_interval_divisor_60 validate :voting_start_date_before_end_date validate :voting_dates_exist @@ -80,23 +100,23 @@ def unregistered(conference) def selected_event_schedules(includes: [:event]) event_schedules = [] event_schedules = selected_schedule.event_schedules.includes(*includes).order(start_time: :asc) if selected_schedule + tracks.self_organized.confirmed.includes(selected_schedule: { event_schedules: includes }).order(start_date: :asc).each do |track| next unless track.selected_schedule event_schedules += track.selected_schedule.event_schedules end - event_schedules.sort_by(&:start_time) + event_schedules.sort_by(&:sortable_timestamp) end ## - # Checks if blind_voting is enabled and if voting period is over + # Checks if blind_voting is enabled or if voting period is over # ====Returns # * +true+ -> If we can show voting details # * +false+ -> If we cannot show voting details def show_voting? - return true unless blind_voting - - Time.current > voting_end_date + # TODO-SNAPCON: Figure out if this is the best behavior? + !blind_voting || Time.current > voting_end_date end ## @@ -115,9 +135,15 @@ def voting_period? # ====Returns # Errors when the condition is not true def voting_dates_exist - errors.add(:voting_start_date, 'must be set, when blind voting is enabled') if blind_voting && !voting_start_date && !voting_end_date + if blind_voting && !voting_start_date && !voting_end_date + errors.add(:voting_start_date, + 'must be set, when blind voting is enabled') + end - errors.add(:voting_end_date, 'must be set, when blind voting is enabled') if blind_voting && !voting_start_date && !voting_end_date + if blind_voting && !voting_start_date && !voting_end_date + errors.add(:voting_end_date, + 'must be set, when blind voting is enabled') + end errors.add(:voting_end_date, 'must be set, when voting_start_date is set') if voting_start_date && !voting_end_date @@ -129,7 +155,10 @@ def voting_dates_exist # ====Returns # Errors when the condition is not true def voting_start_date_before_end_date - errors.add(:voting_start_date, 'must be before voting end date') if voting_start_date && voting_end_date && voting_start_date > voting_end_date + if voting_start_date && voting_end_date && voting_start_date > voting_end_date + errors.add(:voting_start_date, + 'must be before voting end date') + end end ## @@ -160,11 +189,11 @@ def notify_on_schedule_public? return false unless schedule_public # do not notify unless the mail content is set up - (!conference.email_settings.program_schedule_public_subject.blank? && !conference.email_settings.program_schedule_public_body.blank?) + conference.email_settings.program_schedule_public_subject.present? && conference.email_settings.program_schedule_public_body.present? end def languages_list - languages.split(',').map {|l| ISO_639.find(l).english_name} if languages.present? + languages.split(',').map { |l| ISO_639.find(l).english_name } if languages.present? end ## @@ -174,6 +203,7 @@ def languages_list # * +True+ -> If there is any event for the given date # * +False+ -> If there is not any event for the given date def any_event_for_this_date?(date) + return false if date.nil? || date == '' return false unless selected_schedule.present? parsed_date = DateTime.parse("#{date} 00:00").utc @@ -199,8 +229,32 @@ def remaining_cfp_types Cfp::TYPES - cfps.pluck(:cfp_type) end + def event_schedule_for_fullcalendar + Rails.cache.fetch("#{cache_key_for_schedule}}/fullcalendar=X") do + selected_event_schedules(includes: [:event, :room, + { event: %i[event_type track program parent_event] }]) + end + end + + def event_schedule_program_view + Rails.cache.fetch("#{cache_key_for_schedule}}/program") do + selected_event_schedules(includes: [:event, :room, + { event: %i[event_type speakers speaker_event_users + submitter submitter_event_user + track program] }]) + end + end + + def super_events + events.where(superevent: true) + end + private + def cache_key_for_schedule + "#{cache_key_with_version}#{selected_schedule&.cache_key_with_version}" + end + ## # Creates default EventTypes for this Conference. Used as before_create. # @@ -238,11 +292,15 @@ def check_languages_format self.languages = languages.delete(' ').downcase errors.add(:languages, 'must be two letters separated by commas') && return unless languages.match(/^$|(\A[a-z][a-z](,[a-z][a-z])*\z)/).present? + languages_array = languages.split(',') # We check that languages are not repeated errors.add(:languages, "can't be repeated") && return unless languages_array.uniq!.nil? + # We check if every language is a valid ISO 639-1 language - errors.add(:languages, 'must be ISO 639-1 valid codes') unless languages_array.select{ |x| ISO_639.find(x).nil? }.empty? + errors.add(:languages, 'must be ISO 639-1 valid codes') unless languages_array.none? do |x| + ISO_639.find(x).nil? + end end ## diff --git a/app/models/qanswer.rb b/app/models/qanswer.rb index 535da3217..0e532032a 100644 --- a/app/models/qanswer.rb +++ b/app/models/qanswer.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: qanswers +# +# id :bigint not null, primary key +# created_at :datetime +# updated_at :datetime +# answer_id :integer +# question_id :integer +# class Qanswer < ApplicationRecord belongs_to :question belongs_to :answer, dependent: :delete diff --git a/app/models/question.rb b/app/models/question.rb index 702a3784e..e9ca7f8ca 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -1,5 +1,17 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: questions +# +# id :bigint not null, primary key +# global :boolean +# title :string +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# question_type_id :integer +# class Question < ApplicationRecord belongs_to :question_type has_and_belongs_to_many :conferences diff --git a/app/models/question_type.rb b/app/models/question_type.rb index 2a5ebc247..17bdc8c6d 100644 --- a/app/models/question_type.rb +++ b/app/models/question_type.rb @@ -1,5 +1,14 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: question_types +# +# id :bigint not null, primary key +# title :string +# created_at :datetime +# updated_at :datetime +# class QuestionType < ApplicationRecord has_many :questions end diff --git a/app/models/registration.rb b/app/models/registration.rb index 759003fd8..9b5d04fa9 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -1,5 +1,20 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: registrations +# +# id :bigint not null, primary key +# accepted_code_of_conduct :boolean +# attended :boolean default(FALSE) +# other_special_needs :text +# volunteer :boolean +# week :integer +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# user_id :integer +# class Registration < ApplicationRecord require 'csv' belongs_to :user @@ -11,7 +26,7 @@ class Registration < ApplicationRecord has_many :events_registrations has_many :events, through: :events_registrations, dependent: :destroy - has_paper_trail ignore: %i(updated_at week), meta: { conference_id: :conference_id } + has_paper_trail ignore: %i[updated_at week], meta: { conference_id: :conference_id } accepts_nested_attributes_for :user accepts_nested_attributes_for :qanswers @@ -32,6 +47,8 @@ class Registration < ApplicationRecord if: -> { conference.try(:code_of_conduct).present? } } + validate :user_has_registration_ticket, if: -> { conference.registration_ticket_required? } + after_create :set_week, :subscribe_to_conference, :send_registration_mail ## @@ -51,9 +68,7 @@ def subscribe_to_conference end def send_registration_mail - if conference.email_settings.send_on_registration? - Mailbot.registration_mail(conference, user).deliver_later - end + Mailbot.registration_mail(conference, user).deliver_later if conference.email_settings.send_on_registration? end def set_week @@ -65,4 +80,14 @@ def registration_limit_not_exceed errors.add(:base, 'Registration limit exceeded') end end + + def user_has_registration_ticket + return if conference.registration_ticket_required? && + TicketPurchase.where(user: user, ticket: conference.registration_tickets).paid.any? + + errors.add(:base, 'You must purchase a registration ticket before registering') + if TicketPurchase.where(user: user, ticket: conference.registration_tickets).unpaid.any? + errors.add(:base, 'You currently have a ticket with an unfinished purchase') + end + end end diff --git a/app/models/registration_period.rb b/app/models/registration_period.rb index 66f02fe0a..32043ef17 100644 --- a/app/models/registration_period.rb +++ b/app/models/registration_period.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: registration_periods +# +# id :bigint not null, primary key +# end_date :date +# start_date :date +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class RegistrationPeriod < ApplicationRecord belongs_to :conference @@ -12,15 +23,21 @@ class RegistrationPeriod < ApplicationRecord private def before_end_of_conference - errors - .add(:start_date, "can't be after the conference end date (#{conference.end_date})") if conference&.end_date && start_date && (start_date > conference.end_date) + if conference&.end_date && start_date && (start_date > conference.end_date) + errors + .add(:start_date, "can't be after the conference end date (#{conference.end_date})") + end - errors - .add(:end_date, "can't be after the conference end date (#{conference.end_date})") if conference&.end_date && end_date && (end_date > conference.end_date) + if conference&.end_date && end_date && (end_date > conference.end_date) + errors + .add(:end_date, "can't be after the conference end date (#{conference.end_date})") + end end def start_date_before_end_date - errors - .add(:start_date, "can't be after the end date") if start_date && end_date && start_date > end_date + if start_date && end_date && start_date > end_date + errors + .add(:start_date, "can't be after the end date") + end end end diff --git a/app/models/resource.rb b/app/models/resource.rb index 991ce0cf7..a5e435fa9 100644 --- a/app/models/resource.rb +++ b/app/models/resource.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: resources +# +# id :bigint not null, primary key +# description :text +# name :string +# quantity :integer +# used :integer default(0) +# conference_id :integer +# class Resource < ApplicationRecord belongs_to :conference validates :name, :used, :quantity, presence: true diff --git a/app/models/role.rb b/app/models/role.rb index 2be87d7b3..5ad6e85d3 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -1,11 +1,30 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: roles +# +# id :bigint not null, primary key +# description :string +# name :string +# resource_type :string +# created_at :datetime +# updated_at :datetime +# resource_id :integer +# +# Indexes +# +# index_roles_on_name (name) +# index_roles_on_name_and_resource_type_and_resource_id (name,resource_type,resource_id) +# class Role < ApplicationRecord belongs_to :resource, polymorphic: true has_many :users_roles has_many :users, through: :users_roles - has_paper_trail on: [:create, :update], only: [:name, :description], meta: { conference_id: :resource_id } + has_paper_trail on: %i[create update], + only: %i[name description], + meta: { conference_id: :conference_id, organization_id: :organization_id } before_destroy :cancel scopify @@ -14,6 +33,14 @@ class Role < ApplicationRecord validates :name, uniqueness: { scope: :resource } + def conference_id + resource_type == 'Conference' ? resource_id : nil + end + + def organization_id + resource_type == 'Organization' ? resource_id : nil + end + private # Needed to ensure that removing all user from role doesn't remove role. diff --git a/app/models/room.rb b/app/models/room.rb index 4bd88834f..8bb55931e 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -1,5 +1,18 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: rooms +# +# id :bigint not null, primary key +# discussion_url :string +# guid :string not null +# name :string not null +# order :integer +# size :integer +# url :string +# venue_id :integer not null +# class Room < ApplicationRecord include RevisionCount belongs_to :venue @@ -13,9 +26,22 @@ class Room < ApplicationRecord validates :name, :venue_id, presence: true validates :size, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true + validates :order, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true - def conference - venue.conference + default_scope { order(order: :asc) } + + # Cache Busting on the events page, touch all events. + after_update :touch_conference_program + delegate :conference, to: :venue + + def embed_url + return if url.blank? + + if url.match?(/zoom.us/) & !url.match?('/zoom.us/wc') + return url.gsub('/j', '/wc/join') + end + + url end private @@ -28,4 +54,8 @@ def generate_guid def conference_id venue.conference_id end + + def touch_conference_program + conference.program.touch + end end diff --git a/app/models/schedule.rb b/app/models/schedule.rb index 1584733f5..40a2c37ff 100644 --- a/app/models/schedule.rb +++ b/app/models/schedule.rb @@ -1,13 +1,41 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: schedules +# +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# program_id :integer +# track_id :integer +# +# Indexes +# +# index_schedules_on_program_id (program_id) +# index_schedules_on_track_id (track_id) +# class Schedule < ApplicationRecord - belongs_to :program + belongs_to :program, touch: true belongs_to :track has_many :event_schedules, dependent: :destroy has_many :events, through: :event_schedules has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } + def with_all_associated_data + includes(event_schedule: { event: [:event_type] }) + end + + # TODO: User this or remove. + # A user's schedule includes: + # favorited events + # events speaking at + # volunteer events. + def for_user(user) + events.select { |event| event.planned_for_user?(user) } + end + private def conference_id diff --git a/app/models/splashpage.rb b/app/models/splashpage.rb index 5cd9ef0d3..650e06575 100644 --- a/app/models/splashpage.rb +++ b/app/models/splashpage.rb @@ -1,7 +1,35 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: splashpages +# +# id :bigint not null, primary key +# banner_photo_content_type :string +# banner_photo_file_name :string +# banner_photo_file_size :integer +# banner_photo_updated_at :datetime +# include_booths :boolean +# include_cfp :boolean default(FALSE) +# include_happening_now :boolean +# include_lodgings :boolean +# include_program :boolean +# include_registrations :boolean +# include_social_media :boolean +# include_sponsors :boolean +# include_tickets :boolean +# include_tracks :boolean +# include_venue :boolean +# public :boolean +# shuffle_highlights :boolean default(FALSE), not null +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class Splashpage < ApplicationRecord belongs_to :conference has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } + + mount_uploader :banner_photo, PictureUploader, mount_on: :banner_photo_file_name end diff --git a/app/models/sponsor.rb b/app/models/sponsor.rb index fa63411fa..4f7b21cc2 100644 --- a/app/models/sponsor.rb +++ b/app/models/sponsor.rb @@ -1,5 +1,20 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: sponsors +# +# id :bigint not null, primary key +# description :text +# logo_file_name :string +# name :string +# picture :string +# website_url :string +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# sponsorship_level_id :integer +# class Sponsor < ApplicationRecord belongs_to :sponsorship_level belongs_to :conference diff --git a/app/models/sponsorship_level.rb b/app/models/sponsorship_level.rb index 48f41594a..63634e27d 100644 --- a/app/models/sponsorship_level.rb +++ b/app/models/sponsorship_level.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: sponsorship_levels +# +# id :bigint not null, primary key +# position :integer +# title :string +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class SponsorshipLevel < ApplicationRecord validates :title, presence: true belongs_to :conference diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 0bb966407..e4cb7d4d3 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -1,10 +1,20 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: subscriptions +# +# id :bigint not null, primary key +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# user_id :integer +# class Subscription < ApplicationRecord belongs_to :conference belongs_to :user - has_paper_trail on: %i(create destroy), ignore: [:updated_at], meta: { conference_id: :conference_id } + has_paper_trail on: %i[create destroy], ignore: [:updated_at], meta: { conference_id: :conference_id } validates :user_id, uniqueness: { scope: :conference_id, message: 'already subscribed!' } end diff --git a/app/models/survey.rb b/app/models/survey.rb index 66783e90d..75465bd4f 100644 --- a/app/models/survey.rb +++ b/app/models/survey.rb @@ -1,11 +1,30 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: surveys +# +# id :bigint not null, primary key +# description :text +# end_date :datetime +# start_date :datetime +# surveyable_type :string +# target :integer default("after_conference") +# title :string +# created_at :datetime not null +# updated_at :datetime not null +# surveyable_id :integer +# +# Indexes +# +# index_surveys_on_surveyable_type_and_surveyable_id (surveyable_type,surveyable_id) +# class Survey < ActiveRecord::Base belongs_to :surveyable, polymorphic: true has_many :survey_questions, dependent: :destroy has_many :survey_submissions, dependent: :destroy - enum target: [:after_conference, :during_registration, :after_event] + enum target: { after_conference: 0, during_registration: 1, after_event: 2 } validates :title, presence: true ## diff --git a/app/models/survey_question.rb b/app/models/survey_question.rb index 748f70dd9..c53f153c2 100644 --- a/app/models/survey_question.rb +++ b/app/models/survey_question.rb @@ -1,13 +1,27 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: survey_questions +# +# id :bigint not null, primary key +# kind :integer default("boolean") +# mandatory :boolean default(FALSE) +# max_choices :integer +# min_choices :integer +# possible_answers :text +# title :string +# survey_id :integer +# class SurveyQuestion < ActiveRecord::Base belongs_to :survey has_many :survey_replies, dependent: :destroy # Order of this list should not be changed without proper action! - enum kind: [:boolean, :choice, :string, :text, :datetime, :numeric] + enum kind: { boolean: 0, choice: 1, string: 2, text: 3, datetime: 4, numeric: 5 } - ICONS = { boolean: 'circle-dot', choice: 'square-check', string: 'pen-to-square', text: 'align-left', datetime: 'clock', numeric: 'hashtag' }.freeze + ICONS = { boolean: 'circle-dot', choice: 'square-check', string: 'pen-to-square', text: 'align-left', +datetime: 'clock', numeric: 'hashtag' }.freeze validates :title, presence: true validates :possible_answers, :max_choices, :min_choices, presence: true, if: :choice? @@ -39,6 +53,9 @@ def max_choices=(value) private def max_choices_greater_than_min - errors.add(:max_choices, 'Max choices should not be less than min choices') if choice? && max_choices.to_i < min_choices.to_i + if choice? && max_choices.to_i < min_choices.to_i + errors.add(:max_choices, + 'Max choices should not be less than min choices') + end end end diff --git a/app/models/survey_reply.rb b/app/models/survey_reply.rb index bfba843b7..4659ac992 100644 --- a/app/models/survey_reply.rb +++ b/app/models/survey_reply.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: survey_replies +# +# id :bigint not null, primary key +# text :text +# created_at :datetime not null +# updated_at :datetime not null +# survey_question_id :integer +# user_id :integer +# class SurveyReply < ActiveRecord::Base belongs_to :user belongs_to :survey_question diff --git a/app/models/survey_submission.rb b/app/models/survey_submission.rb index f31198d4f..4d1e591f0 100644 --- a/app/models/survey_submission.rb +++ b/app/models/survey_submission.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: survey_submissions +# +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# survey_id :integer +# user_id :integer +# class SurveySubmission < ActiveRecord::Base belongs_to :user belongs_to :survey diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 677ef90f6..fd6efeace 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -1,9 +1,27 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: tickets +# +# id :bigint not null, primary key +# description :text +# email_body :text +# email_subject :string +# price_cents :integer default(0), not null +# price_currency :string default("USD"), not null +# registration_ticket :boolean default(FALSE) +# title :string not null +# visible :boolean default(TRUE) +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class Ticket < ApplicationRecord belongs_to :conference has_many :ticket_purchases, dependent: :destroy has_many :buyers, -> { distinct }, through: :ticket_purchases, source: :user + has_many :currency_conversions, through: :conferences has_paper_trail meta: { conference_id: :conference_id }, ignore: %i[updated_at] @@ -20,6 +38,8 @@ class Ticket < ApplicationRecord validates :price_cents, numericality: { greater_than_or_equal_to: 0 } + scope :visible, -> { where(visible: true) } + def bought?(user) buyers.include?(user) end @@ -57,12 +77,12 @@ def self.total_price(conference, user, paid: false) rescue Money::Bank::UnknownRate result = Money.new(-1, 'USD') end - result ? result : Money.new(0, 'USD') + result || Money.new(0, 'USD') end def self.total_price_user(conference, user, paid: false) tickets = TicketPurchase.where(conference: conference, user: user, paid: paid) - tickets.inject(0){ |sum, ticket| sum + (ticket.amount_paid * ticket.quantity) } + tickets.inject(0) { |sum, ticket| sum + (ticket.amount_paid * ticket.quantity) } end def tickets_turnover_total(id) @@ -83,7 +103,7 @@ def tickets_of_conference_have_same_currency tickets = Ticket.where(conference_id: conference_id) return if tickets.count.zero? || (tickets.count == 1 && self == tickets.first) - unless tickets.all?{|t| t.price_currency == price_currency } + unless tickets.all? { |t| t.price_currency == price_currency } errors.add(:price_currency, 'is different from the existing tickets of this conference.') end end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb index f8e6c2105..5a076ea9d 100644 --- a/app/models/ticket_purchase.rb +++ b/app/models/ticket_purchase.rb @@ -1,5 +1,21 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: ticket_purchases +# +# id :bigint not null, primary key +# amount_paid :float default(0.0) +# paid :boolean default(FALSE) +# quantity :integer default(1) +# week :integer +# created_at :datetime +# conference_id :integer +# payment_id :integer +# ticket_id :integer +# user_id :integer +# + class TicketPurchase < ApplicationRecord belongs_to :ticket belongs_to :user @@ -32,7 +48,7 @@ def self.purchase(conference, user, purchases) errors.push('You cannot buy more than one registration tickets.') else ActiveRecord::Base.transaction do - conference.tickets.each do |ticket| + conference.tickets.visible.each do |ticket| quantity = purchases[ticket.id.to_s].to_i # if the user bought the ticket and is still unpaid, just update the quantity purchase = if ticket.bought?(user) && ticket.unpaid?(user) @@ -40,9 +56,7 @@ def self.purchase(conference, user, purchases) else purchase_ticket(conference, quantity, ticket, user) end - if purchase && !purchase.save - errors.push(purchase.errors.full_messages) - end + errors.push(purchase.errors.full_messages) if purchase && !purchase.save end end end @@ -95,6 +109,12 @@ def registration_ticket_already_purchased errors.add(:quantity, 'cannot be greater than one for registration tickets.') end end + + def generate_confirmation_mail(event_template) + parser = EmailTemplateParser.new(conference, user) + values = parser.retrieve_values(nil, nil, quantity, ticket) + EmailTemplateParser.parse_template(event_template, values) + end end private @@ -105,6 +125,9 @@ def set_week end def count_purchased_registration_tickets(conference, purchases) + # TODO: WHAT CAUSED THIS??? + return 0 unless purchases + conference.tickets.for_registration.inject(0) do |sum, registration_ticket| sum + purchases[registration_ticket.id.to_s].to_i end diff --git a/app/models/ticket_scanning.rb b/app/models/ticket_scanning.rb index 6b422100e..6f9eda7fc 100644 --- a/app/models/ticket_scanning.rb +++ b/app/models/ticket_scanning.rb @@ -1,5 +1,14 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: ticket_scannings +# +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# physical_ticket_id :integer not null +# class TicketScanning < ApplicationRecord belongs_to :physical_ticket diff --git a/app/models/track.rb b/app/models/track.rb index d34bfbefc..d9c51f6b5 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -1,5 +1,33 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: tracks +# +# id :bigint not null, primary key +# cfp_active :boolean not null +# color :string +# description :text +# end_date :date +# guid :string not null +# name :string not null +# relevance :text +# short_name :string not null +# start_date :date +# state :string default("new"), not null +# created_at :datetime +# updated_at :datetime +# program_id :integer +# room_id :integer +# selected_schedule_id :integer +# submitter_id :integer +# +# Indexes +# +# index_tracks_on_room_id (room_id) +# index_tracks_on_selected_schedule_id (selected_schedule_id) +# index_tracks_on_submitter_id (submitter_id) +# class Track < ApplicationRecord include ActiveRecord::Transitions include RevisionCount @@ -15,6 +43,7 @@ class Track < ApplicationRecord has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } + before_validation :capitalize_color before_create :generate_guid validates :name, presence: true validates :color, format: /\A#[0-9A-F]{6}\z/ @@ -26,7 +55,7 @@ class Track < ApplicationRecord } validates :state, presence: true, - inclusion: { in: %w(new to_accept accepted confirmed to_reject rejected canceled withdrawn) } + inclusion: { in: %w[new to_accept accepted confirmed to_reject rejected canceled withdrawn] } validates :cfp_active, inclusion: { in: [true, false] } validates :start_date, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :end_date, presence: true, if: :self_organized_and_accepted_or_confirmed? @@ -38,8 +67,6 @@ class Track < ApplicationRecord validate :valid_room validate :overlapping - before_validation :capitalize_color - scope :accepted, -> { where(state: 'accepted') } scope :confirmed, -> { where(state: 'confirmed') } scope :cfp_active, -> { where(cfp_active: true) } @@ -56,34 +83,34 @@ class Track < ApplicationRecord state :withdrawn event :restart do - transitions to: :new, from: [:rejected, :withdrawn, :canceled] + transitions to: :new, from: %i[rejected withdrawn canceled] end event :to_accept do - transitions to: :to_accept, from: [:new, :to_reject] + transitions to: :to_accept, from: %i[new to_reject] end event :accept do - transitions to: :accepted, from: [:new, :to_accept], on_transition: :create_organizer_role + transitions to: :accepted, from: %i[new to_accept], on_transition: :create_organizer_role end event :confirm do transitions to: :confirmed, from: [:accepted], on_transition: :assign_role_to_submitter end event :to_reject do - transitions to: :to_reject, from: [:new, :to_accept] + transitions to: :to_reject, from: %i[new to_accept] end event :reject do - transitions to: :rejected, from: [:new, :to_reject] + transitions to: :rejected, from: %i[new to_reject] end event :cancel do - transitions to: :canceled, from: [:to_accept, :to_reject, :accepted, :confirmed], on_transition: :revoke_role_and_cleanup + transitions to: :canceled, from: %i[to_accept to_reject accepted confirmed], + on_transition: :revoke_role_and_cleanup end event :withdraw do - transitions to: :withdrawn, from: [:new, :to_accept, :to_reject, :accepted, :confirmed], on_transition: :revoke_role_and_cleanup + transitions to: :withdrawn, from: %i[new to_accept to_reject accepted confirmed], + on_transition: :revoke_role_and_cleanup end end - def conference - program.conference - end + delegate :conference, to: :program ## # Checks if the track is self-organized @@ -176,9 +203,9 @@ def update_state(transition) def generate_guid guid = SecureRandom.urlsafe_base64 -# begin -# guid = SecureRandom.urlsafe_base64 -# end while Person.where(:guid => guid).exists? + # begin + # guid = SecureRandom.urlsafe_base64 + # end while Person.where(:guid => guid).exists? self.guid = guid end @@ -200,10 +227,18 @@ def create_organizer_role # Verify that the track's dates are between the conference's dates # def dates_within_conference_dates - return unless start_date && end_date && program.try(:conference).try(:start_date) && program.try(:conference).try(:end_date) + unless start_date && end_date && program.try(:conference).try(:start_date) && program.try(:conference).try(:end_date) + return + end - errors.add(:start_date, "can't be outside of the conference's dates (#{program.conference.start_date}-#{program.conference.end_date})") unless (program.conference.start_date..program.conference.end_date).cover?(start_date) - errors.add(:end_date, "can't be outside of the conference's dates (#{program.conference.start_date}-#{program.conference.end_date})") unless (program.conference.start_date..program.conference.end_date).cover?(end_date) + unless (program.conference.start_date..program.conference.end_date).cover?(start_date) + errors.add(:start_date, + "can't be outside of the conference's dates (#{program.conference.start_date}-#{program.conference.end_date})") + end + unless (program.conference.start_date..program.conference.end_date).cover?(end_date) + errors.add(:end_date, + "can't be outside of the conference's dates (#{program.conference.start_date}-#{program.conference.end_date})") + end end ## @@ -221,7 +256,10 @@ def start_date_before_end_date def valid_room return unless room.try(:venue).try(:conference) && program.try(:conference) - errors.add(:room, "must be a room of #{program.conference.venue.name}") unless room.venue.conference == program.conference + unless room.venue.conference == program.conference + errors.add(:room, + "must be a room of #{program.conference.venue.name}") + end end ## @@ -233,12 +271,12 @@ def overlapping (program.tracks.accepted + program.tracks.confirmed - [self]).each do |existing_track| next unless existing_track.room == room && existing_track.start_date && existing_track.end_date - if start_date >= existing_track.start_date && start_date <= existing_track.end_date || - end_date >= existing_track.start_date && end_date <= existing_track.end_date || - start_date <= existing_track.start_date && end_date >= existing_track.end_date - errors.add(:track, 'has overlapping dates with a confirmed or accepted track in the same room') - break - end + next unless (start_date >= existing_track.start_date && start_date <= existing_track.end_date) || + (end_date >= existing_track.start_date && end_date <= existing_track.end_date) || + (start_date <= existing_track.start_date && end_date >= existing_track.end_date) + + errors.add(:track, 'has overlapping dates with a confirmed or accepted track in the same room') + break end end end diff --git a/app/models/user.rb b/app/models/user.rb index 993d48928..bcf38b4db 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,52 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: users +# +# id :bigint not null, primary key +# affiliation :string +# avatar_content_type :string +# avatar_file_name :string +# avatar_file_size :integer +# avatar_updated_at :datetime +# biography :text +# confirmation_sent_at :datetime +# confirmation_token :string +# confirmed_at :datetime +# current_sign_in_at :datetime +# current_sign_in_ip :string +# email :string default(""), not null +# email_public :boolean default(FALSE) +# encrypted_password :string default(""), not null +# is_admin :boolean default(FALSE) +# is_disabled :boolean default(FALSE) +# languages :string +# last_sign_in_at :datetime +# last_sign_in_ip :string +# mobile :string +# name :string +# nickname :string +# picture :string +# remember_created_at :datetime +# reset_password_sent_at :datetime +# reset_password_token :string +# sign_in_count :integer default(0) +# timezone :string +# tshirt :string +# unconfirmed_email :string +# username :string +# volunteer_experience :text +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# index_users_on_confirmation_token (confirmation_token) UNIQUE +# index_users_on_email (email) UNIQUE +# index_users_on_reset_password_token (reset_password_token) UNIQUE +# index_users_on_username (username) UNIQUE +# class IChainRecordNotFound < StandardError end @@ -7,6 +54,8 @@ class UserDisabled < StandardError end class User < ApplicationRecord + include TrackSavedChanges + # prevent N+1 queries with has_cached_role? by preloading roles *always* default_scope { preload(:roles) } @@ -17,7 +66,7 @@ def by_conference(conference) end end has_many :tickets, through: :ticket_purchases, source: :ticket do - def for_registration conference + def for_registration(conference) where(conference: conference, registration_ticket: true).first end end @@ -26,18 +75,34 @@ def for_registration conference rolify has_many :roles, through: :users_roles, dependent: :destroy - has_paper_trail on: [:create, :update], ignore: [:sign_in_count, :remember_created_at, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip, :unconfirmed_email, - :avatar_content_type, :avatar_file_size, :avatar_updated_at, :updated_at, :confirmation_sent_at, :confirmation_token, :reset_password_token] + has_paper_trail on: %i[create update], ignore: %i[sign_in_count remember_created_at current_sign_in_at last_sign_in_at current_sign_in_ip last_sign_in_ip unconfirmed_email + avatar_content_type avatar_file_size avatar_updated_at updated_at confirmation_sent_at confirmation_token reset_password_token] + # A user may have an uploaded avatar or use gravatar. + # The uploaded picture takes precedence. include Gravtastic gravtastic size: 32 + mount_uploader :picture, PictureUploader, mount_on: :picture + before_create :setup_role after_save :touch_events + # Note that using after_create_commit and after_update_commit does not work. + # See https://github.com/CactusPuppy/snapcon/pull/43#discussion_r609458034 + after_commit :mailbluster_create_lead, on: :create + after_commit :mailbluster_delete_lead, on: :destroy + after_commit :mailbluster_update_lead, on: :update, if: lambda { |user| + %w[name email].any? do |key| + user.ts_saved_changes.key? key + end + } + # add scope - scope :comment_notifiable, ->(conference) {joins(:roles).where('roles.name IN (?)', [:organizer, :cfp]).where('roles.resource_type = ? AND roles.resource_id = ?', 'Conference', conference.id)} + scope :comment_notifiable, lambda { |conference| + joins(:roles).where('roles.name IN (?)', %i[organizer cfp]).where('roles.resource_type = ? AND roles.resource_id = ?', 'Conference', conference.id) + } # scopes for user distributions scope :recent, lambda { @@ -52,11 +117,13 @@ def for_registration conference devise_modules = [] devise_modules += if ENV.fetch('OSEM_ICHAIN_ENABLED', nil) == 'true' - [:ichain_authenticatable, :ichain_registerable, :omniauthable, omniauth_providers: []] + [:ichain_authenticatable, :ichain_registerable, :omniauthable, { omniauth_providers: [] }] else [:database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, - :omniauthable, omniauth_providers: [:suse, :google, :facebook, :github]] + :omniauthable, + { omniauth_providers: %i[suse google facebook github discourse] }] + # omniauth_providers: [:google, :discourse] end devise(*devise_modules) @@ -67,9 +134,11 @@ def for_registration conference has_many :event_users, dependent: :destroy has_many :events, -> { distinct }, through: :event_users - has_many :presented_events, -> { joins(:event_users).where(event_users: {event_role: 'speaker'}).distinct }, through: :event_users, source: :event + has_many :presented_events, lambda { + joins(:event_users).where(event_users: { event_role: 'speaker' }).distinct + }, through: :event_users, source: :event has_many :registrations, dependent: :destroy do - def for_conference conference + def for_conference(conference) where(conference: conference).first end end @@ -80,11 +149,12 @@ def for_conference conference has_many :voted_events, through: :votes, source: :events has_many :subscriptions, dependent: :destroy has_many :tracks, foreign_key: 'submitter_id' - has_many :booth_requests has_many :booth_requests, dependent: :destroy has_many :booths, through: :booth_requests has_many :survey_replies has_many :survey_submissions + has_and_belongs_to_many :favourite_events, class_name: 'Event' + accepts_nested_attributes_for :roles scope :admin, -> { where(is_admin: true) } @@ -96,7 +166,7 @@ def for_conference conference validates :username, uniqueness: { - case_sensitive: false + case_sensitive: false }, presence: true @@ -116,7 +186,7 @@ def for_conference conference # === Returns # * +true+ if the user attended the event # * +false+ if the user did not attend the event - def attended_event? event + def attended_event?(event) event_registration = event.events_registrations.find_by(registration: registrations) return false unless event_registration.present? @@ -124,30 +194,61 @@ def attended_event? event event_registration.attended end - def mark_attendance_for_conference conference + def mark_attendance_for_conference(conference) registration = registrations.for_conference(conference) + return true if registration&.attended + registration.attended = true registration.save end + def mark_attendance_for_event(event) + event_registration = event.events_registrations.find_by(registration: registrations) + return true if event_registration&.attended + + if event_registration.blank? + conference_registration = registrations.for_conference(event.conference) + event_registration = event.events_registrations.build + event_registration.registration = conference_registration + end + event_registration.attended = true + event_registration.save + end + def name - self[:name].blank? ? username : self[:name] + self[:name].presence || username end ## # Checks if a user has registered to an event # ====Returns # * +true+ or +false+ - def registered_to_event? event + def registered_to_event?(event) event.registrations.include? registrations.find_by(conference: event.program.conference) end - def subscribed? conference + def subscribed?(conference) subscriptions.find_by(conference_id: conference.id).present? end - def supports? conference - ticket_purchases.find_by(conference_id: conference.id).present? + def supports?(conference) + ticket_purchases.find_by(conference_id: conference.id, paid: true).present? + end + + ## + # Returns a user's profile picture URL. + # Partials should *not* directly call `gravatar_url` + def profile_picture(opts = {}) + return gravatar_url(opts) unless picture.present? + + size = (opts[:size] || 0).to_i + if size < 50 + picture.tiny.url + elsif size <= 100 + picture.thumb.url + else + picture.large.url + end end def self.for_ichain_username(username, attributes) @@ -236,6 +337,12 @@ def get_roles result end + # TODO: Use a real authorization in the right place.... + def manages_volunteers?(conference) + organizer_roles = get_roles['organizer'] + organizer_roles&.include?(conference.short_title) # TODO: or Volunteer Coorinator. + end + def registered registrations = self.registrations if registrations.count == 0 @@ -263,23 +370,57 @@ def confirmed? end def proposals(conference) - events.where('program_id = ? AND (event_users.event_role=? OR event_users.event_role=?)', conference.program.id, 'submitter', 'speaker') + events.where('program_id = ? AND (event_users.event_role=? OR event_users.event_role=?)', conference.program.id, + 'submitter', 'speaker') end def proposal_count(conference) proposals(conference).count end + def volunteer_duties(conference) + events.where(program_id: conference.program.id, 'event_users.event_role': 'volunteer') + end + + def count_registration_tickets(conference) + count = 0 + ticket_purchases.by_conference(conference).each do |ticket_purchase| + count += 1 if ticket_purchase.ticket.registration_ticket + end + + count + end + def self.empty? User.count == 1 && User.first.email == 'deleted@localhost.osem' end + def dropdwon_display + more_info = email_public? ? username : "#{username} #{email}" + "#{name} (#{more_info})" + end + private def setup_role self.is_admin = true if User.empty? end + def mailbluster_create_lead + MailblusterCreateLeadJob.perform_later(id) + ts_reset_saved_changes + end + + def mailbluster_delete_lead + MailblusterDeleteLeadJob.perform_later(email) + ts_reset_saved_changes + end + + def mailbluster_update_lead + MailblusterEditLeadJob.perform_later(id, old_email: ts_saved_changes.fetch('email', [nil])[0]) + ts_reset_saved_changes + end + def touch_events event_users.each(&:touch) end @@ -288,12 +429,10 @@ def touch_events # Check if biography has an allowed number of words. Used as validation. # def biography_limit - if biography.present? - errors.add(:biography, 'is limited to 150 words.') if biography.split.length > 150 - end + errors.add(:biography, 'is limited to 200 words.') if biography.present? && (biography.split.length > 200) end - def send_devise_notification(notification, *args) - devise_mailer.send(notification, self, *args).deliver_later + def send_devise_notification(notification, *) + devise_mailer.send(notification, self, *).deliver_later end end diff --git a/app/models/users_role.rb b/app/models/users_role.rb index b5dbe9fbd..621d42d37 100644 --- a/app/models/users_role.rb +++ b/app/models/users_role.rb @@ -1,14 +1,23 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: users_roles +# +# id :bigint not null, primary key +# role_id :integer +# user_id :integer +# +# Indexes +# +# index_users_roles_on_user_id_and_role_id (user_id,role_id) +# class UsersRole < ApplicationRecord belongs_to :role belongs_to :user - has_paper_trail on: [:create, :destroy], meta: { conference_id: :conference_id } + delegate :conference_id, :organization_id, to: :role - private - - def conference_id - role.resource_id - end + has_paper_trail on: %i[create destroy], + meta: { conference_id: :conference_id, organization_id: :organization_id } end diff --git a/app/models/vchoice.rb b/app/models/vchoice.rb index bf5d05a6f..3481338c7 100644 --- a/app/models/vchoice.rb +++ b/app/models/vchoice.rb @@ -1,5 +1,13 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: vchoices +# +# id :bigint not null, primary key +# vday_id :integer +# vposition_id :integer +# class Vchoice < ApplicationRecord belongs_to :vday belongs_to :vposition diff --git a/app/models/vday.rb b/app/models/vday.rb index 5a9b52cfa..02ce8ecc4 100644 --- a/app/models/vday.rb +++ b/app/models/vday.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: vdays +# +# id :bigint not null, primary key +# day :date +# description :text +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class Vday < ApplicationRecord belongs_to :conference diff --git a/app/models/venue.rb b/app/models/venue.rb index 0064b5c46..992a027b6 100644 --- a/app/models/venue.rb +++ b/app/models/venue.rb @@ -1,25 +1,48 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: venues +# +# id :bigint not null, primary key +# city :string +# country :string +# description :text +# guid :string +# latitude :string +# longitude :string +# name :string +# photo_file_name :string +# picture :string +# postalcode :string +# street :string +# website :string +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class Venue < ApplicationRecord belongs_to :conference has_one :commercial, as: :commercialable, dependent: :destroy has_many :rooms, dependent: :destroy + before_save :send_mail_notification before_create :generate_guid - has_paper_trail ignore: [:updated_at, :guid], meta: { conference_id: :conference_id } + has_paper_trail ignore: %i[updated_at guid], meta: { conference_id: :conference_id } accepts_nested_attributes_for :commercial, allow_destroy: true validates :name, :street, :city, :country, presence: true mount_uploader :picture, PictureUploader, mount_on: :photo_file_name - before_save :send_mail_notification - def address "#{street}, #{city}, #{country_name}" end + # TODO-SNAPCON: (mb) Fix this to use the country shortname? def country_name + return unless country + I18nData.countries[country] end @@ -36,10 +59,12 @@ def send_mail_notification def notify_on_venue_changed? return false unless conference.try(:email_settings).try(:send_on_venue_updated) # do not notify unless the address changed - return false unless saved_change_to_name? || saved_change_to_street? || saved_change_to_city? || saved_change_to_country? + unless saved_change_to_name? || saved_change_to_street? || saved_change_to_city? || saved_change_to_country? + return false + end # do not notify unless the mail content is set up - (!conference.email_settings.venue_updated_subject.blank? && !conference.email_settings.venue_updated_body.blank?) + conference.email_settings.venue_updated_subject.present? && conference.email_settings.venue_updated_body.present? end # TODO: create a module to be mixed into model to perform same operation diff --git a/app/models/vote.rb b/app/models/vote.rb index cdd1e4cbe..85cf784f0 100644 --- a/app/models/vote.rb +++ b/app/models/vote.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: votes +# +# id :bigint not null, primary key +# rating :integer +# created_at :datetime +# updated_at :datetime +# event_id :integer +# user_id :integer +# class Vote < ApplicationRecord belongs_to :user belongs_to :event, touch: true diff --git a/app/models/vposition.rb b/app/models/vposition.rb index 6599bc8ab..5d726fe38 100644 --- a/app/models/vposition.rb +++ b/app/models/vposition.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: vpositions +# +# id :bigint not null, primary key +# description :text +# title :string not null +# created_at :datetime +# updated_at :datetime +# conference_id :integer +# class Vposition < ApplicationRecord belongs_to :conference diff --git a/app/pdfs/ticket_pdf.rb b/app/pdfs/ticket_pdf.rb index cb624b1d1..de9c5ce3f 100644 --- a/app/pdfs/ticket_pdf.rb +++ b/app/pdfs/ticket_pdf.rb @@ -99,8 +99,8 @@ def draw_third_square end def draw_fourth_square - x = @mid_horizontal + (@right - @mid_horizontal - 180) / 2 - y = cursor - (bounds.top - @mid_vertical - 180) / 2 + x = @mid_horizontal + ((@right - @mid_horizontal - 180) / 2) + y = cursor - ((bounds.top - @mid_vertical - 180) / 2) print_qr_code(@physical_ticket.token, pos: [x, y], extent: 180, stroke: false) end end diff --git a/app/serializers/conference_serializer.rb b/app/serializers/conference_serializer.rb index 41eb2e28d..32d83dc71 100644 --- a/app/serializers/conference_serializer.rb +++ b/app/serializers/conference_serializer.rb @@ -1,5 +1,40 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: conferences +# +# id :bigint not null, primary key +# booth_limit :integer default(0) +# color :string +# custom_css :text +# custom_domain :string +# description :text +# end_date :date not null +# end_hour :integer default(20) +# events_per_week :text +# guid :string not null +# logo_file_name :string +# picture :string +# registration_limit :integer default(0) +# revision :integer default(0), not null +# short_title :string not null +# start_date :date not null +# start_hour :integer default(9) +# ticket_layout :integer default("portrait") +# timezone :string not null +# title :string not null +# use_vdays :boolean default(FALSE) +# use_volunteers :boolean +# use_vpositions :boolean default(FALSE) +# created_at :datetime +# updated_at :datetime +# organization_id :integer +# +# Indexes +# +# index_conferences_on_organization_id (organization_id) +# class ConferenceSerializer < ActiveModel::Serializer include ApplicationHelper include Rails.application.routes.url_helpers @@ -9,41 +44,41 @@ class ConferenceSerializer < ActiveModel::Serializer :date_range, :revision def difficulty_levels - object.program.difficulty_levels.map do |difficulty_level| { id: difficulty_level.id, - title: difficulty_level.title, - description: difficulty_level.description - } + object.program.difficulty_levels.map do |difficulty_level| + { id: difficulty_level.id, + title: difficulty_level.title, + description: difficulty_level.description } end end def event_types - object.program.event_types.map do |event_type| { id: event_type.id, - title: event_type.title, - length: event_type.length, - description: event_type.description - } + object.program.event_types.map do |event_type| + { id: event_type.id, + title: event_type.title, + length: event_type.length, + description: event_type.description } end end def rooms if object.venue - object.venue.rooms.map do |room| { id: room.id, - size: room.size, - events: room.event_schedules.map do |event_schedule| { guid: event_schedule.event.title, - title: event_schedule.event.title, - subtitle: event_schedule.event.subtitle, - abstract: event_schedule.event.abstract, - description: event_schedule.event.description, - is_highlight: event_schedule.event.is_highlight, - require_registration: event_schedule.event.require_registration, - start_time: event_schedule.start_time, - event_type_id: event_schedule.event.event_type.id, - difficulty_level_id: event_schedule.event.difficulty_level_id, - track_id: event_schedule.event.track_id, - speaker_names: event_schedule.event.speaker_names - } - end - } + object.venue.rooms.map do |room| + { id: room.id, + size: room.size, + events: room.event_schedules.map do |event_schedule| + { guid: event_schedule.event.title, + title: event_schedule.event.title, + subtitle: event_schedule.event.subtitle, + abstract: event_schedule.event.abstract, + description: event_schedule.event.description, + is_highlight: event_schedule.event.is_highlight, + require_registration: event_schedule.event.require_registration, + start_time: event_schedule.start_time, + event_type_id: event_schedule.event.event_type.id, + difficulty_level_id: event_schedule.event.difficulty_level_id, + track_id: event_schedule.event.track_id, + speaker_names: event_schedule.event.speaker_names } + end } end else [] @@ -51,10 +86,10 @@ def rooms end def tracks - object.program.tracks.map do |track| { 'id' => track.id, - 'name' => track.name, - 'description' => track.description - } + object.program.tracks.map do |track| + { 'id' => track.id, + 'name' => track.name, + 'description' => track.description } end end diff --git a/app/serializers/event_schedule_serializer.rb b/app/serializers/event_schedule_serializer.rb index 44ecdbb96..086bfd210 100644 --- a/app/serializers/event_schedule_serializer.rb +++ b/app/serializers/event_schedule_serializer.rb @@ -1,5 +1,25 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: event_schedules +# +# id :bigint not null, primary key +# enabled :boolean default(TRUE) +# start_time :datetime +# created_at :datetime not null +# updated_at :datetime not null +# event_id :integer +# room_id :integer +# schedule_id :integer +# +# Indexes +# +# index_event_schedules_on_event_id (event_id) +# index_event_schedules_on_event_id_and_schedule_id (event_id,schedule_id) UNIQUE +# index_event_schedules_on_room_id (room_id) +# index_event_schedules_on_schedule_id (schedule_id) +# class EventScheduleSerializer < ActiveModel::Serializer include ActionView::Helpers::TextHelper diff --git a/app/serializers/event_serializer.rb b/app/serializers/event_serializer.rb index 0f5b136fc..6f853a7b1 100644 --- a/app/serializers/event_serializer.rb +++ b/app/serializers/event_serializer.rb @@ -1,5 +1,43 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: events +# +# id :bigint not null, primary key +# abstract :text +# comments_count :integer default(0), not null +# committee_review :text +# description :text +# guid :string not null +# is_highlight :boolean default(FALSE) +# language :string +# max_attendees :integer +# presentation_mode :integer +# progress :string default("new"), not null +# proposal_additional_speakers :text +# public :boolean default(TRUE) +# require_registration :boolean +# start_time :datetime +# state :string default("new"), not null +# submission_text :text +# subtitle :string +# superevent :boolean +# title :string not null +# week :integer +# created_at :datetime +# updated_at :datetime +# difficulty_level_id :integer +# event_type_id :integer +# parent_id :integer +# program_id :integer +# room_id :integer +# track_id :integer +# +# Foreign Keys +# +# fk_rails_... (parent_id => events.id) +# class EventSerializer < ActiveModel::Serializer include ActionView::Helpers::TextHelper include Rails.application.routes.url_helpers diff --git a/app/serializers/room_serializer.rb b/app/serializers/room_serializer.rb index c75db316f..014aac4e6 100644 --- a/app/serializers/room_serializer.rb +++ b/app/serializers/room_serializer.rb @@ -1,5 +1,18 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: rooms +# +# id :bigint not null, primary key +# discussion_url :string +# guid :string not null +# name :string not null +# order :integer +# size :integer +# url :string +# venue_id :integer not null +# class RoomSerializer < ActiveModel::Serializer attributes :guid, :name, :description diff --git a/app/serializers/track_serializer.rb b/app/serializers/track_serializer.rb index 0eb7bebbd..8462b3fef 100644 --- a/app/serializers/track_serializer.rb +++ b/app/serializers/track_serializer.rb @@ -1,5 +1,33 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: tracks +# +# id :bigint not null, primary key +# cfp_active :boolean not null +# color :string +# description :text +# end_date :date +# guid :string not null +# name :string not null +# relevance :text +# short_name :string not null +# start_date :date +# state :string default("new"), not null +# created_at :datetime +# updated_at :datetime +# program_id :integer +# room_id :integer +# selected_schedule_id :integer +# submitter_id :integer +# +# Indexes +# +# index_tracks_on_room_id (room_id) +# index_tracks_on_selected_schedule_id (selected_schedule_id) +# index_tracks_on_submitter_id (submitter_id) +# class TrackSerializer < ActiveModel::Serializer attributes :guid, :name, :color end diff --git a/app/services/email_template_parser.rb b/app/services/email_template_parser.rb new file mode 100644 index 000000000..9c74e2d78 --- /dev/null +++ b/app/services/email_template_parser.rb @@ -0,0 +1,76 @@ +class EmailTemplateParser + def initialize(conference, user) + @conference = conference + @user = user + end + + # rubocop:disable Metrics/AbcSize, Metrics/ParameterLists + def retrieve_values(event = nil, booth = nil, quantity = nil, ticket = nil) + h = { + 'email' => @user.email, + 'name' => @user.name, + 'conference' => @conference.title, + 'conference_start_date' => @conference.start_date, + 'conference_end_date' => @conference.end_date, + 'registrationlink' => Rails.application.routes.url_helpers.conference_conference_registration_url( + @conference.short_title, host: Rails.application.routes.default_url_options[:host] + ), + 'conference_splash_link' => Rails.application.routes.url_helpers.conference_url( + @conference.short_title, host: Rails.application.routes.default_url_options[:host] + ), + + 'schedule_link' => Rails.application.routes.url_helpers.conference_schedule_url( + @conference.short_title, host: Rails.application.routes.default_url_options[:host] + ) + } + if @conference.program.cfp + h['cfp_start_date'] = @conference.program.cfp.start_date + h['cfp_end_date'] = @conference.program.cfp.end_date + else + h['cfp_start_date'] = 'Unknown' + h['cfp_end_date'] = 'Unknown' + end + if @conference.venue + h['venue'] = @conference.venue.name + h['venue_address'] = @conference.venue.address + else + h['venue'] = 'Unknown' + h['venue_address'] = 'Unknown' + end + if @conference.registration_period + h['registration_start_date'] = @conference.registration_period.start_date + h['registration_end_date'] = @conference.registration_period.end_date + end + if event + h['eventtitle'] = event.title + h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url( + @conference.short_title, host: ENV.fetch('OSEM_HOSTNAME', 'localhost:3000') + ) + h['committee_review'] = event.committee_review + h['committee_review_html'] = ApplicationController.helpers.markdown(event.committee_review) + end + if booth + h['booth_title'] = booth.title + end + if quantity + h['ticket_quantity'] = quantity.to_s + end + if ticket + h['ticket_title'] = ticket.title + h['ticket_purchase_id'] = ticket.id.to_s + end + h + end + + def self.parse_template(text, values) + values.each do |key, value| + if value.is_a?(Date) + text = text.gsub "{#{key}}", value.strftime('%Y-%m-%d') if text.present? + else + text = text.gsub "{#{key}}", value unless text.blank? || value.blank? + end + end + text + end + # rubocop:enable Metrics/AbcSize, Metrics/ParameterLists +end diff --git a/app/services/full_calendar_formatter.rb b/app/services/full_calendar_formatter.rb new file mode 100644 index 000000000..7f9a21594 --- /dev/null +++ b/app/services/full_calendar_formatter.rb @@ -0,0 +1,49 @@ +class FullCalendarFormatter + def self.rooms_to_resources(rooms) + rooms.map { |room| room_to_resource(room) }.to_json + end + + def self.event_schedules_to_resources(event_schedules) + return '[]' if event_schedules.empty? + + conference = event_schedules.first.schedule.program.conference + event_schedules.map { |event_schedule| event_schedule_to_resource(conference, event_schedule) }.to_json + end + + class << self + include FormatHelper + + private + + def room_to_resource(room) + { + id: room.guid, + title: room.name, + order: room.order + } + end + + def event_schedule_to_resource(conference, event_schedule) + event = event_schedule.event + event_type_color = event.event_type.color + url = Rails.application.routes.url_helpers.conference_program_proposal_path(conference.short_title, event.id) + background_event = event.event_type.title.match(/Break/i) + rooms = background_event ? conference.venue.rooms.pluck(:guid) : [event_schedule.room.guid] + + { + id: event.guid, + title: event.title, + start: event_schedule.start_time_in_conference_timezone, + end: event_schedule.end_time_in_conference_timezone, + resourceIds: rooms, + url: url, + borderColor: event_type_color, + backgroundColor: event_type_color, + textColor: contrast_color(event_type_color), + className: "fc-event-track-#{event.track&.short_name || 'none'}", + display: background_event ? 'background' : 'auto', + has_parent: event.parent_event.present? + } + end + end +end diff --git a/app/services/mailbluster_manager.rb b/app/services/mailbluster_manager.rb new file mode 100644 index 000000000..2bb06f4e9 --- /dev/null +++ b/app/services/mailbluster_manager.rb @@ -0,0 +1,40 @@ +class MailblusterManager + include HTTParty + base_uri 'https://api.mailbluster.com/api/leads/' + @auth_headers = { + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => ENV.fetch('MAILBLUSTER_API_KEY', nil) + } + } + + def self.query_api(method, path, body: {}) + options = @auth_headers.merge(body: body.to_json) + send(method, path, options).parsed_response + end + + def self.create_lead(user) + query_api(:post, '/', body: { + 'email' => user.email, + 'firstName' => user.name, + 'overrideExisting' => true, + 'subscribed' => true, + 'tags' => [ENV.fetch('OSEM_NAME', 'snapcon')] + }) + end + + def self.edit_lead(user, add_tags: [], remove_tags: [], old_email: nil) + email_hash = Digest::MD5.hexdigest(old_email.presence || user.email) + query_api(:put, "/#{email_hash}", body: { + 'email' => user.email, + 'firstName' => user.name, + 'addTags' => add_tags, + 'removeTags' => remove_tags + }) + end + + def self.delete_lead(email) + email_hash = Digest::MD5.hexdigest email + query_api(:delete, "/#{email_hash}") + end +end diff --git a/app/uploaders/picture_uploader.rb b/app/uploaders/picture_uploader.rb index dba60332c..a243d9d9b 100644 --- a/app/uploaders/picture_uploader.rb +++ b/app/uploaders/picture_uploader.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true class PictureUploader < CarrierWave::Uploader::Base @@ -64,6 +63,10 @@ def store_dir process resize_to_fit: [100, 100] end + version :tiny do + process resize_to_fit: [32, 32] + end + version :first, if: :sponsor? version :first do process resize_and_pad: [320, 180, 'white'] @@ -85,11 +88,11 @@ def store_dir end def extension_allowlist - %w(jpg jpeg gif png) + %w[jpg jpeg gif png] end def content_type_allowlist - /image\// + %r{image/} end private diff --git a/app/views/admin/booths/_all_booths.xlsx.axlsx b/app/views/admin/booths/_all_booths.xlsx.axlsx index 5a0a1d48f..0c44a9e5b 100644 --- a/app/views/admin/booths/_all_booths.xlsx.axlsx +++ b/app/views/admin/booths/_all_booths.xlsx.axlsx @@ -2,7 +2,7 @@ wb.add_worksheet(name: "all #{(t 'booth').pluralize}") do |sheet| bold_style = wb.styles.add_style(b: true) - wrap_text = wb.styles.add_style alignment: {wrap_text: true} + wrap_text = wb.styles.add_style alignment: { wrap_text: true } row = ["#{(t 'booth').capitalize} ID", 'Title', 'Description', @@ -23,7 +23,7 @@ wb.add_worksheet(name: "all #{(t 'booth').pluralize}") do |sheet| row << booth.submitter_relationship row << booth.website_url row << booth.state - sheet.add_row row , style: wrap_text - sheet.column_widths 10,20,35,35,20,30,35,10 + sheet.add_row row, style: wrap_text + sheet.column_widths 10, 20, 35, 35, 20, 30, 35, 10 end end diff --git a/app/views/admin/booths/_confirmed_booths.xlsx.axlsx b/app/views/admin/booths/_confirmed_booths.xlsx.axlsx index a6016703c..91da23c7c 100644 --- a/app/views/admin/booths/_confirmed_booths.xlsx.axlsx +++ b/app/views/admin/booths/_confirmed_booths.xlsx.axlsx @@ -2,7 +2,7 @@ wb.add_worksheet(name: "all #{(t 'booth').pluralize}") do |sheet| bold_style = wb.styles.add_style(b: true) - wrap_text = wb.styles.add_style alignment: {wrap_text: true} + wrap_text = wb.styles.add_style alignment: { wrap_text: true } row = ["#{(t 'booth').capitalize} ID", 'Title', 'Description', @@ -23,7 +23,7 @@ wb.add_worksheet(name: "all #{(t 'booth').pluralize}") do |sheet| row << booth.submitter_relationship row << booth.website_url row << booth.state - sheet.add_row row , style: wrap_text - sheet.column_widths 10,20,35,35,20,30,35,10 + sheet.add_row row, style: wrap_text + sheet.column_widths 10, 20, 35, 35, 20, 30, 35, 10 end end diff --git a/app/views/admin/cfps/_form.html.haml b/app/views/admin/cfps/_form.html.haml index f6af36874..3baf8c219 100644 --- a/app/views/admin/cfps/_form.html.haml +++ b/app/views/admin/cfps/_form.html.haml @@ -7,10 +7,9 @@ = f.label :end_date, "End Date" = f.text_field :end_date, class: 'form-control', id: 'registration-period-end-datepicker', start_date: @conference.start_date, end_date: @conference.end_date .form-group - = f.label :description, "Description" - = f.text_area :description, rows: 2, data: { provide: 'markdown' } - %span.help-block - = markdown_hint + = f.label :description + = f.text_area :description, rows: 15, data: { provide: 'markdown' } + .help-block= markdown_hint - if @cfp.cfp_type == 'events' .checkbox %label diff --git a/app/views/admin/commercials/_update_form.haml b/app/views/admin/commercials/_update_form.haml new file mode 100644 index 000000000..1da8551dc --- /dev/null +++ b/app/views/admin/commercials/_update_form.haml @@ -0,0 +1,14 @@ +.caption.panel-footer + - if can?(:update, commercial) + = form_for commercial, url: form_url do |f| + .form-group + = f.label :title + = f.text_field :title, class: 'form-control' + .form-group + = f.label :url, 'URL' + %abbr{title: 'This field is required'} * + = f.url_field :url, class: 'form-control', id: "commercial_url_#{commercial.id}", required: 'required' + .help-block Just paste the url of your video/photo provider + = f.submit 'Update', class: 'btn btn-success' + = link_to 'Delete', admin_conference_commercial_path(conference.short_title, commercial.id), + method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' diff --git a/app/views/admin/commercials/index.html.haml b/app/views/admin/commercials/index.html.haml index 0da9ffef4..3f260b8e0 100644 --- a/app/views/admin/commercials/index.html.haml +++ b/app/views/admin/commercials/index.html.haml @@ -1,11 +1,11 @@ .row .col-md-12 .page-header - %h1 Commercials + %h1 #{@conference.title} Materials %p.text-muted - Conference commercials will be displayed on the events in the - = link_to 'schedule,', conference_schedule_path(@conference.short_title) - if the event speaker didn't add an event commercial. + Conference materials will be displayed on the events in the + = link_to 'schedule,', vertical_schedule_conference_schedule_path(@conference.short_title) + if the event speaker didn't add any event materials. - if can? :create, @conference.commercials.new .row .col-md-6 @@ -15,11 +15,14 @@ .col-md-6 = form_for(@commercial, url: admin_conference_commercials_path(conference_id: @conference.short_title)) do |f| .form-group - = f.label :url + = f.label :title + = f.text_field :title, class: 'form-control' + .form-group + = f.label :url, 'URL' = f.url_field :url, required: 'required', autofocus: true, class: 'form-control' %span.help-block Just paste the url of your video/photo provider. YouTube, Vimeo, SpeakerDeck, SlideShare, Instagram, Flickr. - = f.submit nil, { class: 'btn btn-primary pull-right', id: 'commercial_submit_action', disabled: true } + = f.submit 'Save Materials', { class: 'btn btn-primary pull-right', id: 'commercial_submit_action', disabled: true } %hr - @commercials.each_slice(3) do |slice| @@ -27,18 +30,22 @@ - slice.each do |commercial| - if commercial.persisted? .col-md-4 - .thumbnail - .flexvideo{ id: "resource-content-#{commercial.id}" } - = render partial: 'shared/media_item', locals: { commercial: commercial } - .caption + .panel.panel-default + %div{ id: "resource-content-#{commercial.id}" } + = render 'shared/media_item', commercial: commercial + .caption.panel-footer - if can? :update, commercial = form_for commercial, url: admin_conference_commercial_path(conference_id: @conference.short_title, id: commercial) do |f| .form-group - = f.label :url - = f.url_field :url, id: "commercial_url_#{commercial.id}", required: 'required' + = f.label :title + = f.text_field :title, class: 'form-control' + .form-group + = f.label :url, 'URL' + %abbr{title: 'This field is required'} * + = f.url_field :url, class: 'form-control', id: "commercial_url_#{commercial.id}", required: 'required' %span.help-block Just paste the url of your video/photo provider - = f.submit nil, { class: 'btn btn-success' } + = f.submit 'Update', class: 'btn btn-success' - if can? :destroy, commercial = link_to 'Delete', admin_conference_commercial_path(@conference.short_title, commercial.id), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' diff --git a/app/views/admin/conferences/_form_fields.html.haml b/app/views/admin/conferences/_form_fields.html.haml index ceb72acc8..f5705f455 100644 --- a/app/views/admin/conferences/_form_fields.html.haml +++ b/app/views/admin/conferences/_form_fields.html.haml @@ -1,18 +1,17 @@ -%h4 - Basic Information +%h4 Basic Information %hr - if f.object.new_record? .form-group = f.label :organization, "Organization" = f.select :organization_id, Organization.accessible_by(current_ability, :update).pluck(:name, :id) .form-group - = f.label :title, "Title" + = f.label :title %abbr{title: 'This field is required'} * = f.text_field :title, required: true, class: 'form-control', placeholder: 'Title' %span.help-block The name of your conference as it shall appear throughout the site. Example: 'openSUSE Conference 2013' .form-group - = f.label :title, "Short Title" + = f.label :short_title %abbr{title: 'This field is required'} * = f.text_field :short_title, required: true, pattern: '[a-zA-Z0-9_-]+', title: 'Only letters, numbers, underscores, and dashes.', prepend: conferences_url + '/', class: 'form-control', placeholder: 'Short Title' %span.help-block @@ -21,9 +20,9 @@ froscon2011 - unless f.object.new_record? # We are showing more fields on the edit form .form-group + = f.label :description = f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control' - %span.help-block - = markdown_hint('A description of the conference.') + .help-block= markdown_hint('Splash page content') .form-group = f.color_field :color, size: 6, class: 'form-control' %span.help-block @@ -34,18 +33,27 @@ = image_tag f.object.picture.thumb.url = f.file_field :picture %span.help-block - This will be displayed on the front page. - = f.hidden_field :picture_cache - = f.select :ticket_layout, Conference.ticket_layouts.keys, {}, class: 'form-control' - %span.help-block - Layout type for tickets of the conference. + This will be shown in the navigation bar and emails. + .form-group + = f.label :custom_css, "Custom CSS" + = f.text_area :custom_css, rows: 10, class: 'form-control', html: { style: 'font-family: monospace' } + %span.help-block + Add custon CSS to all pages within the conference. The class + %code .conference-#{@conference.short_title} + is included on the body element. + .form-group + = f.label :ticket_layout + = f.select :ticket_layout, Conference.ticket_layouts.keys, {}, class: 'form-control' + %span.help-block + Layout type for tickets of the conference. -%h4 - Scheduling +%h4 Scheduling %hr -= f.time_zone_select :timezone, nil, { default: 'UTC' }, { class: 'form-control' } -%span.help-block - Please select in what time zone your conference will take place. +.form-group + = f.label :timezone + = f.time_zone_select :timezone, nil, { default: 'UTC' }, { class: 'form-control' } + %span.help-block + Please select in what time zone your conference will take place. .form-group = f.label :start_date, "Start Date" %abbr{title: 'This field is required'} * @@ -53,43 +61,39 @@ .form-group = f.label :end_date, "End Date" %abbr{title: 'This field is required'} * - = f.text_field :end_date, id: 'conference-end-datepicker', required: true, class: 'form-control' -- unless f.object.new_record? # We are showing more fields on the edit form + = f.text_field :end_date, id: 'conference-end-datepicker', required: true, class: 'form-control' +- unless f.object.new_record? .form-group + = f.label :start_hour = f.number_field :start_hour, size: 2, min: 0, max: 23, class: 'form-control' %span.help-block = rescheduling_hint(@affected_event_count) .form-group + = f.label :end_hour = f.number_field :end_hour, size: 2, min: 1, max: 24, class: 'form-control' %span.help-block = rescheduling_hint(@affected_event_count) - %h4 - Registrations + %h4 Registrations %hr .form-group + = f.label :registration_limit = f.number_field :registration_limit, in: 0..9999, class: 'form-control' %span.help-block Limit the number of registrations to the conference (0 no limit). Please note that the registration limit does not apply to speakers of confirmed events, they will still be able to register even if the limit has been reached. - You currently have - = pluralize(@conference.registrations.count, 'registration') + You currently have #{pluralize(@conference.registrations.count, 'registration')}. %h4 Booths %hr .form-group + = f.label :booth_limit = f.number_field :booth_limit, in: 0..9999, class: 'form-control' %span.help-block - = (t'booth').capitalize - limit is the maximum number of - = (t'booth').pluralize - that you can accept for this conference. By setting this number (0 no limit) you can be sure that you are not - going to accept more - = (t'booth').pluralize - than the conference can accommodate. You currently have - = pluralize(@conference.booths.accepted.count, "accepted #{t'booth'}") + '.' + #{(t'booth').capitalize} limit is the maximum number of #{(t'booth').pluralize} + that you can accept for this conference. By setting this number (0 no limit) you can be sure that you are not going to accept more #{(t'booth').pluralize} + than the conference can accommodate. You currently have #{pluralize(@conference.booths.accepted.count, "accepted #{t'booth'}")}. %p.text-right - if f.object.new_record? = f.submit nil, { class: 'btn btn-success' } - else = f.submit nil, { class: 'btn btn-success', data: { confirm: 'Are you sure you want to proceed?' } } - diff --git a/app/views/admin/conferences/_todo_list.html.haml b/app/views/admin/conferences/_todo_list.html.haml index 88bb7c367..74ddc7961 100644 --- a/app/views/admin/conferences/_todo_list.html.haml +++ b/app/views/admin/conferences/_todo_list.html.haml @@ -1,13 +1,14 @@ -.list-group - %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)}" } +%ul.list-group + .list-group-item %h4 Conference progress .progress - .progress-bar{ 'role' => 'progressbar', 'aria-valuenow' => "#{conference_progress['process']}", 'aria-valuemin' => '0', - 'aria-valuemax' => '100', 'style' => "width: #{conference_progress['process']}%;" } + .progress-bar{ role: 'progressbar', 'aria-valuenow': conference_progress['process'], + 'aria-valuemin': 0, 'aria-valuemax': 100, + style: "width: #{conference_progress['process']}%;" } = conference_progress['process'] + '%' - %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['registration'])}" } - %span{ 'class' => icon_for_todo(conference_progress['registration']) } + %li.list-group-item{ class: class_for_todo(conference_progress['registration']) } + %span{ class: icon_for_todo(conference_progress['registration']) } - if can? :update, @conference - if conference.registration_period = link_to 'Set up registration period', edit_admin_conference_registration_period_path(conference_progress['short_title']) @@ -15,14 +16,14 @@ = link_to 'Set up registration period', new_admin_conference_registration_period_path(conference_progress['short_title']) - else Set up registration period - %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['cfp'])}" } - %span{ 'class' => icon_for_todo(conference_progress['cfp']) } + %li.list-group-item{ class: class_for_todo(conference_progress['cfp']) } + %span{ class: icon_for_todo(conference_progress['cfp']) } - if can? :update, Cfp.new(program_id: @program.id) = link_to 'Set up call for papers', admin_conference_program_cfps_path(conference_progress['short_title']) - else Set up call for papers - %li{'class'=>"list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['venue'])}"} - %span{'class'=>icon_for_todo(conference_progress['venue'])} + %li.list-group-item{ class: class_for_todo(conference_progress['venue']) } + %span{ class: icon_for_todo(conference_progress['venue'])} - if can? :update, Venue.new(conference: @conference) - @conference.reload - if conference.venue @@ -32,32 +33,32 @@ - else - @conference.reload Add venue - %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['rooms'])}" } - %span{ 'class' => icon_for_todo(conference_progress['rooms']) } + %li.list-group-item{ class: class_for_todo(conference_progress['rooms']) } + %span{ class: icon_for_todo(conference_progress['rooms']) } - if @conference.venue && (can? :update, @conference.venue.rooms.build) = link_to 'Add rooms', admin_conference_venue_rooms_path(conference_progress['short_title']) - else Add rooms - %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['tracks'])}" } - %span{ 'class' => icon_for_todo(conference_progress['tracks']) } + %li.list-group-item{ class: class_for_todo(conference_progress['tracks']) } + %span{ class: icon_for_todo(conference_progress['tracks']) } - if can? :update, @conference.program.tracks.build = link_to 'Add tracks', admin_conference_program_tracks_path(conference_progress['short_title']) - else Add tracks - %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['event_types'])}" } - %span{ 'class' => icon_for_todo(conference_progress['event_types']) } + %li{ class: "list-group-item #{class_for_todo(conference_progress['event_types'])}" } + %span{ class: icon_for_todo(conference_progress['event_types']) } - if can? :update, @conference.program.event_types.build = link_to 'Add event types', admin_conference_program_event_types_path(conference_progress['short_title']) - else Add event types - %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['difficulty_levels'])}" } - %span{ 'class' => icon_for_todo(conference_progress['difficulty_levels']) } + %li{ class: "list-group-item #{class_for_todo(conference_progress['difficulty_levels'])}" } + %span{ class: icon_for_todo(conference_progress['difficulty_levels']) } - if can? :update, @conference.program.difficulty_levels.build = link_to 'Add difficulty levels', admin_conference_program_difficulty_levels_path(conference_progress['short_title']) - else Add difficulty levels - %li{ class: "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['splashpage'])}" } - %span{ 'class' => icon_for_todo(conference_progress['splashpage']) } + %li.list-group-item{ class: "#{class_for_todo(conference_progress['splashpage'])}" } + %span{ class: icon_for_todo(conference_progress['splashpage']) } - if can? :update, @conference = link_to 'Set up a Splashpage', admin_conference_splashpage_path(conference_progress['short_title']) - else diff --git a/app/views/admin/conferences/_top_submitter.html.haml b/app/views/admin/conferences/_top_submitter.html.haml index 9f220249c..b4f714fd5 100644 --- a/app/views/admin/conferences/_top_submitter.html.haml +++ b/app/views/admin/conferences/_top_submitter.html.haml @@ -6,7 +6,7 @@ - @top_submitter.each do |key, value| .row.top-submitter .col-md-2 - = image_tag(key.gravatar_url(size: '25'), title: "Yo #{key.name}!", alt: '', 'class' => 'img-circle img-responsive text-center') + = image_tag(key.profile_picture(size: '25'), title: "Yo #{key.name}!", alt: '', 'class' => 'img-circle img-responsive text-center') .col-md-10 %h4 = link_to key.name, admin_user_path(key) diff --git a/app/views/admin/conferences/show.html.haml b/app/views/admin/conferences/show.html.haml index 094a56e18..8d164643f 100644 --- a/app/views/admin/conferences/show.html.haml +++ b/app/views/admin/conferences/show.html.haml @@ -3,16 +3,16 @@ Dashboard for #{@conference.title} %hr .row - .col-sm-3.col-xs-3 + .col-sm-3.col-xs-6 = render "big_statistic", icon: "user", subtitle: "Registration", value: @total_reg, delta: @new_reg - .col-sm-3.col-xs-3 + .col-sm-3.col-xs-6 = render "big_statistic", icon: "square-check", subtitle: "Submission", value: @total_submissions, delta: @new_submissions - .col-sm-3.col-xs-3 + .col-sm-3.col-xs-6 = render "big_statistic", icon: "file-lines", subtitle: "Hour", value: @program_length, delta: @new_program_length - .col-sm-3.col-xs-3 + .col-sm-3.col-xs-6 = render "big_statistic", icon: "box-archive", subtitle: "Withdrawn", value: @total_withdrawn, delta: @new_withdrawn @@ -29,8 +29,20 @@ = render 'line_chart', title: 'Submissions per week', data: @submissions .col-md-4 - = render 'todo_list', - conference_progress: @conference_progress, conference: @conference + - unless @conference.ended? + = render 'todo_list', + conference_progress: @conference_todo_list, conference: @conference + - else + .list-group + .list-group-item + %h4 + Conference Progress + .progress + .progress-bar{ role: 'progressbar', 'aria-valuenow': 100, + 'aria-valuemin': 0, 'aria-valuemax': 100, + style: 'width: 100%;' } + Conference Complete! + .row#tickets .col-md-8 = render 'line_chart', @@ -43,14 +55,15 @@ %a{ href: '#distribution_all', 'data-toggle' => 'tab' } %span.fa-solid.fa-star All - %li - %a{ href: '#distribution_confirmed', 'data-toggle' => 'tab' } - %span.fa-solid.fa-comment - Confirmed - %li - %a{ href: '#distribution_withdrawn', 'data-toggle' => 'tab' } - %span.fa-solid.fa-box-archive - Withdrawn + -# TODO-SNAPCON: Disabled for Snap!Con + %li + %a{ href: '#distribution_confirmed', 'data-toggle' => 'tab' } + %span.fa-solid.fa-comment + Confirmed + %li + %a{ href: '#distribution_withdrawn', 'data-toggle' => 'tab' } + %span.fa-solid.fa-box-archive + Withdrawn .tab-content .tab-pane.active#distribution_all .row @@ -63,28 +76,28 @@ .col-md-4 = render 'donut_chart', title: 'Tracks', combined_data: @tracks_distribution - .tab-pane#distribution_confirmed + -# .tab-pane#distribution_withdrawn .row .col-md-4 = render 'donut_chart', title: 'Event types', - combined_data: @event_type_distribution_confirmed + combined_data: @event_type_distribution_withdrawn .col-md-4 = render 'donut_chart', title: 'Difficulty levels', - combined_data: @difficulty_levels_distribution_confirmed + combined_data: @difficulty_levels_distribution .col-md-4 = render 'donut_chart', title: 'Tracks', - combined_data: @tracks_distribution_confirmed - .tab-pane#distribution_withdrawn + combined_data: @tracks_distribution + -# .tab-pane#distribution_confirmed .row .col-md-4 = render 'donut_chart', title: 'Event types', - combined_data: @event_type_distribution_withdrawn + combined_data: @event_type_distribution_confirmed .col-md-4 = render 'donut_chart', title: 'Difficulty levels', - combined_data: @difficulty_levels_distribution_withdrawn + combined_data: @difficulty_levels_distribution_confirmed .col-md-4 = render 'donut_chart', title: 'Tracks', - combined_data: @tracks_distribution_withdrawn + combined_data: @tracks_distribution_confirmed .row .col-md-8 @@ -99,11 +112,11 @@ Recent Submissions .tab-content .tab-pane.active#recent_reg - = render partial: 'recent_registrations', locals: {recent_registrations: @recent_registrations} + = render 'recent_registrations', recent_registrations: @recent_registrations .tab-pane#recent_submissions - = render partial: 'recent_submissions', locals: {recent_events: @recent_events} + = render 'recent_submissions', recent_events: @recent_events .col-md-4 - = render partial: 'top_submitter', locals: {top_submitter: @top_submitter} + = render 'top_submitter', top_submitter: @top_submitter :javascript $('#recentTable a').click(function (e) { diff --git a/app/views/admin/currency_conversions/_form.html.haml b/app/views/admin/currency_conversions/_form.html.haml new file mode 100644 index 000000000..6f052dcd2 --- /dev/null +++ b/app/views/admin/currency_conversions/_form.html.haml @@ -0,0 +1,12 @@ += form_for(@currency_conversion, url: (@currency_conversion.new_record? ? admin_conference_currency_conversions_path : admin_conference_currency_conversion_path(@conference.short_title, @currency_conversion))) do |f| + .form-group + = f.label :from_currency + = f.text_field :from_currency, autofocus: true , required: true, class: 'form-control' + .form-group + = f.label :to_currency + = f.text_field :to_currency, autofocus: true , required: true, class: 'form-control' + .form-group + = f.label :rate + = f.text_field :rate, autofocus: true, required: true, class: 'form-control', type: 'number', step: '0.01' + %p.text-right + = f.submit nil, class: 'btn btn-primary' diff --git a/app/views/admin/currency_conversions/edit.html.haml b/app/views/admin/currency_conversions/edit.html.haml new file mode 100644 index 000000000..69b77e91b --- /dev/null +++ b/app/views/admin/currency_conversions/edit.html.haml @@ -0,0 +1,8 @@ +.row + .col-md-12 + .page-header + %h1 + Edit Currency Conversion +.row + .col-md-8 + = render partial: 'form' diff --git a/app/views/admin/currency_conversions/index.html.haml b/app/views/admin/currency_conversions/index.html.haml new file mode 100644 index 000000000..1d5ef815b --- /dev/null +++ b/app/views/admin/currency_conversions/index.html.haml @@ -0,0 +1,38 @@ +.row + .col-md-12 + .page-header + %h1 Currency Conversions + %p.text-muted + Enter the currency conversions for this conference + %p.alert.alert-warning + %strong Warning: + Currency conversion feature has not been implemented yet. Do not add any currency conversions. +.row + .col-md-12 + %table.table.table-hover#currency-conversions + %thead + %tr + %th From Curr + %th To Curr + %th Rate + %th Actions + %tbody + - @conference.currency_conversions.each do |currency_conversion| + %tr + %td + = currency_conversion.from_currency + %td + = currency_conversion.to_currency + %td + = currency_conversion.rate + %td + .btn-group{ role: 'group' } + = link_to 'Edit', edit_admin_conference_currency_conversion_path(@conference.short_title, currency_conversion.id), + method: :get, class: 'btn btn-primary' + = link_to 'Delete', admin_conference_currency_conversion_path(@conference.short_title, currency_conversion.id), + method: :delete, class: 'btn btn-danger', + data: { confirm: "Do you really want to delete this currency conversion?" } + +.row + .col-md-12.text-right + = link_to 'Add Currency Conversion', new_admin_conference_currency_conversion_path(@conference.short_title), class: 'btn btn-primary' diff --git a/app/views/admin/currency_conversions/new.html.haml b/app/views/admin/currency_conversions/new.html.haml new file mode 100644 index 000000000..bcc3543f2 --- /dev/null +++ b/app/views/admin/currency_conversions/new.html.haml @@ -0,0 +1,8 @@ +.row + .col-md-12 + .page-header + %h1 + Create Currency Conversion +.row + .col-md-8 + = render partial: 'form' diff --git a/app/views/admin/emails/index.html.haml b/app/views/admin/emails/index.html.haml index e296d824f..f60c4889f 100644 --- a/app/views/admin/emails/index.html.haml +++ b/app/views/admin/emails/index.html.haml @@ -33,7 +33,7 @@ 'data-body-input-id' => 'email_settings_registration_body', 'data-body-text' => "Dear {name},\n\nThank you for Registering for the conference {conference}.\nPlease complete your registration by filling out your travel information.\n\nIf you are unable to attend please unregister online:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\nWe look forward to see you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'registration_help' } Show Help - = render partial: 'help', locals: { id: 'registration_help', show_event_variables: false } + = render partial: 'shared/help', locals: { id: 'registration_help', show_event_variables: false, show_ticket_variables: false, show_ticket_variables: false } #proposal.tab-pane{ role: 'tabpanel' } .checkbox %label @@ -47,7 +47,7 @@ = f.text_area :submitted_proposal_body, input_html: { rows: 10, cols: 20 }, class: 'form-control' %a.btn.btn-link.control_label.load_template{ 'data-subject-input-id' => 'email_settings_submitted_proposal_subject', 'data-subject-text' => 'Your proposal has been submitted successfully', 'data-body-input-id' => 'email_settings_submitted_proposal_body', 'data-body-text' => "Dear {name}\n\nThank you for submitting your proposal {eventtitle}.\n\nOur team will review it and get back to you as soon as possible.\n\nFeel free to contact us with any questions or concerns.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'submitted_proposal_help' } Show Help - = render partial: 'help', locals: { id: 'submitted_proposal_help', show_event_variables: true } + = render partial: 'shared/help', locals: { id: 'submitted_proposal_help', show_event_variables: true, show_ticket_variables: false } .checkbox %label = f.check_box :send_on_accepted, data: { name: 'email_settings_accepted_subject' }, class: 'send_on_radio' @@ -63,7 +63,7 @@ 'data-body-input-id' => 'email_settings_accepted_body', 'data-body-text' => "Dear {name}\n\nWe are very pleased to inform you that your submission {eventtitle} has been accepted for the conference {conference}.\n\nThe public page of your submission can be found at:\n{proposalslink}\nIf you haven´t already registered for {conference}, please do as soon as possible:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help - = render partial: 'help', locals: { id: 'accepted_help', show_event_variables: true } + = render partial: 'shared/help', locals: { id: 'accepted_help', show_event_variables: true, show_ticket_variables: false } .checkbox %label = f.check_box :send_on_rejected, data: { name: 'email_settings_rejected_subject'}, class: 'send_on_radio' @@ -79,7 +79,7 @@ 'data-body-input-id' => 'email_settings_rejected_body', 'data-body-text' => "Dear {name},\n\nThank you for your submission {eventtitle} for the conference {conference}.\nAfter careful consideration we are sorry to inform you that your submission has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'rejected_help' } Show Help - = render partial: 'help', locals: {id: 'rejected_help', show_event_variables: true} + = render partial: 'shared/help', locals: {id: 'rejected_help', show_event_variables: true, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_confirmed_without_registration, data: {name: 'email_settings_confirmed_without_registration_subject'}, class: 'send_on_radio' @@ -95,7 +95,7 @@ 'data-body-input-id' => 'email_settings_confirmed_without_registration_body', 'data-body-text' => "Dear {name},\n\nThank you for the confirmation of {eventtitle}. Unfortunately you are not registered for the conference {conference}. Please register as soon as possible:\n{registrationlink}\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'confirmed_help' } Show Help - = render partial: 'help', locals: {id: 'confirmed_help', show_event_variables: true} + = render partial: 'shared/help', locals: {id: 'confirmed_help', show_event_variables: true, show_ticket_variables: false} #notifications.tab-pane{ role: 'tabpanel' } .checkbox %label @@ -112,7 +112,7 @@ 'data-body-input-id' => 'email_settings_conference_dates_updated_body', 'data-body-text' => "Dear {name},\n\nThe date of {conference} has changed.\n New Dates : {conference_start_date} - {conference_end_date}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_dates_help' } Show Help - = render partial: 'help', locals: {id: 'updated_dates_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_dates_help', show_event_variables: false, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_conference_registration_dates_updated, data: { name: 'email_settings_conference_registration_dates_updated_subject' }, class: 'send_on_radio' @@ -128,7 +128,7 @@ 'data-body-input-id' => 'email_settings_conference_registration_dates_updated_body', 'data-body-text' => "Dear {name},\n\nThe registration date of {conference} has changed.\n New Dates : {registration_start_date} - {registration_end_date}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_registrations_dates_help' } Show Help - = render partial: 'help', locals: {id: 'updated_registrations_dates_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_registrations_dates_help', show_event_variables: false, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_venue_updated, data: { name: 'email_settings_venue_updated_subject'}, class: 'send_on_radio' @@ -144,7 +144,7 @@ 'data-body-input-id' => 'email_settings_venue_updated_body', 'data-body-text' => "Dear {name},\n\nThe Conference venue of {conference} has changed. New location is: {venue}.\n Address: {venue_address}.\n\nFeel free to contact us with any questions or concerns.\n\nWe look forward to seeing you there.\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_venue_help' } Show Help - = render partial: 'help', locals: {id: 'updated_venue_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_venue_help', show_event_variables: false, show_ticket_variables: false} #cfp.tab-pane{ role: 'tabpanel' } .checkbox %label @@ -161,7 +161,7 @@ 'data-body-input-id' => 'email_settings_program_schedule_public_body', 'data-body-text' => "Dear {name},\n\nThe schedule for {conference} has been announced.\nLink to Schedule {schedule_link}\n\nBest wishes\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help - = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_cfp_help', show_event_variables: false, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_cfp_dates_updated @@ -177,7 +177,7 @@ 'data-body-input-id' => 'email_settings_cfp_dates_updated_body', 'data-body-text' => "Dear {name},\n\nThe Conference Call for Papers Details of {conference} has changed.\nNew Dates : {cfp_start_date} - {cfp_end_date}.\n Link to Schedule {schedule_link} \n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'updated_cfp_help' } Show Help - = render partial: 'help', locals: {id: 'updated_cfp_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'updated_cfp_help', show_event_variables: false, show_ticket_variables: false} #booth.tab-pane{ role: 'tabpanel' } .checkbox %label @@ -194,7 +194,7 @@ 'data-body-input-id' => 'email_settings_booths_acceptance_body', 'data-body-text' => "Dear {name},\n\nWe are pleased to inform you that your #{t'booth'} request {booth_title} has been accepted for the conference {conference}.\nPlease click the confirm button to let us know you can make it as soon as possible!\n\nFeel free to contact us with any questions or concerns.\n\nWe are looking forward to seeing you there.\n\nBest wishes\n\n{conference} Team"} Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_acceptance_help' } Show help - = render partial: 'help', locals: {id: 'booth_acceptance_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'booth_acceptance_help', show_event_variables: false, show_ticket_variables: false} .checkbox %label = f.check_box :send_on_booths_rejection @@ -210,7 +210,7 @@ 'data-body-input-id' => 'email_settings_booths_rejection_body', 'data-body-text' => "Dear {name},\n\nThank you for your #{t'booth'} request {booth_title} for the conference {conference}.\n\nUnfortunately, we are sorry to inform you that your request has been rejected.\n\n\nBest wishes\n\n{conference} Team" } Load Template %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'booth_rejection_help' } Show help - = render partial: 'help', locals: {id: 'booth_rejection_help', show_event_variables: false} + = render partial: 'shared/help', locals: {id: 'booth_rejection_help', show_event_variables: false, show_ticket_variables: false, show_ticket_variables: false} .row .col-md-12 diff --git a/app/views/admin/event_types/_form.html.haml b/app/views/admin/event_types/_form.html.haml index 813474c30..d0a2739c1 100644 --- a/app/views/admin/event_types/_form.html.haml +++ b/app/views/admin/event_types/_form.html.haml @@ -13,7 +13,8 @@ = @event_type.program.schedule_interval .form-group = f.label :description - = f.text_field :description, class: 'form-control' + = f.text_area :description, class: 'form-control', rows: 5, data: { provide: 'markdown' } + .help-block= markdown_hint .form-group = f.label :minimum_abstract_length %abbr{title: 'This field is required'} * @@ -22,6 +23,10 @@ = f.label :maximum_abstract_length %abbr{title: 'This field is required'} * = f.number_field :maximum_abstract_length, size: 3, required: true, class: 'form-control' + .form-group + = f.label :submission_template + = f.text_area :submission_template, rows: 5, data: { provide: 'markdown' } + .help-block= markdown_hint .form-group = f.label :color = f.color_field :color, class: 'form-control' diff --git a/app/views/admin/event_types/index.html.haml b/app/views/admin/event_types/index.html.haml index 36c6dc78e..56411fb78 100644 --- a/app/views/admin/event_types/index.html.haml +++ b/app/views/admin/event_types/index.html.haml @@ -11,6 +11,7 @@ %tr %th Title %th Description + %th Instructions %th Length %th Abstract Length %th Color @@ -21,7 +22,9 @@ %td = event_type.title %td - = event_type.description + = markdown(event_type.description) + %td + = markdown(event_type.submission_template) %td = event_type.length Minutes diff --git a/app/views/admin/events/_all_events.xlsx.axlsx b/app/views/admin/events/_all_events.xlsx.axlsx index 686102c59..44f4deb66 100644 --- a/app/views/admin/events/_all_events.xlsx.axlsx +++ b/app/views/admin/events/_all_events.xlsx.axlsx @@ -2,7 +2,7 @@ wb.add_worksheet(name: 'all events') do |sheet| bold_style = wb.styles.add_style(b: true) - wrap_text = wb.styles.add_style alignment: {wrap_text: true} + wrap_text = wb.styles.add_style alignment: { wrap_text: true } row = ['Event ID', 'Title', 'Abstract', @@ -22,7 +22,7 @@ wb.add_worksheet(name: 'all events') do |sheet| row << event.id row << event.title row << event.abstract - row << (event.time.present? ? "#{event.time.strftime("%Y-%m-%d")} #{event.time.strftime("%I:%M%p")} " : '') + row << (event.time.present? ? "#{event.time.strftime('%Y-%m-%d')} #{event.time.strftime('%I:%M%p')} " : '') row << event.submitter.name row << event.speaker_names row << event.speaker_emails @@ -31,7 +31,7 @@ wb.add_worksheet(name: 'all events') do |sheet| row << (event.difficulty_level.present? ? event.difficulty_level.title : '') row << (event.room.present? ? event.room.name : '') row << event.state - sheet.add_row row , style: wrap_text - sheet.column_widths 10,15,35,13,18,18,28,12,15,15,15,10 + sheet.add_row row, style: wrap_text + sheet.column_widths 10, 15, 35, 13, 18, 18, 28, 12, 15, 15, 15, 10 end end diff --git a/app/views/admin/events/_all_with_comments.xlsx.axlsx b/app/views/admin/events/_all_with_comments.xlsx.axlsx index 60e038a6b..8a877c792 100644 --- a/app/views/admin/events/_all_with_comments.xlsx.axlsx +++ b/app/views/admin/events/_all_with_comments.xlsx.axlsx @@ -22,13 +22,13 @@ wb.add_worksheet(name: 'events with comments') do |sheet| @events.each do |event| all_comments = '' event.root_comments.each do |comment| - all_comments << "#{comment.created_at.strftime("%Y-%m-%d")} #{comment.created_at.strftime("%I:%M%p")} #{comment.user.name}: #{comment.body}\n" + all_comments << "#{comment.created_at.strftime('%Y-%m-%d')} #{comment.created_at.strftime('%I:%M%p')} #{comment.user.name}: #{comment.body}\n" end row = [] row << event.id row << event.title row << event.abstract - row << (event.time.present? ? "#{event.time.strftime("%Y-%m-%d")} #{event.time.strftime("%I:%M%p")} " : '') + row << (event.time.present? ? "#{event.time.strftime('%Y-%m-%d')} #{event.time.strftime('%I:%M%p')} " : '') row << event.submitter.name row << event.speaker_names row << event.speaker_emails @@ -39,6 +39,6 @@ wb.add_worksheet(name: 'events with comments') do |sheet| row << event.state row << all_comments.strip sheet.add_row row, style: cell_style - sheet.column_widths 10,15,35,13,18,18,28,12,15,15,15,10 + sheet.column_widths 10, 15, 35, 13, 18, 18, 28, 12, 15, 15, 15, 10 end end diff --git a/app/views/admin/events/_confirmed_events.csv.haml b/app/views/admin/events/_confirmed_events.csv.haml index 2a61c3526..1c0ddb937 100644 --- a/app/views/admin/events/_confirmed_events.csv.haml +++ b/app/views/admin/events/_confirmed_events.csv.haml @@ -17,7 +17,7 @@ event.title, event.abstract, (event.time.present? ? "#{event.time.strftime("%Y-%m-%d")} #{event.time.strftime("%I:%M%p")} " : ''), - event.submitter.name, + (event.submitter.present? ? event.submitter.name : ''), event.speaker_names, event.speaker_emails, event.event_type.title, diff --git a/app/views/admin/events/_confirmed_events.xlsx.axlsx b/app/views/admin/events/_confirmed_events.xlsx.axlsx index bbb7ef821..204bcb5ca 100644 --- a/app/views/admin/events/_confirmed_events.xlsx.axlsx +++ b/app/views/admin/events/_confirmed_events.xlsx.axlsx @@ -2,7 +2,7 @@ wb.add_worksheet(name: 'confirmed events') do |sheet| bold_style = wb.styles.add_style(b: true) - wrap_text = wb.styles.add_style alignment: {wrap_text: true} + wrap_text = wb.styles.add_style alignment: { wrap_text: true } row = ['Event ID', 'Title', 'Abstract', @@ -22,7 +22,7 @@ wb.add_worksheet(name: 'confirmed events') do |sheet| row << event.id row << event.title row << event.abstract - row << (event.time.present? ? "#{event.time.strftime("%Y-%m-%d")} #{event.time.strftime("%I:%M%p")} " : '') + row << (event.time.present? ? "#{event.time.strftime('%Y-%m-%d')} #{event.time.strftime('%I:%M%p')} " : '') row << event.submitter.name row << event.speaker_names row << event.speaker_emails @@ -31,7 +31,7 @@ wb.add_worksheet(name: 'confirmed events') do |sheet| row << (event.difficulty_level.present? ? event.difficulty_level.title : '') row << (event.room.present? ? event.room.name : '') row << event.state - sheet.add_row row , style: wrap_text - sheet.column_widths 10,15,35,13,18,18,28,12,15,15,15,10 + sheet.add_row row, style: wrap_text + sheet.column_widths 10, 15, 35, 13, 18, 18, 28, 12, 15, 15, 15, 10 end end diff --git a/app/views/admin/events/_datatable_row.haml b/app/views/admin/events/_datatable_row.haml index 17f7b2645..cdf3a9036 100644 --- a/app/views/admin/events/_datatable_row.haml +++ b/app/views/admin/events/_datatable_row.haml @@ -1,7 +1,6 @@ - cache ['admin', conference_id, program, event, current_user] do %tr{ id: "event-#{event.id}" } - %td - = event.id + %td= event.id %td = link_to event.title, @@ -34,6 +33,10 @@ .small No Speaker + %td + - if event.speakers.any? + = volunteer_links(event) + - if @program.languages.present? %td = event.language @@ -49,16 +52,19 @@ %td.text-center{ data: { order: event.is_highlight.to_s } } = event_switch_checkbox(event, :is_highlight, conference_id) - %td - = event_type_dropdown(event, event_types, conference_id) + - if @program.event_types.any? + %td{ data: { search: event.event_type.try(:title) || 'Type' } } + = event_type_dropdown(event, event_types, conference_id) - %td - = track_dropdown(event, tracks, conference_id) + - if @program.tracks.any? + %td{ data: { search: event.track.try(:name) || 'Track' } } + = track_dropdown(event, tracks, conference_id) - %td - = difficulty_dropdown(event, difficulty_levels, conference_id) + - if @program.difficulty_levels.any? + %td{ data: { search: event.difficulty_level.try(:title) || 'Difficulty Level' } } + = difficulty_dropdown(event, difficulty_levels, conference_id) - %td + %td{ data: { search: event.state.humanize } } = state_dropdown(event, conference_id, email_settings) %td.text-center diff --git a/app/views/admin/events/_datatable_row_rating.haml b/app/views/admin/events/_datatable_row_rating.haml index 8e3e9c29d..e2183c5e3 100644 --- a/app/views/admin/events/_datatable_row_rating.haml +++ b/app/views/admin/events/_datatable_row_rating.haml @@ -1,12 +1,11 @@ - if show_votes - %div{ data: { toggle: 'tooltip' }, title: rating_tooltip(event, max_rating) }< + %span{ data: { toggle: 'tooltip' }, title: rating_tooltip(event, max_rating) }< = rating_stars(event.average_rating, max_rating, avgrate: true) -.clearfix - - if event.voted?(current_user) - %span.label.label-success - You voted: - = rating_fraction(event.user_rating(current_user), max_rating) - - else - %span.label.label-danger - Not rated +- if event.voted?(current_user) + %span.label.label-success<> + You voted: + = rating_fraction(event.user_rating(current_user), max_rating) +- else + %span.label.label-danger<> + Not rated diff --git a/app/views/admin/events/_event_version.haml b/app/views/admin/events/_event_version.haml new file mode 100644 index 000000000..fc46f08d6 --- /dev/null +++ b/app/views/admin/events/_event_version.haml @@ -0,0 +1,32 @@ +%tr + %td= version.id + %td + = link_to_user(version.whodunnit) + + - if version.item_type == 'Event' + = event_change_description(version) + = "event #{event.title}" + + - elsif version.item_type == 'Vote' + = vote_change_description(version) + = "event #{event.title}" + + - elsif version.item_type == 'EventUser' + = general_change_description(version) + = 'event speaker or volunteer' + + - else + = general_change_description(version) + = link_to 'materials', + edit_admin_conference_program_event_path(conference_id: conference.short_title, + id: event.id, anchor: 'commercials-content') + + %small.text-muted + = distance_of_time_in_words(Time.now, version.created_at) + ' ago' + %br + = "(#{version.created_at.strftime('%B %-d, %Y %H:%M')})" + + %br + = render 'shared/object_changes', version: version + %td + = render 'shared/changelog_actions', version: version diff --git a/app/views/admin/events/_form.html.haml b/app/views/admin/events/_form.html.haml index e3e75f7f2..538d842a6 100644 --- a/app/views/admin/events/_form.html.haml +++ b/app/views/admin/events/_form.html.haml @@ -6,38 +6,37 @@ %li.active = link_to 'Proposal', '#proposal-content', 'data-toggle' => 'tab' %li - = link_to 'Commercials', '#commercials-content', 'data-toggle' => 'tab' + = link_to 'Materials', '#commercials-content', 'data-toggle' => 'tab' .tab-content .tab-pane.active#proposal-content = form_for(@event, url: @url) do |f| - = render partial: 'proposals/form', locals: { f: f } - = render partial: 'shared/user_selectize' + = render 'proposals/form', f: f + = render 'shared/user_selectize' .tab-pane#commercials-content - %p.text-muted - You can add commercials for your proposal. These commercials will be displayed on the - = link_to 'public proposal page.', conference_program_proposal_path(@conference.short_title, @event) - If you don't add a commercial, the conference commercial will be displayed! - - if can? :create, @event.commercials.new - .row - .col-md-6 - #resource-content - #resource-placeholder{ style: 'background-color:#d3d3d3; float: left; width: 400px; height: 250px; margin: 5px; border-width: 1px; border-style: solid; border-color: rgba(0,0,0,.2);' } - .row - .col-md-6 - = form_for(@event.commercials.new, url: conference_program_proposal_commercials_path(conference_id: @conference.short_title, proposal_id: @event)) do |f| - = render partial: 'proposals/commercial_form_fields', locals: { f: f, commercial: @event.commercials.build } + - if @event.persisted? + %p.text-muted + You can add materals for your proposal. These materials will be displayed on the + = link_to 'public proposal page.', conference_program_proposal_path(@conference.short_title, @event) + If you don't add any materials, the conference materials will be displayed. + - if can? :create, @event.commercials.new + .row + .col-md-6 + #resource-content + #resource-placeholder{ style: 'background-color:#d3d3d3; float: left; width: 400px; height: 250px; margin: 5px; border-width: 1px; border-style: solid; border-color: rgba(0,0,0,.2);' } + .row + .col-md-6 + = form_for(@event.commercials.new, url: conference_program_proposal_commercials_path(conference_id: @conference.short_title, proposal_id: @event)) do |f| + = render 'proposals/commercial_form_fields', f: f, commercial: @event.commercials.build %hr - @event.commercials.each_slice(3) do |slice| .row - slice.each do |commercial| - if commercial.persisted? .col-md-4 - .thumbnail - .flexvideo{ id: "resource-content-#{commercial.id}"} + .panel.panel-default + %div{ id: "resource-content-#{commercial.id}"} = render partial: 'shared/media_item', locals: { commercial: commercial } - .caption + .caption.panel-footer - if can? :update, commercial = form_for(commercial, url: conference_program_proposal_commercial_path(conference_id: @conference.short_title, proposal_id: @event, id: commercial)) do |f| - = render partial: 'proposals/commercial_form_fields', locals: { f: f, commercial: commercial } - - + = render 'proposals/commercial_form_fields', f: f, commercial: commercial diff --git a/app/views/admin/events/_proposal.html.haml b/app/views/admin/events/_proposal.html.haml index 17ed991d0..ae50c63d2 100644 --- a/app/views/admin/events/_proposal.html.haml +++ b/app/views/admin/events/_proposal.html.haml @@ -7,6 +7,8 @@ %small = @event.subtitle .btn-group.pull-right + - if @event.public + = link_to 'Preview', conference_program_proposal_path(@conference.short_title, @event.id), class: 'btn btn-mini btn-primary' = link_to 'Registrations', registrations_admin_conference_program_event_path(@conference.short_title, @event), class: 'btn btn-success' = link_to 'Edit', edit_admin_conference_program_event_path(@conference.short_title, @event), class: 'btn btn-mini btn-primary' @@ -28,6 +30,22 @@ %b State %td = state_dropdown(@event, @conference.short_title, @conference.email_settings) + %tr + %td + %b Is Parent Event? + %td + = event_switch_checkbox(@event, :superevent, @conference.short_title) + %tr + %td + %b Parent Event + %td + - if @event.parent_event.present? + = link_to(@event.parent_event.title, admin_conference_program_event_path(@conference, @event.parent_event)) + - else + = '(None)' + %tr + %th Presentation Mode + %td= @event.presentation_mode&.humanize %tr %td %b Track @@ -69,11 +87,12 @@ %td %b Submitter %td - = link_to @event.submitter.name, admin_user_path(@event.submitter) - - if @event.submitter.email_public - ( - = link_to @event.submitter.email, "mailto: #{@event.submitter.email}" - ) + - if @event.submitter + = link_to @event.submitter.name, admin_user_path(@event.submitter) + - if @event.submitter.email_public + (#{mail_to(@event.submitter.email)}) + - else + %span.text-danger Unkown Submitter %tr %td %b Speakers @@ -82,9 +101,12 @@ %div = link_to speaker.name, admin_user_path(speaker) - if speaker.email_public - ( - = link_to speaker.email, "mailto: #{speaker.email}" - ) + (#{mail_to(speaker.email)}) + %tr + %td + %b Volunteers + %td + = volunteer_links(@event) %tr %td %b Biographies @@ -106,11 +128,21 @@ %td %b Abstract %td= markdown(@event.abstract) + %tr + %td + %b Submission Description + %td= markdown(@event.submission_text) + %tr %td %b Requirements %td= simple_format(@event.description) + %tr + %td + %b Committee Feedback + %td= markdown(@event.committee_review) + - if @conference.program.rating_enabled? = render 'voting', event: @event, @@ -137,4 +169,3 @@ = f.text_area :body, class: 'form-control' .text-right = f.submit nil, class: 'btn btn-primary' - diff --git a/app/views/admin/events/_voting.html.haml b/app/views/admin/events/_voting.html.haml index e03886632..e0baa0508 100644 --- a/app/views/admin/events/_voting.html.haml +++ b/app/views/admin/events/_voting.html.haml @@ -22,20 +22,20 @@ - if voting_period - max_rating.times do |counter| - if event.user_rating(current_user) > counter - = link_to '', + = link_to('', vote_admin_conference_program_event_path(conference_id, event, rating: counter + 1), remote: true, id: "label#{counter + 1}", class: 'rating myrating bright', - voted: true + voted: true) - else - = link_to '', + = link_to('', vote_admin_conference_program_event_path(conference_id, event, rating: counter + 1), remote: true, id: "label#{counter + 1}", - class: 'rating myrating' + class: 'rating myrating') - else = rating_stars(event.user_rating(current_user), max_rating, voted: true) (Voting period is closed) diff --git a/app/views/admin/events/events.xlsx.axlsx b/app/views/admin/events/events.xlsx.axlsx index 7b8b1dc49..bc90bd790 100644 --- a/app/views/admin/events/events.xlsx.axlsx +++ b/app/views/admin/events/events.xlsx.axlsx @@ -2,9 +2,9 @@ wb = xlsx_package.workbook if @event_export_option == 'confirmed' - render partial: 'confirmed_events', locals: {:wb => wb} + render partial: 'confirmed_events', locals: { wb: wb } elsif @event_export_option == 'all' - render partial: 'all_events', locals: {:wb => wb} + render partial: 'all_events', locals: { wb: wb } elsif @event_export_option == 'all_with_comments' - render partial: 'all_with_comments', locals: {:wb => wb} + render partial: 'all_with_comments', locals: { wb: wb } end diff --git a/app/views/admin/events/index.html.haml b/app/views/admin/events/index.html.haml index bb6db7514..46429af29 100644 --- a/app/views/admin/events/index.html.haml +++ b/app/views/admin/events/index.html.haml @@ -6,9 +6,9 @@ = "(#{@events.length})" if @events.any? .btn-group.pull-right - %button.btn.btn-primary{ title: 'Mass import of commercials for events', + %button.btn.btn-primary{ title: 'Mass import of materials for events', data: { toggle: 'modal', target: '#mass-commercials-modal' } } - Add Commercials + Add Materials - if can? :create, Event = link_to 'Add Event', @@ -29,7 +29,7 @@ .modal-content .modal-header %h1 - Add commercials to events + Add materials to events .modal-body = form_for('', url: mass_upload_commercials_admin_conference_program_path(@conference.short_title), method: :post) do |f| .form-group @@ -59,32 +59,22 @@ %table.datatable %thead %tr - %th - %b ID - %th - %b Title + %th ID + %th Title - if @program.rating_enabled? - %th - %b Rating - %th - %b Submitter - %th - %b Speakers + %th Rating + %th Submitter + %th Speakers - if @program.languages.present? - %th - %b Language - %th - %b Requires Registration - %th - %b Highlight - %th - %b Type - %th - %b Track - %th - %b Difficulty - %th - %b State + %th Language + %th Requires Registration + %th Highlight + %th Type + - if @program.tracks.any? + %th Track + - if @program.difficulty_levels.any? + %th Difficulty + %th State %th .fa-solid.fa-comment %th Add Survey diff --git a/app/views/admin/events/new.html.haml b/app/views/admin/events/new.html.haml index edf9a1d0f..4dca33846 100644 --- a/app/views/admin/events/new.html.haml +++ b/app/views/admin/events/new.html.haml @@ -1,10 +1,3 @@ -.row - .col-md-12 - .page-header - %h1 - New Event -.row - .col-md-8 - = form_for(@event, url: @url) do |f| - = render partial: 'proposals/form', locals: { f: f } - = render partial: 'shared/user_selectize' += form_for(@event, url: @url) do |f| + = render 'proposals/form', f: f + = render 'shared/user_selectize' diff --git a/app/views/admin/events/show.html.haml b/app/views/admin/events/show.html.haml index d19d056a5..6cd270117 100644 --- a/app/views/admin/events/show.html.haml +++ b/app/views/admin/events/show.html.haml @@ -11,7 +11,7 @@ - progress_status = @event.progress_status = progress_status.reject{ |_key, value| value || value.nil? }.length %li - = link_to 'Commercials', '#proposal-commercials', 'data-toggle' => 'tab' + = link_to 'Materials', '#proposal-commercials', 'data-toggle' => 'tab' .tab-content #proposal-content.tab-pane.active @@ -26,42 +26,11 @@ %th Actions %tbody - @versions.each do |version| - %tr - %td - = version.id - %td - %p - = link_to_user(version.whodunnit) - - - if version.item_type == 'Event' - = event_change_description(version) - = "event #{@event.title}" - - - elsif version.item_type == 'Vote' - = vote_change_description(version) - = "event #{@event.title}" - - - else - = general_change_description(version) - = link_to 'commercial', - edit_admin_conference_program_event_path(conference_id: @conference.short_title, - id: @event.id, anchor: 'commercials-content') - - %small.text-muted - = distance_of_time_in_words(Time.now, version.created_at) + ' ago' - %br - = "(#{version.created_at.strftime('%B %-d, %Y %H:%M')})" - - %br - = render partial: 'shared/object_changes', locals: { version: version } - %td - = render partial: 'shared/changelog_actions', locals: { version: version } - + = render 'event_version', version: version, event: @event, conference: @conference #proposal-tasks.tab-pane - progress_percentage = @event.calculate_progress .col-md-12 - %h3 - = @event.title + %h3= @event.title %br %table.table.table-hover %tr @@ -83,7 +52,7 @@ %td{ 'class' => class_for_todo(progress_status['subtitle']) } %span{ 'class' => [icon_for_todo(progress_status['subtitle']), 'fa-lg'] } %tr - %td= link_to 'Add a commercial', edit_admin_conference_program_event_path(@event.program.conference.short_title, @event, anchor: 'commercials-content') + %td= link_to 'Add materials', edit_admin_conference_program_event_path(@event.program.conference.short_title, @event, anchor: 'commercials-content') %td{ 'class' => class_for_todo(progress_status['commercials']) } %span{ 'class' => [icon_for_todo(progress_status['commercials']), 'fa-lg'] } - unless progress_status['track'].nil? @@ -95,5 +64,6 @@ %td= link_to 'Add a difficulty level', edit_admin_conference_program_event_path(@event.program.conference.short_title, @event) %td{ 'class' => class_for_todo(progress_status['difficulty_level']) } %span{ 'class' => [icon_for_todo(progress_status['difficulty_level']), 'fa-lg'] } + #proposal-commercials.tab-pane = render partial: 'shared/media_items', locals: { commercials: @event.commercials } diff --git a/app/views/admin/physical_tickets/_physical_ticket.html.haml b/app/views/admin/physical_tickets/_physical_ticket.html.haml index ff4dd1850..241e88754 100644 --- a/app/views/admin/physical_tickets/_physical_ticket.html.haml +++ b/app/views/admin/physical_tickets/_physical_ticket.html.haml @@ -1,8 +1,17 @@ %tr %td= physical_ticket.id %td= physical_ticket.ticket.title - %td= physical_ticket.user.email + %td= physical_ticket.ticket.registration_ticket? ? 'Yes' : 'No' + %td= physical_ticket.user&.email %td= humanized_money_with_symbol physical_ticket.ticket_purchase.amount_paid + %td + - if physical_ticket.ticket_scannings.present? + %span Checked in: + %br + = format_all_timestamps(physical_ticket.ticket_scannings.pluck(:created_at), conference) + = form_for(physical_ticket, url: admin_ticket_scanning_path, method: :post) do |f| + = f.hidden_field 'token' + = f.submit "Mark Present", { class: 'btn btn-success' } %td .btn-group = link_to 'Show', @@ -13,4 +22,4 @@ conference_physical_ticket_path(conference.short_title, physical_ticket.token, format: :pdf), - class: 'button btn btn-default btn-info' + class: 'button btn btn-info' diff --git a/app/views/admin/physical_tickets/index.html.haml b/app/views/admin/physical_tickets/index.html.haml index 63e8d202b..80835f901 100644 --- a/app/views/admin/physical_tickets/index.html.haml +++ b/app/views/admin/physical_tickets/index.html.haml @@ -21,8 +21,10 @@ %tr %th ID %th Type + %th Registration? %th User %th Paid + %th Attedance %th Actions %tbody - @physical_tickets.each do |physical_ticket| diff --git a/app/views/admin/programs/_form.html.haml b/app/views/admin/programs/_form.html.haml index 037418bf4..c341dbbe3 100644 --- a/app/views/admin/programs/_form.html.haml +++ b/app/views/admin/programs/_form.html.haml @@ -26,6 +26,7 @@ .form-group = f.label :voting_start_date = f.text_field :voting_start_date, id: 'datetimepicker-voting_start_date', value: (f.object.voting_start_date.to_formatted_s(:db_without_seconds) unless f.object.voting_start_date.nil?), class: 'form-control' + .form-group = f.label :voting_end_date = f.text_field :voting_end_date, id: 'datetimepicker-voting_end_date', value: (f.object.voting_end_date.to_formatted_s(:db_without_seconds) unless f.object.voting_end_date.nil?), class: 'form-control' %h4 @@ -42,7 +43,9 @@ = f.select :languages, I18nData.languages.invert, { include_blank: 'Any Language', include_hidden: false }, { multiple: true, class: 'form-control' } %span.help-block The languages allowed for events. + %hr .form-group + = f.label :schedule_interval = f.number_field :schedule_interval, autofocus: true, class: 'form-control' %span.help-block It is the minimal time interval of your schedule. The value should be 5, 6, 10, 12, 15, 20, 30 or 60. diff --git a/app/views/admin/registrations/index.csv.haml b/app/views/admin/registrations/index.csv.haml index 76f012111..1929abb10 100644 --- a/app/views/admin/registrations/index.csv.haml +++ b/app/views/admin/registrations/index.csv.haml @@ -2,15 +2,11 @@ 'Name', 'Nickname', 'AffilΚation', - 'Email', - 'Arrival', - 'Departure'] + 'Email'] = CSV.generate_line headers - @registrations.each do |registration| = CSV.generate_line([registration.attended ? 'X' : '', registration.name, registration.nickname, registration.affiliation, - registration.email, - registration.arrival.to_s, - registration.departure.to_s]) + registration.email]) diff --git a/app/views/admin/registrations/index.html.haml b/app/views/admin/registrations/index.html.haml index 5dc557b0a..45f66b232 100644 --- a/app/views/admin/registrations/index.html.haml +++ b/app/views/admin/registrations/index.html.haml @@ -20,13 +20,14 @@ combined_data: @registration_distribution .row .col-md-12 - %div.margin-event-table + .margin-event-table %table.datatable#registrations{ data: { source: admin_conference_registrations_path(conference_id: @conference, format: :json) } } %thead %tr %th{ width: '0' } ID# %th{ width: '25%' } Name %th{ width: '0' } E-Mail + %th{ width: '0' } Ticket Type %th{ width: '0' } %abbr{ title: 'Code of Conduct' } CoC %th{ width: '0' } Actions @@ -34,7 +35,7 @@ :javascript $(function () { - var codeOfConductPresent = #{@code_of_conduct ? 'true' : 'false'}; + var codeOfConductPresent = #{@code_of_conduct ? true : false}; var registrationsDataTable = $('#registrations.datatable').DataTable({ "processing": true, "serverSide": true, @@ -65,10 +66,13 @@ { "data": "email" }, + { + "data": "ticket_type" + }, { "data": "accepted_code_of_conduct", "className": "code-of-conduct text-center", - "searchable": false + "searchable": false, }, { "data": "actions", @@ -84,5 +88,5 @@ ] }); - registrationsDataTable.columns(3).visible(codeOfConductPresent); + registrationsDataTable.columns(4).visible(true); }); diff --git a/app/views/admin/registrations/index.xlsx.axlsx b/app/views/admin/registrations/index.xlsx.axlsx index a95c163e5..d9cf24940 100644 --- a/app/views/admin/registrations/index.xlsx.axlsx +++ b/app/views/admin/registrations/index.xlsx.axlsx @@ -3,24 +3,24 @@ wb = xlsx_package.workbook wb.add_worksheet(name: 'registrations') do |sheet| bold_style = wb.styles.add_style(b: true) - row = ['Attended', 'Name', 'Nickname', 'AffilΚation', 'Email'] + row = %w[Attended Name Nickname AffilΚation Email] @conference.questions.each do |question| row << question.title end - sheet.add_row row, :style => bold_style + sheet.add_row row, style: bold_style @registrations.each do |registration| row = [] - row << ( registration.attended ? 'X' : '' ) + row << (registration.attended ? 'X' : '') row << registration.name row << registration.nickname row << registration.affiliation row << registration.email @conference.questions.each do |question| qa = registration.qanswers.find_by(question: question) - answer = ( qa ? qa.answer.title : '' ) + answer = (qa ? qa.answer.title : '') row << answer end diff --git a/app/views/admin/reports/_all_events.html.haml b/app/views/admin/reports/_all_events.html.haml index 7f53357be..d3e86d1ed 100644 --- a/app/views/admin/reports/_all_events.html.haml +++ b/app/views/admin/reports/_all_events.html.haml @@ -15,7 +15,7 @@ %th Title %th Speakers Registered %th Speakers Biographies - %th Commercial + %th Materials %th Subtitle %th Difficulty Level - if @program.tracks.any? diff --git a/app/views/admin/reports/_events_without_commercials.html.haml b/app/views/admin/reports/_events_without_commercials.html.haml index 36292485e..aebcd0e8e 100644 --- a/app/views/admin/reports/_events_without_commercials.html.haml +++ b/app/views/admin/reports/_events_without_commercials.html.haml @@ -2,10 +2,10 @@ .col-md-12 .page-header %h1 - Events without commercials + Events Without Materials = "(#{@events_missing_commercial.length})" %p.text-muted - All submissions that have no commercial + All submissions that have no materials .col-md-12 %table.datatable %thead diff --git a/app/views/admin/reports/_missing_speakers.html.haml b/app/views/admin/reports/_missing_speakers.html.haml index f3a482939..2c1fd239a 100644 --- a/app/views/admin/reports/_missing_speakers.html.haml +++ b/app/views/admin/reports/_missing_speakers.html.haml @@ -2,7 +2,7 @@ .col-md-12 .page-header %h1 - Missing Speakers + Speaker Registration = "(#{@missing_event_speakers.distinct(:user_id).length})" %p.text-muted All event speakers who haven't checked in @@ -11,6 +11,7 @@ %thead %tr %th Speaker Name + %th E-Mail %th Registered? %th Event %th Room @@ -22,6 +23,7 @@ - event = event_user.event %tr %td= link_to speaker.name, admin_user_path(speaker) + %td= speaker.email %td - if @conference.user_registered?(speaker) = link_to 'Yes', admin_conference_registrations_path(@conference.short_title) diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index 6bbb88861..6f800dcf9 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -4,7 +4,7 @@ = link_to 'All Events', '#all', 'data-toggle' => 'tab' %li %a{href: '#missing-commercial', 'data-toggle' => 'tab'} - Events without Commercials + Events without Materials %span.label.label-danger{style: 'border-radius: 1em;'} = @events_missing_commercial.length %li @@ -15,7 +15,7 @@ %li %a{href: '#missing-speakers', 'data-toggle' => 'tab'} - Missing Speakers + Speaker Registration %span.label.label-danger{style: 'border-radius: 1em;'} = @missing_event_speakers.distinct(:user_id).length diff --git a/app/views/admin/rooms/_form.html.haml b/app/views/admin/rooms/_form.html.haml index a531548a8..7453ce0eb 100644 --- a/app/views/admin/rooms/_form.html.haml +++ b/app/views/admin/rooms/_form.html.haml @@ -3,7 +3,21 @@ = f.label :name %abbr{title: 'This field is required'} * = f.text_field :name, autofocus: true, required: true, class: 'form-control' + .form-group = f.label :size, 'Capacity' = f.number_field :size, size: 5, class: 'form-control' + .form-group + = f.label :order + = f.number_field :order, size: 5, class: 'form-control' + .help-bloc Force the order of rooms in the schedule. + .form-group + = f.label :url, 'URL' + = f.url_field :url, class: 'form-control' + .help-block The URL is only visible to registered attendees. + .form-group + = f.label :discussion_url, 'Discussion URL' + = f.url_field :discussion_url, class: 'form-control' + .help-block A link to live chat during sessions in this room. + %p.text-right = f.submit nil, class: 'btn btn-primary' diff --git a/app/views/admin/rooms/index.html.haml b/app/views/admin/rooms/index.html.haml index c2040d5ac..95a28fa37 100644 --- a/app/views/admin/rooms/index.html.haml +++ b/app/views/admin/rooms/index.html.haml @@ -13,20 +13,32 @@ %tr %th Name %th Capacity + %th Order + %th URL %th Actions %tbody - @rooms.each_with_index do |room, index| %tr - %td + %th = room.name %td = room.size %td - = link_to 'Edit', edit_admin_conference_venue_room_path(@conference.short_title, room.id), - method: :get, class: 'btn btn-primary' - = link_to 'Delete', admin_conference_venue_room_path(@conference.short_title, room.id), - method: :delete, class: 'btn btn-danger', - data: { confirm: "Do you really want to delete #{room.name}? Attention: This room will be removed from all Events that have it set"} + = room.order + %td + Meeting: + = link_to(room.url, room.url, target: '_blank') + - if room.discussion_url.present? + %br + Discussion: + = link_to(room.discussion_url, room.discussion_url, target: '_blank') + %td + .btn-group + = link_to 'Edit', edit_admin_conference_venue_room_path(@conference.short_title, room.id), class: 'btn btn-primary' + = link_to('Delete', + admin_conference_venue_room_path(@conference.short_title, room.id), + method: :delete, class: 'btn btn-danger', + data: { confirm: "Do you really want to delete #{room.name}? Attention: This room will be removed from all Events that have it set"}) .row .col-md-12.text-right = link_to 'Add Room', new_admin_conference_venue_room_path(@conference.short_title), class: 'btn btn-primary' diff --git a/app/views/admin/schedules/_day_tab.html.haml b/app/views/admin/schedules/_day_tab.html.haml index 8a1dff600..844dc6ae2 100644 --- a/app/views/admin/schedules/_day_tab.html.haml +++ b/app/views/admin/schedules/_day_tab.html.haml @@ -1,13 +1,15 @@ - compact_grid = @program.schedule_interval < 15 - cells_per_hour = 60 / @program.schedule_interval / use smaller cell heights for more compact grids -- cell_height = compact_grid ? 32 : 58 +- cell_height = compact_grid ? (@program.schedule_interval < 8 ? 16 : 32) : 58 - date_event_schedules = @event_schedules.select{ |e| e.start_time.to_date.eql? date } +/ Dynamically set the column width, but no narrower than 2 (on a BS grid of 12) +- col_size = [2, (12 / @rooms.count).to_i ].max .row - @rooms.each do |room| - non_schedulable = room.tracks.self_organized.confirmed.any? do |track| - !track.schedules.include?(@schedule) && (track.start_date..track.end_date).include?(date) - .col-md-2.col-xs-6{ class: ('non_schedulable' if non_schedulable) } + .col-xs-6{ class: "#{('non_schedulable' if non_schedulable)} col-md-#{col_size}" } .room-name - room_date_event_schedules = date_event_schedules.select{ |e| e.room == room } = room.name @@ -19,10 +21,11 @@ room_id: room.id, | hour: time, | date: date, | + class: "#{'compact' if compact_grid}", | style: "height: #{cell_height}px"} .div = time - event_schedules = room_date_event_schedules.select{ |e| (e.start_time.hour.to_s + e.start_time.strftime(':%M')).eql? time } - if event_schedules.any? - event_schedule = event_schedules.first - = render partial: 'event', locals: { event: event_schedule.event, event_schedule_id: event_schedule.id} + = render 'event', event: event_schedule.event, event_schedule_id: event_schedule.id diff --git a/app/views/admin/schedules/_event.html.haml b/app/views/admin/schedules/_event.html.haml index 9df56a39a..1d6c46a96 100644 --- a/app/views/admin/schedules/_event.html.haml +++ b/app/views/admin/schedules/_event.html.haml @@ -1,29 +1,28 @@ - cells_length = event.event_type.length / @program.schedule_interval / this height fits the room cells - compact_grid = @program.schedule_interval < 15 -- single_cell_height = compact_grid ? 32 : 58 +- single_cell_height = compact_grid ? (@program.schedule_interval < 8 ? 16 : 32) : 58 - height = (cells_length * single_cell_height) -- height -= 23 unless compact_grid / subtracting the padding before calculate the number of lines -- lines = (height - 7) / 23 -- color = event.track.try(:color).present? ? event.track.try(:color) : 'FFFFFF' +- lines = [(height - 7) / 20, 1].max +- color = event.event_type.try(:color).present? ? event.event_type.try(:color) : 'FFFFFF' - non_schedulable = event_schedule_id && (EventSchedule.find(event_schedule_id).schedule != @schedule) -.schedule-event{ style: "height: #{height}px; background-color: #{color}; color: #{contrast_color(color)}", | +.schedule-event{ style: "height: #{height}px; background-color: #{color}; color: #{contrast_color(color)}", | id: "event-#{event.id}", | event_id: event.id, | length: cells_length, | event_schedule_id: event_schedule_id, | class: "#{'compact' if compact_grid} #{'non_schedulable' if non_schedulable}" } - .schedule-event-text{ style: "-webkit-line-clamp: #{lines}; height: #{lines * 23}px;"} - %span.schedule-event-delete-button{ onclick: "Schedule.remove(\'event-#{event.id}\');" } X - %b - = link_to(event.id, admin_conference_program_event_path(@conference, event)) + .schedule-event-text{ style: "-webkit-line-clamp: #{lines}; height: #{height - 2}px;"} + %span.fa-solid.fa-circle-xmark.schedule-event-delete-button{ onclick: "Schedule.remove(\'event-#{event.id}\');", class: "#{'compact' if compact_grid}" } + %b= link_to(event.id, admin_conference_program_event_path(@conference, event)) = event.speakers.collect(&:name).to_sentence = ':' = event.title - - if event.difficulty_level - %span.label.label-default= event.difficulty_level.title - - if event.event_type - %span.label.label-default= event.event_type.title - - if event.track - %span.label.label-default= event.track.short_name + - unless compact_grid + - if event.difficulty_level + %span.label.label-default= event.difficulty_level.title + - if event.event_type + %span.label.label-default= event.event_type.title + - if event.track + %span.label.label-default= event.track.short_name diff --git a/app/views/admin/schedules/_form.html.haml b/app/views/admin/schedules/_form.html.haml index 9304b8e84..a0d585be5 100644 --- a/app/views/admin/schedules/_form.html.haml +++ b/app/views/admin/schedules/_form.html.haml @@ -9,4 +9,3 @@ .form-group = f.select :track, Track.accessible_by(current_ability).where(program: @program).confirmed.pluck(:name, :id), { include_blank: false }, { class: 'form-control' } = f.submit nil, class: 'btn btn-primary' - diff --git a/app/views/admin/schedules/show.html.haml b/app/views/admin/schedules/show.html.haml index df864b88b..5777f4836 100644 --- a/app/views/admin/schedules/show.html.haml +++ b/app/views/admin/schedules/show.html.haml @@ -3,9 +3,10 @@ .row .col-md-12 .page-header - %h1 Schedule + %h1= "#{@conference.title} Schedule" %p.text-muted Create the schedules for the conference + = render 'schedules/event_types_key', event_types: @event_types, favourites: false - if @rooms.present? .row @@ -25,17 +26,16 @@ Unscheduled events .unscheduled-events - @unscheduled_events.each do |e| - = render partial: 'event', locals: { event: e, event_schedule_id: nil } + = render 'event', event: e, event_schedule_id: nil .col-md-10 %ul.nav.nav-tabs - @dates.each do |date| - %li{ class: "#{ (@dates.first == date) ? 'active' : '' }"} - %a{ href: "##{date}" } - = date + %li{ class: ('active' if @dates.first == date) } + %a{ href: "##{date}" }= "#{date.strftime('%a')} #{date}" .tab-content - @dates.each do |date| - .tab-pane{ class: "#{ (@dates.first == date) ? 'active' : '' }", id: "#{date}" } - = render partial: 'day_tab', locals: { date: date } + .tab-pane{ class: ('active' if @dates.first == date), id: date } + = render 'day_tab', date: date - else - if @venue.try(:rooms).present? .text-right diff --git a/app/views/admin/splashpages/_form.html.haml b/app/views/admin/splashpages/_form.html.haml index 644561586..338ad4a2e 100644 --- a/app/views/admin/splashpages/_form.html.haml +++ b/app/views/admin/splashpages/_form.html.haml @@ -1,4 +1,13 @@ = form_for(@splashpage, url: admin_conference_splashpage_path) do |f| + .row + .col-md-12 + %label{for: "banner_photo"} Banner Photo + %br + - if @splashpage.banner_photo.present? + = image_tag @splashpage.banner_photo.thumb.url + = f.file_field :banner_photo + = f.hidden_field :banner_photo_cache + .checkbox %label = f.check_box :include_cfp @@ -11,6 +20,11 @@ %label = f.check_box :include_tracks Include confirmed tracks in the program? + .checkbox + %label + = f.check_box :include_happening_now + Include events happening now? + .checkbox %label = f.check_box :include_booths diff --git a/app/views/admin/splashpages/show.html.haml b/app/views/admin/splashpages/show.html.haml index 0bf3267c1..c6fa8a70d 100644 --- a/app/views/admin/splashpages/show.html.haml +++ b/app/views/admin/splashpages/show.html.haml @@ -11,6 +11,14 @@ Build a splashpage with all the information for your conference - if @splashpage + .row + .col-md-12 + %strong Banner Image + - if @splashpage.banner_photo.present? + = image_tag @splashpage.banner_photo.thumb.url + - else + %p None Set + %hr .row .col-md-12 %ul.fa-ul @@ -27,6 +35,9 @@ %li %i{ class: "fa-li #{icon_for_todo @splashpage.include_booths?}" } Include confirmed #{(t'booth').pluralize} + %li + %i{ class: "fa-li #{icon_for_todo @splashpage.include_happening_now}" } + Include events happening now %li %i{ class: "fa-li #{icon_for_todo @splashpage.include_registrations?}" } Display the registration period @@ -46,7 +57,7 @@ %i{ class: "fa-li #{icon_for_todo @splashpage.include_social_media?}" } Display social media links %li - - if @conference.splashpage && @conference.splashpage.public + - if @conference.splashpage && @conference.splashpage.public? %i{ class: "fa-li #{icon_for_todo @splashpage.public?}" } %text-muted.publicorprivate Public - else diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml index 54f82c50f..5ad23df1f 100644 --- a/app/views/admin/tickets/_form.html.haml +++ b/app/views/admin/tickets/_form.html.haml @@ -4,7 +4,22 @@ %abbr{title: 'This field is required'} * = f.text_field :title, required: true, autofocus: true, class: 'form-control' .form-group + = f.label :description = f.text_area :description, rows: 5, data: { provide: 'markdown' }, class: 'form-control' + .form-group + = f.label :email_subject + = f.text_field :email_subject, class: 'form-control' + .form-group + = f.label :email_body + = f.text_area :email_body, rows: 10, cols: 20, class: 'form-control' + - email_template = default_ticket_email_template + %a.btn.btn-link.control_label.load_template{'data-subject-input-id' => email_template[:subject_input_id], + 'data-subject-text' => email_template[:subject_text], + 'data-body-input-id' => email_template[:body_input_id], + 'data-body-text' => email_template[:body_text] + } Load Default Email + %a.btn.btn-link.control_label.template_help_link{ 'data-name' => 'accepted_help' } Show Help + = render partial: 'shared/help', locals: { id: 'accepted_help', show_event_variables: true, show_ticket_variables: true} .form-group = f.label :price = f.number_field :price, class: 'form-control' @@ -17,9 +32,16 @@ %span.help-block Your conference tickets are in = f.object.conference.tickets.first.price_currency - .checkbox - %label - = f.check_box :registration_ticket + .form-group + = f.check_box :registration_ticket, class: 'form-check-input' + = f.label :registration_ticket, class: 'form-check-label' + .help-block A registration ticket is with which user register for the conference. + .form-group + = f.check_box :visible, class: 'form-check-input' + = f.label :visible, 'Visible?', class: 'form-check-label' + .help-block + Only visible tickets are available to registrants. Non-visible tickets can only be managed by Admins. + %p.text-right = f.submit nil, class: 'btn btn-primary' diff --git a/app/views/admin/emails/_help.html.haml b/app/views/admin/tickets/_help.html.haml similarity index 80% rename from app/views/admin/emails/_help.html.haml rename to app/views/admin/tickets/_help.html.haml index 371bac7b7..0ad816dd9 100644 --- a/app/views/admin/emails/_help.html.haml +++ b/app/views/admin/tickets/_help.html.haml @@ -10,13 +10,6 @@ %tr %td {conference} %td The full conference title - - if show_event_variables - %tr - %td {proposalslink} - %td A link to the user's proposal page - %tr - %td {eventtitle} - %td The title of an accepted or rejected proposal %tr %td {conference_start_date} %td The start date of the conference @@ -51,14 +44,16 @@ - if @conference.program.schedule_public %td {schedule_link} %td The link to complete schedule of the conference - - if @conference.splashpage && @conference.splashpage.public + - if @conference.splashpage && @conference.splashpage.public? %tr %td {conference_splash_link} %td The link to conference splash page - - if @conference.booths - %tr - %td {submitter_name} - %td Submitter's name - %tr - %td {booth_title} - %td Booth's title + %tr + %td {ticket_quantity} + %td The quantity of tickets purchased + %tr + %td {ticket_title} + %td The ticket title + %tr + %td {ticket_purchase_id} + %td The id of the ticket purchase transaction diff --git a/app/views/admin/tickets/index.html.haml b/app/views/admin/tickets/index.html.haml index 369adbc10..fc632ac81 100644 --- a/app/views/admin/tickets/index.html.haml +++ b/app/views/admin/tickets/index.html.haml @@ -16,6 +16,7 @@ %th Sold %th Turnover %th Registration Ticket + %th Visible? %th Actions %tbody - @conference.tickets.each do |ticket| @@ -31,6 +32,8 @@ = humanized_money_with_symbol ticket.tickets_turnover_total(ticket.id) %td = ticket.registration_ticket? ? 'Yes' : 'No' + %td + = ticket.visible? ? 'Yes' : 'No' %td .btn-group = link_to 'Edit', edit_admin_conference_ticket_path(@conference.short_title, ticket.id), diff --git a/app/views/admin/tickets/show.html.haml b/app/views/admin/tickets/show.html.haml index 6fa746392..883a7aaa2 100644 --- a/app/views/admin/tickets/show.html.haml +++ b/app/views/admin/tickets/show.html.haml @@ -7,7 +7,13 @@ Ticket %small = humanized_money_with_symbol @ticket.price - = link_to 'Edit Ticket', edit_admin_conference_ticket_path, class: 'btn btn-primary pull-right' + = link_to 'Edit Ticket', edit_admin_conference_ticket_path, class: 'btn btn-primary pull-right' + - if can? :give, Ticket + .pull-right + = link_to 'Give a Ticket', '#', + data: { toggle: 'modal', target: "#modal-give-ticket-#{@ticket.id}" }, + class: 'button btn btn-default btn-info' + %p.text-muted People who bought this ticket .row @@ -15,24 +21,56 @@ %table.datatable %thead %tr - %th # + %th ID %th Name %th Quantity %th E-Mail %th Affiliation %th Paid + %th Date + %th + %span.sr-only Delete + ❌ %tbody - @ticket.buyers.each_with_index do |buyer, index| + - purchases = buyer.ticket_purchases.where(ticket_id: @ticket.id) %tr %td - = index + 1 + = purchases.length == 1 ? purchases.first.id : purchases.map(&:id) %td = buyer.name %td - = buyer.ticket_purchases.where(ticket_id: @ticket.id).sum('quantity') + = purchases.sum('quantity') %td = buyer.email %td = buyer.affiliation %td = @ticket.tickets_paid(buyer) + %td + = purchases.first.created_at + %td + - if purchases.length == 1 + = button_to("Delete", conference_ticket_purchase_path(@conference, purchases.first.id), method: :delete, data: {confirm: "Are you sure?"}, class: 'btn btn-danger btn-sm') + +- content_for :modals do + .modal.fade{ id: "modal-give-ticket-#{@ticket.id}" } + .modal-dialog + .modal-content + = form_for(@ticket.ticket_purchases.new, + url: give_admin_conference_ticket_path(@conference, @ticket)) do |f| + .modal-header + %button.close{ data: { dismiss: 'modal' } } + %i.fa.fa-close + %h3.modal-title + Give a #{@ticket.title} Ticket + .modal-body + .form-group + = f.label :user_id, 'Search Users' + = f.select :user_id, [], {}, { multiple: false, class: "select-help-toggle js-userSelector form-control", placeholder: "Search users..." } + %span.help-block + Search for a user by name, email, or username. + .modal-footer + = f.submit 'Give Ticket', class: 'btn btn-primary' + += render partial: 'shared/user_selectize' diff --git a/app/views/admin/tracks/_all_tracks.xlsx.axlsx b/app/views/admin/tracks/_all_tracks.xlsx.axlsx index 1b9566a50..ee29fb331 100644 --- a/app/views/admin/tracks/_all_tracks.xlsx.axlsx +++ b/app/views/admin/tracks/_all_tracks.xlsx.axlsx @@ -2,7 +2,7 @@ wb.add_worksheet(name: 'all tracks') do |sheet| bold_style = wb.styles.add_style(b: true) - wrap_text = wb.styles.add_style alignment: {wrap_text: true} + wrap_text = wb.styles.add_style alignment: { wrap_text: true } row = ['Track ID', 'Name', 'Description', @@ -25,7 +25,7 @@ wb.add_worksheet(name: 'all tracks') do |sheet| row << track.try(:submitter).try(:name) row << (track.cfp_active? ? 'Yes' : 'No') row << track.state - sheet.add_row row , style: wrap_text - sheet.column_widths 10,20,50,20,12,12,20,15,10 + sheet.add_row row, style: wrap_text + sheet.column_widths 10, 20, 50, 20, 12, 12, 20, 15, 10 end end diff --git a/app/views/admin/tracks/_confirmed_tracks.xlsx.axlsx b/app/views/admin/tracks/_confirmed_tracks.xlsx.axlsx index 1cb773183..6c3c84eee 100644 --- a/app/views/admin/tracks/_confirmed_tracks.xlsx.axlsx +++ b/app/views/admin/tracks/_confirmed_tracks.xlsx.axlsx @@ -2,7 +2,7 @@ wb.add_worksheet(name: 'all tracks') do |sheet| bold_style = wb.styles.add_style(b: true) - wrap_text = wb.styles.add_style alignment: {wrap_text: true} + wrap_text = wb.styles.add_style alignment: { wrap_text: true } row = ['Track ID', 'Name', 'Description', @@ -25,7 +25,7 @@ wb.add_worksheet(name: 'all tracks') do |sheet| row << track.try(:submitter).try(:name) row << (track.cfp_active? ? 'Yes' : 'No') row << track.state - sheet.add_row row , style: wrap_text - sheet.column_widths 10,20,50,20,12,12,20,15,10 + sheet.add_row row, style: wrap_text + sheet.column_widths 10, 20, 50, 20, 12, 12, 20, 15, 10 end end diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 51a50069a..12f44433e 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -14,9 +14,10 @@ url: "#{toggle_confirmation_admin_user_path(@user.id)}?user[to_confirm]=", class: 'switch-checkbox', readonly: true - .checkbox + .form-group.switch %label - = f.check_box :is_admin + = f.check_box :is_admin, + class: 'switch-checkbox' Is admin %span.help-block An admin can create a new conference, manage users and make other users admins. .form-group diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 2556f090e..03f5684f0 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -16,15 +16,17 @@ %th{ width: '0' } ID %th{ width: '0' } Confirmed? %th{ width: '0' } Email - %th{ width: '50%' } Name - %th{ width: '0' } Conferences Attended + %th{ width: '30%' } Name + %th{ width: '20%' } Conferences Attended %th{ width: '50%' } Roles %th{ width: '0' } Actions + %th{ style: 'display: none' } Confirmed? %tbody :javascript $(function () { $('#users.datatable').DataTable({ + "buttons": { buttons: ["csv"] }, "processing": true, "serverSide": true, "ajax": $('#users.datatable').data('source'), @@ -36,6 +38,7 @@ { "data": "confirmed_at", "render": function (data, type, row, meta) { + console.log(meta) return ''+data+'' } }, + { "data": "email" }, + { "data": "username" }, { "data": "attended" }, { "data": "roles", @@ -71,6 +75,11 @@ 'Edit'+ ''; } + }, + { + "data": "confirmed", + "className": 'hidden', + "render": false } ] }); diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index c9048be4e..c0a8f1fb5 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -12,8 +12,10 @@ .tab-content #user-info-content.tab-pane{class: "#{'active' unless params[:tab] == 'submissions-content'}"} - if can? :edit, @user - .pull-right + .pull-right.btn-group = link_to 'Edit', edit_admin_user_path(@user), class: 'btn btn-primary' + = link_to 'Delete', admin_user_path(@user),method: :delete, class: 'btn btn-danger', + data: {confirm: "Are you sure?"} %table.table - @show_attributes.each do |attr| %tr @@ -26,6 +28,9 @@ - elsif attr == 'biography' %td = markdown(@user.biography) + - elsif attr == 'profile_picture' + %td + = image_tag @user.profile_picture(size: '100'), alt: '' - elsif attr == 'email' %td = @user.send(attr) diff --git a/app/views/admin/venues/_form.html.haml b/app/views/admin/venues/_form.html.haml index 960b01e4c..d7fca0db4 100644 --- a/app/views/admin/venues/_form.html.haml +++ b/app/views/admin/venues/_form.html.haml @@ -7,7 +7,7 @@ %li.active = link_to 'Details', '#details-content', 'data-toggle' => 'tab' %li - = link_to 'Commercials', '#commercials-content', 'data-toggle' => 'tab' + = link_to 'Materials', '#commercials-content', 'data-toggle' => 'tab' .tab-content #details-content.tab-pane.active @@ -26,7 +26,7 @@ %span.help-block = markdown_hint .form-group - = f.label 'Logo' + = f.label :picture, 'Logo' %br - if @venue.picture? = image_tag @venue.picture.thumb.url @@ -38,8 +38,10 @@ = f.label :street %abbr{title: 'This field is required'} * = f.text_field :street, required: true, class: 'form-control' + .form-group = f.label :postalcode = f.text_field :postalcode, class: 'form-control' + .form-group = f.label :city %abbr{title: 'This field is required'} * = f.text_field :city, required: true, class: 'form-control' @@ -55,7 +57,7 @@ = f.submit nil, class: 'btn btn-primary' #commercials-content.tab-pane - - if can? :create, @venue.commercial and @venue.id + - if can?(:create, @venue.commercial) and @venue.id - if @venue.commercial.nil? .row .col-md-6 @@ -66,31 +68,18 @@ .col-md-6 = form_for(Commercial.new,as: :commercial, url: admin_conference_venue_venue_commercial_path(conference_id: @conference.short_title)) do |f| .form-group + = f.label :title + = f.text_field :title, class: 'form-control' = f.label :url = f.url_field :url, class: 'form-control', required: 'required' %span.help-block - Just paste the url of your video/photo provider. Currently supported: YouTube, Vimeo, SpeakerDeck, SlideShare, Instagram, Flickr. + Just paste the url of your video/photo provider. Anything that supports an iframe is allowed. = f.submit nil, class: 'btn btn-primary pull-right', id: 'commercial_submit_action', disabled: true %hr - else - - if @venue.commercial.persisted? - .col-md-4 - .thumbnail - .flexvideo{ id: "resource-content-#{@venue.commercial.id}"} - = render partial: 'shared/media_item', locals: { commercial: @venue.commercial } - .caption - - if can? :update, @venue.commercial - = form_for @venue.commercial, as: :commercial, url: admin_conference_venue_venue_commercial_path(conference_id: @conference.short_title, id: @venue.commercial.id) do |f| - .form-group - = f.label :url - = f.url_field :url, id: "commercial_url_#{@venue.commercial.id}", class: 'form-control', required: 'required' - %span.help-block - Just paste the url of your video/photo provider. Currently supported: YouTube, Vimeo, SpeakerDeck, SlideShare, Instagram, Flickr. - = f.submit nil, class: 'btn btn-success' - - if can? :destroy, @venue.commercial - = link_to 'Delete', admin_conference_venue_venue_commercial_path(conference_id: @conference.short_title, id: @venue.commercial.id), - method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' - %hr - + .col-md-4 + %div{ id: "resource-content-#{@venue.commercial.id}"} + = render partial: 'shared/media_item', locals: { commercial: @venue.commercial } + = render 'admin/commercials/update_form', form_url: admin_conference_venue_venue_commercial_path(conference_id: @conference.short_title, id: @venue.commercial.id), conference: @conference, commercial: @venue.commercial - else - First Create Venue, then update commercial + First Create Venue, then update materials diff --git a/app/views/admin/venues/show.html.haml b/app/views/admin/venues/show.html.haml index 36906e05c..de29c4e3a 100644 --- a/app/views/admin/venues/show.html.haml +++ b/app/views/admin/venues/show.html.haml @@ -12,7 +12,7 @@ -else - if @venue.commercial.nil? .row - %img{ "data-src" => "holder.js/500x300?text=No Commercial Set", class: 'img-responsive img-rounded' } + %img{ "data-src" => "holder.js/500x300?text=No Materials Set", class: 'img-responsive img-rounded' } - else - if @venue.commercial.persisted? .thumbnail diff --git a/app/views/admin/versions/_object_desc_and_link.html.haml b/app/views/admin/versions/_object_desc_and_link.html.haml index 1728b999e..6870af950 100644 --- a/app/views/admin/versions/_object_desc_and_link.html.haml +++ b/app/views/admin/versions/_object_desc_and_link.html.haml @@ -15,18 +15,14 @@ - role = current_or_last_object_state('Role', object.role_id) - role_name = role.try(:name) || PaperTrail::Version.where(item_type: 'Role', item_id: object.role_id).last.changeset[:name].second role - - if role_name == 'organization_admin' - - if Organization.find_by(id: version.conference_id) - -# organization_admin belongs to organization and not conferences - - organization = Organization.find_by(id: version.conference_id) - = link_if_alive version, role_name, - admins_admin_organization_path(organization), organization - - else - (Deleted Organization) - - else + - if version.conference_id - conference = Conference.find_by(id: version.conference_id) - conference_short_title = conference.try(:short_title) || current_or_last_object_state('Conference', version.conference_id).try(:short_title) || ' ' = link_if_alive version, role.try(:name), admin_conference_role_path(conference_short_title,role.try(:name) || ' '), conference + - elsif version.organization_id + - organization = Organization.find(version.organization_id) + = link_if_alive version, role_name, + admins_admin_organization_path(organization), organization = version.event == 'create' ? 'to' : 'from' user @@ -79,8 +75,9 @@ = link_to (current_or_last_object_state('Event', object.event_id).try(:title) || 'deleted'), admin_conference_program_event_path(conference_short_title, object.event_id) in - = link_to "Schedule #{version.item.schedule_id}", - admin_conference_schedule_path(conference_short_title, version.item.schedule_id) + - if version.item + = link_to "Schedule #{version.item&.schedule_id}", + admin_conference_schedule_path(conference_short_title, version.item&.schedule_id) - when 'Schedule' = link_if_alive version, "Schedule #{version.item_id}", @@ -133,19 +130,15 @@ - when 'Role' role - role_name = object.try(:name) || PaperTrail::Version.where(item_type: 'Role', item_id: version.item_id).last.changeset[:name].second - - if role_name == 'organization_admin' - - if Organization.find_by(id: version.conference_id) - -# organization_admin belongs to organization and not conferences - - organization = Organization.find_by(id: version.conference_id) - = link_if_alive version, role_name, - admins_admin_organization_path(organization), organization - - else - (Role Deleted) - - else + - if version.conference_id - conference = Conference.find_by(id: version.conference_id) - conference_short_title = conference.try(:short_title) || current_or_last_object_state('Conference', version.conference_id).try(:short_title) || ' ' = link_if_alive version, role_name, admin_conference_role_path(conference_short_title, role_name), conference + - elsif version.organization_id + - organization = Organization.find(version.organization_id) + = link_if_alive version, role_name, + admins_admin_organization_path(organization), organization - when 'Venue' venue @@ -203,20 +196,16 @@ = link_to_user(version.item_id) - unless %w(Conference Subscription Registration User Organization).include?(version.item_type) - - if (version.item_type == 'Role' && role_name == 'organization_admin') || (version.item_type == 'UsersRole' && role_name == 'organization_admin') - in organization - - if Organization.find_by(id: version.conference_id) - -# organization_admin belongs to organization and not conferences - - organization = Organization.find_by(id: version.conference_id) - = link_to_organization(version.conference_id) - - else - (Organization Deleted) - - elsif version.item_type == 'Commercial' + - if version.item_type == 'Commercial' - commercial = current_or_last_object_state(version.item_type, version.item_id) - commercialable = current_or_last_object_state(commercial.commercialable_type, commercial.commercialable_id) - unless commercial.commercialable_type == 'Conference' in conference = link_to_conference(version.conference_id) + - elsif version.organization_id + - organization = Organization.find(version.organization_id) + in organization + = link_to_organization(version.organization_id) - else in conference = link_to_conference(version.conference_id) diff --git a/app/views/application/_big_statistic.haml b/app/views/application/_big_statistic.haml index c45a17f15..d767a0b19 100644 --- a/app/views/application/_big_statistic.haml +++ b/app/views/application/_big_statistic.haml @@ -1,13 +1,13 @@ - reverse ||= false - variant = variant_from_delta(delta, reverse: reverse) -.dashbox.text-center - %span - = icon('fa-solid', icon) - = value - %p.text-nowrap - %small - = subtitle.pluralize(value) - %span.label{ class: "label-#{variant}" } - = delta - since you last logged in - +.dashbox.panel.panel-primary + .paenl-body.text-center + %span + = icon('fa-solid', icon) + = value + %p.text-nowrap + %small + = subtitle.pluralize(value) + .label{ class: "label-#{variant}" } + = delta || 0 + since last log in diff --git a/app/views/booths/index.html.haml b/app/views/booths/index.html.haml index 85f847b44..ed5628b5a 100644 --- a/app/views/booths/index.html.haml +++ b/app/views/booths/index.html.haml @@ -48,5 +48,6 @@ = link_to 'Re-submit', restart_conference_booth_path(@conference.short_title, booth), method: :patch, class: 'btn btn-mini btn-success', id: "restart_booth_#{booth.id}" + .pull-right = link_to "Add #{(t'booth').capitalize }", new_conference_booth_path(@conference.short_title), class: 'button btn btn-primary' diff --git a/app/views/conference_registrations/_event.html.haml b/app/views/conference_registrations/_event.html.haml new file mode 100644 index 000000000..dd389f665 --- /dev/null +++ b/app/views/conference_registrations/_event.html.haml @@ -0,0 +1,39 @@ +.panel.panel-default{ class: ('panel-success' if event.registrations.include?(registration)) } + .panel-heading + %label{ for: "registration_event_ids_#{event.id}" } + %h3{ style: 'margin: 0 auto;' } + = hidden_field_tag 'registration[event_ids][]', nil + = check_box_tag 'registration[event_ids][]', event.id, event.registrations.include?(registration), id: "registration_event_ids_#{event.id}" + = event.title + %small + = event.subtitle + .text-muted + = registered_text(event) + - if event.scheduled? + (Scheduled on: #{event.time.to_date}) + + .panel-body + -# + %p + = canceled_replacement_event_label(event, event_schedule) + = replacement_event_notice(event_schedule) + %p + - if event.speakers.any? + presented by #{event.speaker_names} + - if event_schedule.present? + .h4.track + %span.fa.fa-clock-o + %span.label{ style: 'background-color: grey' } + = event_schedule.start_time.strftime('%A, %B %-d %H:%M') + \- + = event_schedule.end_time.strftime('%H:%M') + %p + = markdown(truncate(event.abstract, length: 250)) + -# TODO: More informative text or aria-label. + = link_to 'more', conference_program_proposal_path(conference.short_title, event.id), target: '_blank' + + - if event.track + %span.track + %span.fa.fa-road + %span.label{ style: "background-color: #{event.track.color}; color: #{contrast_color(event.track.color)}" } + = event.track.name diff --git a/app/views/conference_registrations/_registration_info.html.haml b/app/views/conference_registrations/_registration_info.html.haml index e8293d549..a952c067c 100644 --- a/app/views/conference_registrations/_registration_info.html.haml +++ b/app/views/conference_registrations/_registration_info.html.haml @@ -17,6 +17,11 @@ %h4 Pre-registration required for the following: - @registration.events_ordered.each do |event| + %p + You are registered for #{pluralize(@registration.events.count, 'event')}. + They are at the end of this list. + - @registration.events_ordered.each do |event| + = render 'conference_registrations/event', event: event, event_schedule: event.event_schedules.first, conference: @conference, registration: @registration %label = hidden_field_tag "registration[event_ids][]", nil = check_box_tag "registration[event_ids][]", event.id, event.registrations.include?(@registration) diff --git a/app/views/conference_registrations/show.html.haml b/app/views/conference_registrations/show.html.haml index c419d5359..9d89752ab 100644 --- a/app/views/conference_registrations/show.html.haml +++ b/app/views/conference_registrations/show.html.haml @@ -78,7 +78,7 @@ = link_to event.title, conference_program_proposal_path(@conference.short_title, event.id) = '(' + registered_text(event) + ')' - - if @conference.tickets.any? + - if @conference.tickets.visible.any? .row .col-md-12 %h4 @@ -151,7 +151,7 @@ Registered = word_pluralize(@conference.participants.count, 'Attendee') - @conference.participants.each do |participant| - = link_to image_tag(participant.gravatar_url(size: '25'), title: "#{participant.name}!", class: 'img-circle'), user_path(participant) + = link_to image_tag(participant.profile_picture(size: '25'), title: "#{participant.name}!", class: 'img-circle'), user_path(participant) .col-md-4.col-md-offset-2 - if @conference.program.speakers.confirmed.any? %h4 @@ -162,4 +162,4 @@ Confirmed = word_pluralize(@conference.program.speakers.confirmed.count, 'Speaker') - @conference.program.speakers.confirmed.each do |speaker| - = link_to image_tag(speaker.gravatar_url(size: '25'), title: "#{speaker.name}!", class: 'img-circle'), user_path(speaker) + = link_to image_tag(speaker.profile_picture(size: '25'), title: "#{speaker.name}!", class: 'img-circle'), user_path(speaker) diff --git a/app/views/conferences/_about_and_happening_now.haml b/app/views/conferences/_about_and_happening_now.haml new file mode 100644 index 000000000..239cbbce4 --- /dev/null +++ b/app/views/conferences/_about_and_happening_now.haml @@ -0,0 +1,34 @@ += content_for :happening_now do + #happening-now + = render 'happening_now', conference: conference, + events_schedules: events_schedules, pagy: pagy, + events_schedules_length: events_schedules_length, + events_schedules_limit: events_schedules_limit, + is_happening_next: is_happening_next + += content_for :about do + #about + = markdown(conference.description, false) + +%section#about-and-happening-now + .container + .row + .col-md-12 + = yield :additional_messages + .row + -# happening now events are displayed second in md or lg view + - if conference.splashpage.include_happening_now && conference.splashpage.include_program? + - if conference.description.present? + .col-md-6.col-md-push-6.col-lg-4.col-lg-push-8 + = yield :happening_now + - else + .col-md-12 + = yield :happening_now + - if conference.description.present? + - if conference.splashpage.include_happening_now && conference.splashpage.include_program + .col-md-6.col-md-pull-6.col-lg-8.col-lg-pull-4 + = yield :about + - else + .col-md-12 + = yield :about + .trapezoid diff --git a/app/views/conferences/_call_for_content.haml b/app/views/conferences/_call_for_content.haml index 8287e9119..58e7bfef7 100644 --- a/app/views/conferences/_call_for_content.haml +++ b/app/views/conferences/_call_for_content.haml @@ -1,6 +1,6 @@ = content_for :splash_nav do %li - %a.smoothscroll{ href: '#call' } Call For Content + %a.smoothscroll{ href: '#call' } Proposals %section#call .container @@ -20,3 +20,4 @@ call: call_for_booths - if two_calls_open(call_for_events, call_for_tracks, call_for_booths) .col-md-2.col-sm-2.hidden-xs   + .trapezoid diff --git a/app/views/conferences/_call_for_papers.haml b/app/views/conferences/_call_for_papers.haml index 4847a9928..d0d513ff7 100644 --- a/app/views/conferences/_call_for_papers.haml +++ b/app/views/conferences/_call_for_papers.haml @@ -2,7 +2,7 @@ expires_in: 1.hour) do .col-md-4.col-sm-4.text-center %h2 - Call for Papers + Call for Participation %p.lead We are now accepting proposals for sessions! %p diff --git a/app/views/conferences/_conference_details.html.haml b/app/views/conferences/_conference_details.html.haml index a79d31e14..b860d609a 100644 --- a/app/views/conferences/_conference_details.html.haml +++ b/app/views/conferences/_conference_details.html.haml @@ -2,11 +2,13 @@ .col-md-12 .well .row - .col-md-4.text-center - = image_tag(conference.picture_url, class: 'img-responsive') if conference.picture? - .col-md-6 + .col-md-10 + .row + .col-md-5 + .col-md-2 + = image_tag(conference.picture_url, class: 'img-responsive') if conference.picture? %h3 - = conference.title + = conference.title.html_safe %small %b = date_string(conference.start_date, conference.end_date) @@ -18,14 +20,14 @@ %span>= conference.country_name - unless conference.description.blank? %p - = markdown(conference.description) + = markdown(truncate(conference.description, length: 1000, separator: "\n", escape: false), escape_html=false) .col-md-2 .btn-group-vertical - if !@conference || @conference != conference - if conference.splashpage && conference.splashpage.public = link_to "View Conference", conference_path(conference.short_title), class: 'btn btn-default' - if conference.program and conference.program.schedule_public - = link_to "Schedule", conference_schedule_path(conference.short_title), class: 'btn btn-default' + = link_to "Schedule", vertical_schedule_conference_schedule_path(conference.short_title), class: 'btn btn-default' - unless conference.code_of_conduct.blank? = link_to "Code of Conduct", [:code_of_conduct, conference.organization], diff --git a/app/views/conferences/_footer.haml b/app/views/conferences/_footer.haml index cba8f844b..1e6f1b0ed 100644 --- a/app/views/conferences/_footer.haml +++ b/app/views/conferences/_footer.haml @@ -3,12 +3,12 @@ %i.fa-solid.fa-2x.fa-circle-arrow-up :javascript - $(function(){ - $(document).on( 'scroll', function(){ - if ($(window).scrollTop() > 100) { + $(function() { + $(document).on( 'scroll', function() { + if ($(window).scrollTop() > 100) { $('.scroll-top-wrapper').addClass('show'); - } else { + } else { $('.scroll-top-wrapper').removeClass('show'); - } + } }); }); diff --git a/app/views/conferences/_gallery.html.haml b/app/views/conferences/_gallery.html.haml index 696354025..791f47365 100644 --- a/app/views/conferences/_gallery.html.haml +++ b/app/views/conferences/_gallery.html.haml @@ -1,17 +1,14 @@ - -%div{ class: "modal fade", id: "gallery", tabindex: "-1", role: "dialog", "aria-labelledby" => "myLargeModalLabel", "aria-hidden"=> "true" } - %div{ class: "modal-dialog modal-lg" } - %div{ class: "modal-content" } - %div{ class: "modal-body" } - %div{ id: "carousel-example-generic", class: "carousel slide", "data-ride" => "carousel" } - - %div{ class: "carousel-inner" } +.modeal.fade#gallery{ tabindex: "-1", role: "dialog", "aria-labelledby": "myLargeModalLabel", "aria-hidden": true } + .modal-dialog.modal-lg + .modal-content + .modal-body + .carousel.slide#carousel-example-generic{ "data-ride": "carousel" } + .carousel-inner - - %a{ class: "left carousel-control", href: "#carousel-example-generic", role: "button", "data-slide" => "prev" } - %span.fa-solid.fa-chevron-left - %a{ class: "right carousel-control", href: "#carousel-example-generic", role: "button", "data-slide" => "next" } - %span.fa-solid.fa-chevron-right + %a.left.carousel-control{ href: "#carousel-example-generic", "data-slide": "prev" } + %span.fa-solid.fa-chevron-left{"aria-label": 'Previous Item'} + %a.right.carousel-control{ href: "#carousel-example-generic", "data-slide": "next" } + %span.fa-solid.fa-chevron-right{'aria-label': 'Next Item'} :javascript @@ -20,7 +17,7 @@ $('#gallery-btn').click(function(){ if(count == 0){ $('#gallery').modal('show'); - $('#gallery .modal-body').append(''); + $('#gallery .modal-body').append(''); count +=1; } else{ diff --git a/app/views/conferences/_happening_now.haml b/app/views/conferences/_happening_now.haml new file mode 100644 index 000000000..cf7f6f937 --- /dev/null +++ b/app/views/conferences/_happening_now.haml @@ -0,0 +1,20 @@ +- if events_schedules.present? && events_schedules.any? + .row + %h3.text-left{ style: 'margin-bottom:30px; padding-left:20px' } + - if is_happening_next + Upcoming Events + - else + Events Happening Now + - events_schedules.each do |event_schedule| + = render 'schedules/event_mini', conference: conference, event_schedule: event_schedule, event: event_schedule.event + - if events_schedules_length > events_schedules_limit + .container{ style: 'width:100%; text-align:center' } + != pagy_bootstrap_nav_js(pagy) +- else + .row + %h3.text-center There are no upcoming events. + +:javascript + $(document).ready(function(){ + updateFavouriteStatus({ events: #{@favourited_events || []}, loggedIn: #{current_user.present?} }); + }); diff --git a/app/views/conferences/_header.haml b/app/views/conferences/_header.haml index cc9ffa8fb..72d634896 100644 --- a/app/views/conferences/_header.haml +++ b/app/views/conferences/_header.haml @@ -1,34 +1,28 @@ - cache [conference, venue, '#splash#header'] do - #banner + #banner{ style: ("background-image: url(#{splashpage.banner_photo_url})" if splashpage.banner_photo_url) } .container .row - .col-md-8.col-md-offset-2#header + - picture_present = splashpage.banner_photo? || conference.picture? + .col-md-6.col-md-offset-3{ id: (picture_present ? "header-image" : "header-no-image") } .row - .col-md-4 - - if conference.picture? + - if conference.picture? && !splashpage.banner_photo? + .col-md-4 = image_tag(conference.picture_url, class: 'img-responsive img-center', id: 'splash-logo') .col-md-8 %h1 - = conference.title + = conference.title.html_safe %h3 - if conference.start_date && conference.end_date %span.date.text-nowrap = date_string(conference.start_date, conference.end_date) - if conference.venue %span.venue.text-nowrap - - if venue.website + - if venue.website.present? = sanitize link_to(venue.name, venue.website) - else = venue.city - - if venue.country_name + - if venue.country != 'US' • = venue.country_name - - - unless conference.description.blank? - %section#about - .container - .row - .col-md-8.col-md-offset-2 - = markdown(conference.description) diff --git a/app/views/conferences/_highlights.haml b/app/views/conferences/_highlights.haml index 2bb5b3833..b457aebc8 100644 --- a/app/views/conferences/_highlights.haml +++ b/app/views/conferences/_highlights.haml @@ -12,7 +12,7 @@ event), class: 'thumbnail') do - if speaker - = image_tag speaker.gravatar_url(size: 300), + = image_tag speaker.profile_picture(size: 300), class: ['img-responsive', 'img-circle'], title: speaker.name .caption diff --git a/app/views/conferences/_lodging.haml b/app/views/conferences/_lodging.haml index 768f75d1a..8c48d3555 100644 --- a/app/views/conferences/_lodging.haml +++ b/app/views/conferences/_lodging.haml @@ -1,7 +1,3 @@ -= content_for :splash_nav do - %li - %a.smoothscroll{ href: '#lodging' } Lodging - - cache [venue, lodgings, '#splash#lodging'] do %section#lodging .container @@ -17,25 +13,22 @@ .row.row-centered - lodgings.each do |lodging| - .col-md-4.col-sm-4.col-centered.col-top + .col-md-6.col-sm-4.col-centered.col-top .thumbnail - if lodging.picture? - if lodging.website_link.present? - = link_to(lodging.website_link, class: 'thumbnail') do + = link_to(lodging.website_link) do = image_tag lodging.picture.large.url, class: 'img-responsive img-lodging' - else = image_tag lodging.picture.large.url, class: 'img-responsive img-lodging' - - else - %p.text-center - - if lodging.website_link.present? - = link_to(lodging.website_link, class: 'thumbnail') do - %i.fa-solid.fa-house.fa-5x - - else - %i.fa-solid.fa-house.fa-5x .caption %h3.text-center - = lodging.name + - if lodging.website_link.present? + = link_to(lodging.name, lodging.website_link) + - else + = lodging.name - if lodging.description.present? = markdown(lodging.description) + .trapezoid diff --git a/app/views/conferences/_program.haml b/app/views/conferences/_program.haml index 2ab2ef709..7d5f93d8e 100644 --- a/app/views/conferences/_program.haml +++ b/app/views/conferences/_program.haml @@ -1,16 +1,18 @@ = content_for :splash_nav do %li - %a.smoothscroll{ href: '#program' } Program + = link_to('Schedule', events_conference_schedule_path(conference)) + %li + %a.smoothscroll{ href: '#program' } Featured -- cache [conference, highlights, tracks, booths, '#splash#program'] do +- cache [conference, conference.program, highlights, tracks, booths, '#splash#program'] do %section#program .container .row .col-md-12 %p.lead.text-center %span.notranslate - = conference.title - has the most awesome program ever! + = conference.title.html_safe + will have an engaging program! - unless highlights.blank? = render 'highlights', conference_id: conference.short_title, @@ -28,9 +30,10 @@ .row .col-md-12 %p.cta-button.text-center - = link_to(conference_schedule_path(conference.short_title), + = link_to(events_conference_schedule_path(conference.short_title), class: 'btn btn-success btn-lg') do - Full Schedule + View Full Schedule + .trapezoid - unless booths.blank? - booths.each do |booth| diff --git a/app/views/conferences/_registration.haml b/app/views/conferences/_registration.haml index 51258f15c..373e1c13e 100644 --- a/app/views/conferences/_registration.haml +++ b/app/views/conferences/_registration.haml @@ -1,7 +1,3 @@ -= content_for :splash_nav do - %li - %a.smoothscroll{ href: '#registration' } Registration - - cache [conference, registration_period, tickets, '#splash#registration'] do %section#registration .container @@ -37,3 +33,4 @@ = link_to('Register Now', new_conference_conference_registration_path(conference_id), class: 'btn btn-lg btn-success') + .trapezoid diff --git a/app/views/conferences/_social_media.haml b/app/views/conferences/_social_media.haml index 672a19cdf..4cfd4d2e0 100644 --- a/app/views/conferences/_social_media.haml +++ b/app/views/conferences/_social_media.haml @@ -27,3 +27,4 @@ - if contact.email? = mail_to "#{ contact.email }" do %i.fa-solid.fa-envelope.fa-4x + .trapezoid.social-media diff --git a/app/views/conferences/_sponsors.haml b/app/views/conferences/_sponsors.haml index 5d7bbaa95..ab9e36385 100644 --- a/app/views/conferences/_sponsors.haml +++ b/app/views/conferences/_sponsors.haml @@ -27,9 +27,10 @@ .row .col-md-12 %h3.text-center - Want to sponsor? + Interested in sponsoring #{conference.title}? = link_to(sponsorship_mailto(conference)) do - Contact us! + Please, contact us! + .trapezoid - sponsors.each do |sponsor| - content_for :modals do diff --git a/app/views/conferences/_tickets.haml b/app/views/conferences/_tickets.haml index 325f04b83..c3036f53b 100644 --- a/app/views/conferences/_tickets.haml +++ b/app/views/conferences/_tickets.haml @@ -6,20 +6,22 @@ %section#tickets .container .row - .col-md-12.text-center + .col-md-12.text-center.col-top %h2 - Support - = conference.title + Sign up for #{conference.title}! + .row.text-center + %h3 + = link_to('Looking for the SAP Young Thinkers Learning Festival?', conference_path('ylf2021')) if conference.short_title == '2021' .row.row-centered - tickets.each do |ticket| - .col-md-3.col-sm-3.col-centered.col-top - = link_to(conference_tickets_path(conference.short_title), - class: 'thumbnail') do + .col-lg-4.col-md-3.col-sm-3.col-centered.col-top + = link_to(conference_tickets_path(conference.short_title), class: 'thumbnail') do .caption %h3.text-center.word_break = ticket.title .word_break - = markdown(ticket.description) + = short_ticket_description(ticket) %button.btn-block.btn.btn-lg.btn-success - %i.fa-solid.fa-ticket.fa-fw + %i.fa-solid.fa-ticket.fa-fw{ "aria-hidden": true } = humanized_money_with_symbol(ticket.price) + .trapezoid diff --git a/app/views/conferences/_venue.haml b/app/views/conferences/_venue.haml index f25851c1e..d47769c41 100644 --- a/app/views/conferences/_venue.haml +++ b/app/views/conferences/_venue.haml @@ -1,10 +1,16 @@ = content_for :splash_nav do %li - %a.smoothscroll{ href: '#venue' } Venue + %a.smoothscroll{ href: '#venue' } Location %section#venue - if venue.location? = render '/conferences/venue_map', venue: venue + - if venue.description.present? + .container + .row + .col-md-8.col-md-offset-2 + = markdown(venue.description, escape_html=false) + .trapezoid - else - cache [venue, commercial, '#splash#venue'] do .container @@ -42,3 +48,4 @@ - if venue.website %br = sanitize link_to(h(venue.website), h(venue.website)) + .trapezoid diff --git a/app/views/conferences/index.html.haml b/app/views/conferences/index.html.haml index 720e1cdfd..9049d912c 100644 --- a/app/views/conferences/index.html.haml +++ b/app/views/conferences/index.html.haml @@ -8,14 +8,15 @@ - if @antiquated and @antiquated.any? .row .col-md-12 - %p.text-right + .page-header %button{ type: 'button', class: 'btn btn-link btn-sm', 'data-toggle' => 'collapse', 'data-target' => '#antiquated', 'aria-expanded' => 'true', 'aria-controls' => 'antiquated'} - Older conferences - %span.notranslate - = "(#{@antiquated.count})" - %i.fa-solid.fa-chevron-right - %i.fa-solid.fa-chevron-down{ style: 'display: none' } - #antiquated.collapse + %h2 + Past Conferences + %span.notranslate + = "(#{@antiquated.count})" + %i.fa-solid.fa-chevron-right{ style: 'display: none' } + %i.fa-solid.fa-chevron-down + #antiquated - @antiquated.each do |conference| = render '/conferences/conference_details', conference: conference %p diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml index 85b2bac65..29c5014e2 100644 --- a/app/views/conferences/show.html.haml +++ b/app/views/conferences/show.html.haml @@ -1,10 +1,10 @@ - content_for :head do %meta{ property: "og:title", content: @conference.title } %meta{ property: "og:site_name", content: ENV.fetch('OSEM_NAME', 'OSEM') } - %meta{ property: "og:description", content: @conference.description } + %meta{ property: "og:description", content: plain_text(@conference.description) } %meta{ property: "og:url", content: conference_url(@conference.short_title) } %meta{ property: "twitter:title", content: (@conference.title) } - %meta{ property: "twitter:description", content: @conference.description } + %meta{ property: "twitter:description", content: plain_text(@conference.description) } - if @conference.picture? %meta{ property: "og:image", content: @image_url } %meta{ property: "og:image:secure_url", content: @image_url } @@ -13,72 +13,80 @@ - else %meta{ property: "twitter:card", content: "summary" } -= content_for :title do - = @conference.title +- provide :title, @conference.title += content_for :additional_messages do + - if user_signed_in? && @conference.registration_tickets.any? && current_user.ticket_purchases.by_conference(@conference).empty? + .alert.alert-dismissable.alert-info.text-center#no-tickets{ role: 'alert' } + %button.button.close{ "data-dismiss": "alert", "aria-label": "close" } + × + You have not booked any tickets for this conference yet. + = link_to "Book your tickets now!", "#tickets", class: 'btn btn-info btn-lg' + + - if @unpaid_tickets + .alert.alert-dismissable.alert-info.text-center#unpaid-tickets{ role: 'alert' } + %button.button.close{ "data-dismiss" => "alert", "aria-label"=>"close" } + × + You have unpaid tickets. Please complete your purchase. + = link_to('Purchase Tickets', new_conference_payment_path(@conference), class: 'btn btn-success btn-lg') + + - if @user_needs_to_register + .alert.alert-dismissable.alert-warning.text-center#no-reg{ role: 'alert' } + %button.button.close{ "data-dismiss" => "alert", 'aria-label': 'close' } + × + You still need to complete your registration for #{@conference.title}. + = link_to('Complete Registration', new_conference_conference_registration_path(@conference), class: 'btn btn-success btn-lg') #splash + -# - cache [@conference, @splashpage, @conference.program, current_user, '#splash#main'] do - if @conference.code_of_conduct.present? = render 'code_of_conduct', organization: @conference.organization - -# header/description - = render 'header', conference: @conference, venue: @conference.venue + -# header + = render 'header', conference: @conference, venue: @conference.venue, splashpage: @splashpage, cached: true + + -# description / happening now + - if @conference.splashpage.include_happening_now || @conference.description.present? + = render 'about_and_happening_now', conference: @conference, + events_schedules: @events_schedules, pagy: @pagy, + events_schedules_length: @events_schedules_length, + events_schedules_limit: @events_schedules_limit, + is_happening_next: @is_happening_next -# calls for content, or program - - if @conference.splashpage.include_cfp + - if @conference.splashpage.include_cfp? = render 'call_for_content', conference: @conference, call_for_events: @call_for_events, call_for_tracks: @call_for_tracks, call_for_booths: @call_for_booths, event_types: @event_types, tracks: @track_names - - if @conference.splashpage.include_program + - if @conference.splashpage.include_program? = render 'program', conference: @conference, tracks: @tracks, - highlights: @highlights, booths: @booths + highlights: @highlights, booths: @booths, cached: true -# attendance/registration - - if @conference.splashpage.include_registrations + - if @conference.splashpage.include_registrations? - if @conference.registration_open? = render 'registration', conference: @conference, registration_period: @conference.registration_period, tickets: @tickets, conference_id: @conference.short_title - if @conference.splashpage.include_tickets && @conference.tickets.any? - = render 'tickets', conference: @conference, tickets: @tickets + = render 'tickets', conference: @conference, tickets: @tickets, cached: true -# geo - - if @conference.splashpage.include_venue && @conference.venue + - if @conference.splashpage.include_venue? && @conference.venue = render 'venue', conference: @conference, venue: @conference.venue, commercial: @conference.venue.commercial - if @conference.splashpage.include_lodgings && @conference.lodgings.any? = render 'lodging', venue: @conference.venue, lodgings: @lodgings -# sponsorship - - if @conference.splashpage.include_sponsors + - if @conference.splashpage.include_sponsors? = render 'sponsors', conference: @conference, sponsorship_levels: @sponsorship_levels, sponsors: @sponsors - -# footer - - if @conference.splashpage.include_social_media - - if @conference.contact.has_social_media? - = render 'social_media', contact: @conference.contact - = render 'footer' - -- content_for :script_head do - :javascript - var triangle_tcs = tinycolor("#{h(@conference.color)}").monochromatic(); - var triangle_colors = triangle_tcs.map(function(t) { - return t.toHexString(); - }); - $(function () { - $(document).ready(function() { - var triangle_width = document.body.clientWidth; - var triangle_height = ($( "#banner" ).height() + 200 ); - var pattern = Trianglify({ width: triangle_width, - height: triangle_height, - cell_size: 100, - x_colors: triangle_colors - }); - $('#banner').css('background-image', 'url("' + pattern.png() + '")'); - }); - }); + - if @conference.splashpage.include_social_media? && @conference.contact.has_social_media? + = render 'social_media', contact: @conference.contact, cached: true + -# = render 'footer', cached: true diff --git a/app/views/conferences/show.js.erb b/app/views/conferences/show.js.erb new file mode 100644 index 000000000..61c0a3a35 --- /dev/null +++ b/app/views/conferences/show.js.erb @@ -0,0 +1,6 @@ +$('#happening-now').html("<%= j(render 'happening_now', conference: @conference, + events_schedules: @events_schedules, pagy: @pagy, + events_schedules_length: @events_schedules_length, + events_schedules_limit: @events_schedules_limit, + is_happening_next: @is_happening_next) %>"); +Pagy.init(document.getElementById('happening-now')); diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 1aa00e559..c00f3626e 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -4,7 +4,7 @@ .panel.panel-default .panel-heading %h3.panel-title - Resend confirmation instructions + Resend account confirmation instructions .panel-body = form_for(resource, as: resource_name, url: confirmation_path(resource_name), method: :post) do |f| .form-group diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 000000000..55318a91f --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,9 @@ +<%= render partial: "layouts/mailbot_header" %> +
+

Welcome to Snap!Con <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

+
+<%= render partial: "layouts/mailbot_footer" %> diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 000000000..32f4ba803 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @email %>!

+ +<% if @resource.try(:unconfirmed_email?) %> +

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

+<% else %> +

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 000000000..b41daf476 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 000000000..f667dc12f --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 000000000..41e148bf2 --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/sessions/_form_fields.html.haml b/app/views/devise/sessions/_form_fields.html.haml index 8f29c0fac..088b40c79 100644 --- a/app/views/devise/sessions/_form_fields.html.haml +++ b/app/views/devise/sessions/_form_fields.html.haml @@ -8,7 +8,7 @@ = f.password_field :password, required: true, class: 'form-control', placeholder: 'Password' - if devise_mapping.rememberable? %p.text-right.small - = f.label 'Remember me' + = f.label :remember_me, 'Remember me' = f.check_box :remember_me %p.text-right %button{type: 'submit', class: 'btn btn-success'} diff --git a/app/views/devise/shared/_help.html.haml b/app/views/devise/shared/_help.html.haml index 22a7ce21d..99eff9f1d 100644 --- a/app/views/devise/shared/_help.html.haml +++ b/app/views/devise/shared/_help.html.haml @@ -1,5 +1,5 @@ %p.text-right %a.small.btn.btn-default.btn-xs{"data-toggle" => "collapse", "data-target" => "#devise-help"} - Need Help? + Can't login? #devise-help.collapse = render 'devise/shared/links' diff --git a/app/views/devise/shared/_openid_links.html.haml b/app/views/devise/shared/_openid_links.html.haml index 2b3ba8dc0..842b355db 100644 --- a/app/views/devise/shared/_openid_links.html.haml +++ b/app/views/devise/shared/_openid_links.html.haml @@ -1,8 +1,23 @@ .text-center .btn-group.btn-group-lg#openid-btn-grp - omniauth_configured.each do |provider| - = link_to "user_#{provider}_omniauth_authorize".to_sym, class: "btn btn-success btn-lg", + - if provider != :discourse + = link_to "user_#{provider}_omniauth_authorize".to_sym, class: "btn btn-success btn-lg", id: "omniauth-#{provider}", title: "Your #{provider} login", method: :post do - %i{class: "fa-brands fa-#{provider}"} + %i{class: "fa-brands fa-#{provider}"} + - else + = link_to("user_#{provider}_omniauth_authorize".to_sym, + class: "btn btn-success btn-lg", + id: "omniauth-#{provider}", + title: "Your #{provider} login", method: :post) do + %span + Snap + %em> ! +%br +.text-center + %em If you are not currently logged into Snap! or the Snap! Forums, you will need to log in to + Snap!Con twice when using your Snap! account. We're working on fixing this. Thanks! + +%br diff --git a/app/views/devise/shared/_sign_up_form_embedded.html.haml b/app/views/devise/shared/_sign_up_form_embedded.html.haml new file mode 100644 index 000000000..94e249c4c --- /dev/null +++ b/app/views/devise/shared/_sign_up_form_embedded.html.haml @@ -0,0 +1,15 @@ +- unless current_user + %p + %strong Create an account directly for Snap!Con, or sign in using Google. + %p + You may sign in via Snap!, but the first time you try, you + %em might not + be redirected back to Snap!Con. If that happens, try a second time and the login will work. + We're trying to fix this issue. Thanks! + = semantic_fields_for @user do |u| + = u.input :username, input_html: { required: 'required', autocomplete: 'off' } + = u.input :email, input_html: { required: 'required', autocomplete: 'off' } + = u.input :password, input_html: { required: 'required', autocomplete: 'off', id: 'password_inline' } + = u.input :password_confirmation, input_html: { required: 'required', autocomplete: 'off' } + = render partial: 'devise/shared/openid' + = render partial: 'devise/shared/help' diff --git a/app/views/layouts/_admin.html.haml b/app/views/layouts/_admin.html.haml index a9add1acf..994d25e22 100644 --- a/app/views/layouts/_admin.html.haml +++ b/app/views/layouts/_admin.html.haml @@ -6,9 +6,10 @@ = render 'layouts/admin_sidebar' -else -# Index admin sidebar - = render 'layouts/admin_sidebar_index' + - cache ['#admin-sidebar', @conference, current_user] do + = render 'layouts/admin_sidebar_index' .col-md-10 #messages =render 'layouts/messages' #content - = yield \ No newline at end of file + = yield diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index f4c200060..3cf84385d 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -43,7 +43,7 @@ = link_to 'Contact', edit_admin_conference_contact_path(@conference.short_title) - if can? :index, @conference.commercials.build %li{class: "#{active_nav_li(admin_conference_commercials_path(@conference.short_title))}"} - = link_to 'Commercials', admin_conference_commercials_path(@conference.short_title) + = link_to 'Materials', admin_conference_commercials_path(@conference.short_title) - if can? :update, @conference %li{class: active_nav_li(edit_admin_conference_splashpage_path(@conference.short_title))} = link_to 'Splashpage', admin_conference_splashpage_path(@conference.short_title) @@ -116,6 +116,13 @@ - if can? :update, @conference.tickets.build %li{class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } = link_to 'Tickets', admin_conference_tickets_path(@conference.short_title) + - if can? :update, @conference.currency_conversions.build + %li + = link_to 'Currency', admin_conference_currency_conversions_path(@conference.short_title) + - if can? :update, @conference.tickets.build + %li{class: active_nav_li(admin_conference_physical_tickets_path(@conference.short_title)) } + = link_to 'Ticket Purchases', admin_conference_physical_tickets_path(@conference.short_title) + - if can? :manage, @conference.booths.build %li = link_to admin_conference_booths_path(@conference.short_title) do diff --git a/app/views/layouts/_mailbot_footer.html.erb b/app/views/layouts/_mailbot_footer.html.erb new file mode 100644 index 000000000..4cc06c7fb --- /dev/null +++ b/app/views/layouts/_mailbot_footer.html.erb @@ -0,0 +1,7 @@ + <% if @conference.present? %> +
+ <% else %> +
+ <% end %> + + diff --git a/app/views/layouts/_mailbot_header.html.erb b/app/views/layouts/_mailbot_header.html.erb new file mode 100644 index 000000000..2ad931e76 --- /dev/null +++ b/app/views/layouts/_mailbot_header.html.erb @@ -0,0 +1,72 @@ + + + + + + <%= 'Email' %> + + + + + + + + + <% if @conference.present? %> +
+ <% else %> +
+ <% end %> +
+
+ <% if defined?(@conference) && @conference.picture.present? %> + <%- image_url = @conference.picture.url %> + <% else %> + <%- image_url = 'snapcon_logo.png' %> + <% end %> + <%= image_tag(image_url, style: "display:block;height:70px;width:auto;", alt: "#{ENV['OSEM_NAME']} logo") %> +
+
+
diff --git a/app/views/layouts/_messages.html.haml b/app/views/layouts/_messages.html.haml index 00ecde342..cc2990c4f 100644 --- a/app/views/layouts/_messages.html.haml +++ b/app/views/layouts/_messages.html.haml @@ -10,8 +10,8 @@ - flash.each do |type, message| .row .col-md-12 - %div{class: "alert alert-dismissable #{bootstrap_class_for(type)}", id: "flash"} - .button.close{"data-dismiss" => "alert", "aria-hidden"=>"true"} + .alert.alert-dismissable#flash{class: bootstrap_class_for(type)} + .button.close{"data-dismiss": "alert", "aria-hidden": "true"} × %p = message diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index fb4a049e8..a9b4ba7b1 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -1,7 +1,6 @@ -.navbar.navbar-default.navbar-fixed-top.nav-osem{role: 'navigation'} +%nav.navbar.navbar-default.navbar-fixed-top.nav-osem{role: 'navigation'} .container .navbar-header - - if controller.class.name.split("::").first=="Admin" %button{ "data-target"=>"#side-nav", "data-toggle"=>"collapse", class: 'navbar-toggle side-nav-btn', type: 'button' } %span.sr-only Toggle navigation @@ -14,62 +13,35 @@ %span.icon-bar %span.icon-bar %span.icon-bar - = nav_root_link_for conference + = nav_root_link_for(conference) .collapse.navbar-collapse#main-nav - - if content_for :splash_nav - %ul.nav.navbar-nav#splash-nav + %ul.nav.navbar-nav#splash-nav + - if content_for :splash_nav = content_for :splash_nav + = render 'layouts/snapcon_nav' -if user_signed_in? .btn-group.pull-right %ul.nav.navbar-nav.navbar-right %li.dropdown %a.dropdown-toggle{"data-toggle" => "dropdown", href: '#', id: "current-user-detail"} = current_user.name - = image_tag(current_user.gravatar_url(size: '18'), title: "Yo #{current_user.name}!", alt: '') + = image_tag(current_user.profile_picture(size: '18'), class: 'profile-thumbnail', alt: '') %b.caret %ul.dropdown-menu = render 'layouts/user_menu' - else %ul.nav.navbar-nav.navbar-right - if ENV.fetch('OSEM_ICHAIN_ENABLED', nil) == 'true' - %li{class: "#{active_nav_li(new_ichain_registration_path('user'))}"} - = link_to(new_ichain_registration_path('user')) do - %span.fa-solid.fa-heart - Sign Up + - reg_path = new_ichain_registration_path('user') - else - %li{class: "#{active_nav_li(new_registration_path('user'))}"} - = link_to(new_registration_path('user')) do - %span.fa-solid.fa-heart - Sign Up - %li.dropdown.visible-desktop - %a.dropdown-toggle{"data-toggle" => "dropdown", href: '#'} - %span.fa-solid.fa-user + - reg_path = new_registration_path('user') + %li + = link_to(reg_path) do + %span.fa-solid.fa-heart + Sign Up + %li + = link_to(sign_in_path) do + %span.fa.fa-user Sign In - %span.caret - .dropdown-menu - - if ENV.fetch('OSEM_ICHAIN_ENABLED', nil) == 'true' - = form_tag User.ichain_login_url do - = text_field_tag 'username', nil, id: 'user_ichain_email_dd', class: 'form-control', placeholder: 'Username' - = password_field_tag 'password', nil, id: 'user_ichain_password_dd', class: 'form-control', placeholder: 'Password' - %button.btn.btn-success.btn-block Sign in - - else - = form_tag new_user_session_path do - = text_field_tag 'user[login]', nil, id: 'user_login_dd', class: 'form-control', placeholder: 'Username / E-Mail' - = password_field_tag 'user[password]', nil, id: 'user_password_dd', class: 'form-control', placeholder: 'Password' - %p.text-right - %small - %label{for: 'user_remember_me'} Remember me - = check_box_tag 'user[remember_me]' - %button.btn.btn-success.btn-block Sign in - - unless omniauth_configured.empty? - .divider - %h6.text-center - or - = render 'devise/shared/openid_links' - %p.text-right - %br - %a.small.btn.btn-xs.btn-default{"data-toggle" => "collapse", "data-target" => "#navbar-devise-help"} - Need Help? - #navbar-devise-help.collapse - = render 'devise/shared/links' + .trapezoid diff --git a/app/views/layouts/_snapcon_nav.haml b/app/views/layouts/_snapcon_nav.haml new file mode 100644 index 000000000..82dc820ed --- /dev/null +++ b/app/views/layouts/_snapcon_nav.haml @@ -0,0 +1,17 @@ +%li + = link_to 'https://snap.berkeley.edu', target: '_blank' do + Snap + %em> ! +%li + = link_to 'Forum', 'https://forum.snap.berkeley.edu', target: '_blank' +%li.dropdown + %a.dropdown-toggle{ 'data-toggle': 'dropdown', href: '#', id: 'all-events' } + All Events + %b.caret + %ul.dropdown-menu + - visible_conference_links.each_with_index do |(org, confs), index| + %li.dropdown-header= org.name + - confs.sort_by(&:start_date).each do |conf| + %li= link_to(conf.title, conference_path(conf.short_title)) + - if index < visible_conference_links.length - 1 + %li.divider diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 2b2eca75a..f73e125a4 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,15 +1,14 @@ -%html{xmlns: 'http://www.w3.org/1999/html'} +%html{lang: 'en'} %head %meta{charset: 'utf-8'} - %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} + %meta{name: 'viewport', content: 'width=device-width, initial-scale=1'} %title= content_for?(:title) ? yield(:title) : ENV.fetch('OSEM_NAME', 'OSEM') - %meta{content: '', name: 'description'} - %meta{content: '', name: 'author'} + %meta{content: 'Snap!Con -- A conference all about Snap!, a programing language from UC Berkeley.', name: 'description'} + %meta{content: 'Michael Ball, Brian Harvey, Jens Moenig, Bernat Romagosa, Dan Garcia, Lauren Mock', name: 'author'} = stylesheet_link_tag "application", media: 'all' = javascript_include_tag "application" = csrf_meta_tags - = content_for(:script_head) - if ENV.fetch('OSEM_TRANSIFEX_APIKEY', nil) :javascript window.liveSettings = { @@ -21,12 +20,16 @@ = javascript_include_tag "//cdn.transifex.com/live.js" = yield(:head) + - if @conference && @conference.custom_css + %style{type: 'text/css'} + = @conference.custom_css.html_safe + %body{ class: ("conference-#{@conference.short_title}" if @conference) } = render 'layouts/navigation', conference: @conference -# Admin area - if controller.class.name.split("::").first=="Admin" = render 'layouts/admin' - -else + - else #messages .container = render 'layouts/messages' @@ -45,7 +48,7 @@ #{link_to "MIT license", "http://opensource.org/licenses/MIT"}. You can run, copy, distribute, study, change and improve it. The source code and the developers are on - #{link_to "GitHub", "https://github.com/openSUSE/osem"}. + #{link_to "GitHub", "https://github.com/snap-cloud/snapcon"}. This site is a modification of #{link_to "OSEM", "https://github.com/openSUSE/osem"}. - if ENV.fetch('SKYLIGHT_PUBLIC_DASHBOARD_URL', nil) Performance data is available on #{link_to "Skylight", ENV["SKYLIGHT_PUBLIC_DASHBOARD_URL"]}. diff --git a/app/views/mailbot/comment_template.html.erb b/app/views/mailbot/comment_template.html.erb new file mode 100644 index 000000000..b9e9cce0a --- /dev/null +++ b/app/views/mailbot/comment_template.html.erb @@ -0,0 +1,16 @@ +<%= render partial: "layouts/mailbot_header" %> +
+ + Dear <%= @user.name %>, + + User <%= @comment.user.name %> posted a new comment for event <%= @event.title %> of <%= @conference.short_title %> . + + "<%= @comment.body %>" + + To reply to this comment, please go to <%= h(admin_conference_program_event_url(@conference.short_title, @event, only_path: false)) %> + + Best wishes, + <%= @conference.title %> Team + +
+<%= render partial: "layouts/mailbot_footer" %> \ No newline at end of file diff --git a/app/views/mailbot/custom_ticket_confirmation_template.html.erb b/app/views/mailbot/custom_ticket_confirmation_template.html.erb new file mode 100644 index 000000000..d5fa91b3c --- /dev/null +++ b/app/views/mailbot/custom_ticket_confirmation_template.html.erb @@ -0,0 +1,7 @@ +<%= render partial: "layouts/mailbot_header" %> +
+ + <%= @ticket_purchase.ticket.email_body %> + +
+<%= render partial: "layouts/mailbot_footer" %> diff --git a/app/views/mailbot/custom_ticket_confirmation_template.text.erb b/app/views/mailbot/custom_ticket_confirmation_template.text.erb new file mode 100644 index 000000000..426630251 --- /dev/null +++ b/app/views/mailbot/custom_ticket_confirmation_template.text.erb @@ -0,0 +1 @@ +<%= @ticket_purchase.ticket.email_body %> \ No newline at end of file diff --git a/app/views/mailbot/email_template.html.erb b/app/views/mailbot/email_template.html.erb new file mode 100644 index 000000000..8dd249a01 --- /dev/null +++ b/app/views/mailbot/email_template.html.erb @@ -0,0 +1,7 @@ +<%= render partial: "layouts/mailbot_header" %> +
+ + <%= @email_body.html_safe %> + +
+<%= render partial: "layouts/mailbot_footer" %> diff --git a/app/views/mailbot/ticket_confirmation_template.html.erb b/app/views/mailbot/ticket_confirmation_template.html.erb new file mode 100644 index 000000000..455c0f613 --- /dev/null +++ b/app/views/mailbot/ticket_confirmation_template.html.erb @@ -0,0 +1,14 @@ +<%= render partial: "layouts/mailbot_header" %> +
+ + Dear <%= @user.name %>, + + Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. + <%= @ticket_purchase.ticket.email_body %> + Please, find the ticket(s) pdf attached. + + Best wishes, + <%= @conference.title %> Team + +
+<%= render partial: "layouts/mailbot_footer" %> diff --git a/app/views/mailbot/ticket_confirmation_template.text.erb b/app/views/mailbot/ticket_confirmation_template.text.erb index 08f5175af..7acc04625 100644 --- a/app/views/mailbot/ticket_confirmation_template.text.erb +++ b/app/views/mailbot/ticket_confirmation_template.text.erb @@ -1,7 +1,7 @@ Dear <%= @user.name %>, Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. - +<%= @ticket_purchase.ticket.email_body %> Please, find the ticket(s) pdf attached. Best wishes, diff --git a/app/views/mailbot/young_thinkers_ticket_confirmation_template.html.erb b/app/views/mailbot/young_thinkers_ticket_confirmation_template.html.erb new file mode 100644 index 000000000..d35d8a1f9 --- /dev/null +++ b/app/views/mailbot/young_thinkers_ticket_confirmation_template.html.erb @@ -0,0 +1,16 @@ +<%= render partial: "layouts/mailbot_header" %> +
+ + Dear <%= @user.name %>, + + Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. + + Please, find the ticket(s) pdf attached. + + The SAP Young Thinkers team will reach out to you with information on how to participate in the event soon. In the meantime, you can check the event page (https://events.sap.com/yt-learning-festival-at-snapcon-2020/en/home) or send an email with your questions to youngthinkers@sap.com. + + Best wishes, + <%= @conference.title %> Team + +
+<%= render partial: "layouts/mailbot_footer" %> \ No newline at end of file diff --git a/app/views/payments/_payment.html.haml b/app/views/payments/_payment.html.haml index 3fa79e6b4..a7f54170f 100644 --- a/app/views/payments/_payment.html.haml +++ b/app/views/payments/_payment.html.haml @@ -1,6 +1,6 @@ .div .col-md-12.table-responsive - %table.table.table-hover + %table.table.table-hover.table-striped %thead %tr %th Ticket @@ -19,9 +19,14 @@ %td = humanized_money_with_symbol ticket.quantity * ticket.price -= form_tag conference_payments_path do += form_tag conference_payments_path(@conference.short_title, :has_registration_ticket => @has_registration_ticket) do %script.stripe-button{ src: "https://checkout.stripe.com/checkout.js", - data: { amount: @total_amount_to_pay.cents, label: "Pay #{humanized_money_with_symbol @total_amount_to_pay}", - email: current_user.email, currency: @total_amount_to_pay.currency, name: ENV.fetch('OSEM_NAME', 'OSEM'), - description: "book your tickets", key: Rails.application.secrets.stripe_publishable_key, locale: "auto"}} + data: { amount: @total_amount_to_pay.cents, + label: "Pay #{humanized_money_with_symbol @total_amount_to_pay}", + email: current_user.email, + currency: @total_amount_to_pay.currency, + name: ENV.fetch('OSEM_NAME', 'OSEM'), + description: "#{@conference.title} tickets", + key: ENV['STRIPE_PUBLISHABLE_KEY'] || Rails.application.secrets.stripe_publishable_key, + locale: "auto"}} = link_to 'Edit Purchase', conference_tickets_path(@conference.short_title), class: 'btn btn-default' diff --git a/app/views/payments/new.html.haml b/app/views/payments/new.html.haml index 0401f7851..e03c0bd62 100644 --- a/app/views/payments/new.html.haml +++ b/app/views/payments/new.html.haml @@ -2,12 +2,18 @@ .row .col-xs-6.col-xs-offset-3 %h1 - Payment Summary : + Payment Summary : = humanized_money_with_symbol @total_amount_to_pay .col-xs-8.col-xs-offset-2.well = render partial: 'payment' .row .col-md-13 + %p.text-center + %strong + If you do not have a credit card, please reach out to us at + = mail_to(@conference.contact.email) + %hr + %p.text-muted.text-center %small All payments are handled securely by our payment processor, diff --git a/app/views/physical_tickets/index.html.haml b/app/views/physical_tickets/index.html.haml index 6999c606a..26cbfda05 100644 --- a/app/views/physical_tickets/index.html.haml +++ b/app/views/physical_tickets/index.html.haml @@ -9,6 +9,18 @@ .text-muted Your tickets for the conference + -# TODO: And if they have a registration ticket? + - if !@conference.user_registered?(@user) && @has_registration_ticket + .col-md-12 + .alert.alert-success{ role: 'alert' } + = link_to 'Complete Registration', + new_conference_conference_registration_path(@conference), + class: 'btn btn-info pull-right btn-lg' + %h3 + 🎉 Thanks for getting a #{@conference.title} ticket! One last step... + %strong + You are not yet registered for the conference. + .col-md-12 - if @physical_tickets.present? %table.table.table-bordered.table-striped.table-hover#roles @@ -17,6 +29,7 @@ %th ID %th Type %th User + %th Registration? %th Actions %tbody - @physical_tickets.each do |physical_ticket| @@ -24,6 +37,7 @@ %td= physical_ticket.id %td= physical_ticket.ticket.title %td= physical_ticket.user.name + %td= physical_ticket.ticket.registration_ticket? ? 'Yes' : 'No' %td .btn-group = link_to 'Show', diff --git a/app/views/proposals/_commercial_form_fields.html.haml b/app/views/proposals/_commercial_form_fields.html.haml index 6f9b7ea0c..74b28d854 100644 --- a/app/views/proposals/_commercial_form_fields.html.haml +++ b/app/views/proposals/_commercial_form_fields.html.haml @@ -1,17 +1,20 @@ .form-group - = f.label :url + = f.label :title + = f.text_field :title, class: 'form-control' +.form-group + = f.label :url, 'URL' %abbr{title: 'This field is required'} * = f.text_field :url, required: 'required', class: 'form-control' %span.help-block - Just paste the url of your video/photo provider. Currently supported: YouTube, Vimeo, SpeakerDeck, SlideShare, Instagram, Flickr. + Just paste the url of your video/photo provider. Anything that supports an iframe is allowed. .form-group - if f.object.new_record? - = f.submit nil, class: 'btn btn-primary pull-right', id: 'commercial_submit_action', disabled: true + = f.submit 'Create Materials', class: 'btn btn-primary pull-right', id: 'commercial_submit_action', disabled: true - else - = f.submit nil, class: 'btn btn-primary pull-right' + = f.submit 'Update Materials', class: 'btn btn-primary pull-right' - if can? :destroy, commercial = link_to('Delete', conference_program_proposal_commercial_path(@conference.short_title, @event.id, commercial.id), - :method => :delete, :data => { :confirm => 'Are you sure?' }, class: 'btn btn-danger') + method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger') - if f.object.versions.any? .text-right added by diff --git a/app/views/proposals/_encouragement_text.html.haml b/app/views/proposals/_encouragement_text.html.haml index ad9f01ea8..f7f6033f0 100644 --- a/app/views/proposals/_encouragement_text.html.haml +++ b/app/views/proposals/_encouragement_text.html.haml @@ -7,7 +7,7 @@ = "#{pluralize(@program.tracks.confirmed.cfp_active.count, 'track')}:" = "#{tracks(@conference)}." - if @program.cfp_open? - The submission period has begun + The submission period is open from %em.notranslate = @program.cfp.start_date.strftime('%A, %B %-d. %Y') and closes @@ -16,9 +16,8 @@ That means you have %b.notranslate= pluralize(@program.cfp.remaining_days, 'day') left! - Remember - %span.notranslate - = @conference.title - will only be as good as the content you present. Submit early, submit often! - -else + %span.notranslate= @conference.title + will only be as good as the content you present. + %br Submit early, submit often! + - else The submission period is closed now. diff --git a/app/views/proposals/_form.html.haml b/app/views/proposals/_form.html.haml index 928d4a9c4..20a612b34 100644 --- a/app/views/proposals/_form.html.haml +++ b/app/views/proposals/_form.html.haml @@ -1,94 +1,101 @@ -:ruby - action_is_edit = @event.persisted? - user_is_admin = current_user&.is_admin? - display_details = user_is_admin || action_is_edit - display_registration = user_is_admin || @program.cfp&.enable_registrations? - -%h4 - Proposal Information +%h4 Proposal Information %hr + .form-group = f.label :title %abbr{title: 'This field is required'} * = f.text_field :title, autofocus: true, required: true, class: 'form-control' -- if display_details + +.form-group + = f.label :subtitle + = f.text_field :subtitle, class: 'form-control' +.form-group + = f.label :presentation_mode + = f.select :presentation_mode, options_for_select(Event.display_presentation_modes, @event.presentation_mode), { include_blank: true }, { class: 'select-help-toggle form-control' } + +.form-group + = f.label :speaker_ids, 'Speakers' + = f.select :speaker_ids, user_options_for_dropdown(@event, :speakers), {}, { multiple: true, class: "select-help-toggle js-userSelector form-control", placeholder: "Select speakers..." } + .help-block The people responsible for the event. You can only select existing users. + += committee_only_actions(current_user, @conference) do .form-group - = f.label :subtitle - = f.text_field :subtitle, class: 'form-control' + = f.label :volunteer_ids, 'Volunteers' + = f.select :volunteer_ids, user_options_for_dropdown(@event, :volunteers), {}, { multiple: true, class: "select-help-toggle js-userSelector form-control", placeholder: "Select volunteers..." } + .help-block Assign volunteers to help run this session. .form-group - %label{for: 'users_selectize-selectized'} Speakers - = f.select :speaker_ids, f.object.speakers.pluck(:username, :id), {}, { multiple: true, class: "form-control", id: "users_selectize", placeholder: "Speakers" } - %span.help-block - The people responsible for the event, beside you. You can only select existing users. - - if @program.tracks.confirmed.cfp_active.any? - .form-group - = f.label :track_id, 'Track' - = f.select :track_id, @program.tracks.confirmed.cfp_active.pluck(:name, :id), { include_blank: '(Please select)' }, { class: 'form-control select-help-toggle' } - = render 'shared/select_help_text', f: f, for: :track_id, include_blank: true, options: @program.tracks.confirmed.cfp_active do |track| - = markdown(track.description) -.form-group - = f.label :event_type_id, 'Type' - = f.select :event_type_id, event_type_select_options(@conference.program.event_types), { include_blank: false }, { class: 'select-help-toggle form-control' } + = f.label "Is this is parent event?", for: :superevent + = f.check_box :superevent, class: 'switch-checkbox' + .help-block + A parent event can contain subevents. Enable this for the primary lightning talk + session, poster sessions, etc. + .form-group + = f.label :parent_id, 'Selet a Parent Event' + = f.select :parent_id, @superevents.map { |e| [e.title, e.id] }, {include_blank: 'Select a Parent Event'}, {class: 'select-help-toggle form-control'} + .help-block + Designate a parent event so that this event appears scheduled "inside" + the parent event on the schedule. + .form-group + = f.label :committee_review, 'Committee Feedback' + = f.text_area :committee_review, rows: 5, data: { provide: 'markdown' } + .help-block= markdown_hint('This field is shared with the submission authors.') + .form-group + = f.label :is_highlight + = f.check_box :is_highlight, class: 'switch-checkbox' + .help-block This shows the event on the conference homepage. + + +- if @program.tracks.confirmed.cfp_active.any? + .form-group + = f.label :track_id, 'Track' + = f.select :track_id, @program.tracks.confirmed.cfp_active.pluck(:name, :id), { include_blank: '(Please select)' }, { class: 'form-control' } + - @program.tracks.confirmed.cfp_active.each do |track| + .help-block.select-help-text.track-description.collapse{ id: "#{track.id}-help" } + = track.description + + - if @program.languages.present? .form-group - = f.label :language - = f.select :language, @languages, { include_blank: false}, { class: 'select-help-toggle form-control' } -= render 'shared/select_help_text', f: f, for: :event_type_id, options: @conference.program.event_types do |event_type| - = event_type.description -- if display_details - - if @conference.program.difficulty_levels.any? + = f.label :language + = f.select :language, @languages, { include_blank: false}, { class: 'select-help-toggle form-control' } + +- if @conference.program.difficulty_levels.any? + .form-group = f.label :difficulty_level = f.select :difficulty_level_id, @conference.program.difficulty_levels.map{|level| [level.title, level.id ] }, {include_blank: false}, { class: 'select-help-toggle form-control' } - = render 'shared/select_help_text', f: f, for: :difficulty_level_id, options: @conference.program.difficulty_levels do |difficulty_level| - = difficulty_level.description + - @conference.program.difficulty_levels.each do |difficulty_level| + .help-block.select-help-text.collapse{ id: "#{difficulty_level.id}-help" } + = difficulty_level.description + +- if @event.committee_review.present? + %br + %strong.control-label Committee Feedback + %small Use this feedback to improve your submission. + .well= markdown(@event.committee_review) + += render 'proposals/submission_type_content_form', f: f, program: @program + +- if @program.cfp&.enable_registrations? + %h4 Event Registration + %hr + .checkbox + %label + = f.check_box :require_registration + Require participants to register to your event + .form-group + = f.number_field :max_attendees + .help-block + The maximum number of participants. + = @event.room ? "Value must be between 1 and #{@event.room.size}" : 'Check room capacity after scheduling.' + .form-group - = f.label :abstract - = f.text_area :abstract, required: true, rows: 5, data: { provide: 'markdown' }, class: 'form-control' - %span.help-block - = markdown_hint('[Tips to improve your presentations.](http://blog.hubspot.com/blog/tabid/6307/bid/5975/10-Rules-to-Instantly-Improve-Your-Presentations.aspx)') - %p - You have used - %span#abstract-count = @event.abstract_word_count - words. Abstracts must be between - %span#abstract-minimum-word-count - 0 - and - %span#abstract-maximum-word-count - 250 - words. - - if display_registration && display_details - %h4 - Event Registration - %hr - - if display_registration - .checkbox - %label - = f.check_box :require_registration - Require participants to register to your event - - if display_registration && display_details - .form-group - = f.label :max_attendees - = f.number_field :max_attendees, class: 'form-control' - %span.help-block - - message = @event.room ? "Value must be between 1 and #{@event.room.size}" : 'Check room capacity after scheduling.' - = 'The maximum number of participants. ' + message - - if display_details && current_user.has_any_role?(:admin, { name: :organizer, resource: @conference }, { name: :cfp, resource: @conference }) - .checkbox - %label - = f.check_box :is_highlight - Is a highlight? - %p.text-right - = link_to '#description', 'data-toggle' => 'collapse' do - Do you require something special for your event? - #description{ class: "collapse #{ 'in' if @event.description.present? }" } - .form-group - = f.label :description, 'Requirements' - = f.text_area :description, rows: 5 - %span.help-block - Eg. Whiteboard, printer, or something like that. - %p.text-right - - submit_copy = action_is_edit ? 'Update Proposal' : 'Create Proposal' - = f.submit submit_copy, class: 'btn btn-success' + = f.label :description, 'Requirements and Scheduling' + %p Please include any scheduling constraints. + = f.text_area :description, rows: 5, class: 'form-control' + .help-block e.g. Are you only attending certain days? + +%p.text-right + = f.submit @event.persisted? ? 'Update Proposal' : 'Create Proposal', class: 'btn btn-success' - content_for :script_head do :javascript diff --git a/app/views/proposals/_speaker_info.haml b/app/views/proposals/_speaker_info.haml new file mode 100644 index 000000000..4e7dc4c92 --- /dev/null +++ b/app/views/proposals/_speaker_info.haml @@ -0,0 +1,19 @@ +.speakerinfo + .row + .col-md-4 + = image_tag speaker.profile_picture(size: 120), class: 'img-responsive img-rounded' + .col-md-8 + %h4 + = link_to speaker.name, user_path(speaker.id) + %br + - if speaker.email_public? + = mail_to speaker.email.to_s do + %i.fa-solid.fa-envelope.fa-2x + - if speaker.affiliation? + .text-muted + from + = speaker.affiliation + - if (defined?(show_bio) && show_bio) && speaker.biography? + .row.speakerbio + .col-md-12 + = markdown(speaker.biography) diff --git a/app/views/proposals/_submission_type_content_form.haml b/app/views/proposals/_submission_type_content_form.haml new file mode 100644 index 000000000..6a66fa5a1 --- /dev/null +++ b/app/views/proposals/_submission_type_content_form.haml @@ -0,0 +1,57 @@ +%h2 Submission Type and Details +%p Please select a submission type, then fill in the abstract and extended details. + +.form-group + = f.label :event_type_id, 'Type' + = f.select :event_type_id, event_type_select_options(@conference.program.event_types), { include_blank: false }, { class: 'select-help-toggle form-control' } + +- program.event_types.each do |event_type| + .help-block.event_event_type_id.collapse{ id: "#{dom_id(event_type)}-help" } + %strong Description + = markdown(event_type.description) + +%h3 Submission Abstract +%p + The abstract is reviewed by the committee and included in the conference program. + You are encouraged to include links or other references as appropriate. + += f.label :abstract, class: 'sr-only' += f.text_area :abstract, required: true, label: nil, rows: 5, data: { provide: 'markdown' }, class: 'form-control md-input' +.help-block= markdown_hint + +%p + You have used + %span#abstract-count 0 + words. Abstracts must be between + %span#abstract-minimum-word-count 0 + and + %span#abstract-maximum-word-count 250 + words. + +%h3 Extended Information +%p This part of the submission is intended only for the conference committee. + +- program.event_types.each do |event_type| + .help-block.select-help-text.event_event_type_id.collapse{ id: "#{dom_id(event_type)}-instructions" } + - if event_type.submission_template.blank? + %p + Use this space to include any additional inforrmation that is helpful in reviewing your + submission. + - else + %p + Please use the following as the template for your submission. This will help the conference + committee review your submission with all the details they need. + .panel.panel-primary + .panel-heading= "#{event_type.name} Template" + .panel-body= markdown(event_type.submission_template) + .panel-footer + %button.btn.btn-warning.btn-xs.js-resetSubmissionText{ type: 'button', + data: { confirm: 'Do you really want to reset your submission text to the provided template?' } } + Reset Submission to Template + %span.small You may want to use this if you have changed the submission type. + +%hr +.form-group + = f.label :submission_text, class: 'sr-only' + = f.text_area :submission_text, rows: 10, data: { provide: 'markdown' }, class: 'form-control md-input' + .help-block= markdown_hint diff --git a/app/views/proposals/_toggle_favorite_event.haml b/app/views/proposals/_toggle_favorite_event.haml new file mode 100644 index 000000000..8d0a6cf94 --- /dev/null +++ b/app/views/proposals/_toggle_favorite_event.haml @@ -0,0 +1,8 @@ +%span.js-toggleEvent{ style: 'padding: 8px;' } + = link_to('#', onClick: 'starClicked();', + 'aria-label': "#{is_favourite ? 'un' : ''}favorite event #{event.title}") do + %i.fa-star.fa-2x{ style: "color: #{color}", + id: "eventFavourite-#{event.id}", + class: is_favourite ? 'fa-solid' : 'fa-regular', + 'aria-hidden': 'true', + 'data-url': toggle_favorite_conference_program_proposal_path(conference.short_title, event.id) } diff --git a/app/views/proposals/_volunteers_table.haml b/app/views/proposals/_volunteers_table.haml new file mode 100644 index 000000000..c842c1de1 --- /dev/null +++ b/app/views/proposals/_volunteers_table.haml @@ -0,0 +1,20 @@ +%table.table.table-striped#events + - events.each do |event| + %tr + %td.col-md-7{ style: 'padding:20px 8px 20px 8px;' } + = link_to event.title, conference_program_proposal_path(conference.short_title, event.id) + %br + %small.text-muted + = event.event_type.title + = "(#{event.event_type.length} min)" + = "in #{event.track.name}" if event.track + - if event.require_registration + %br + = link_to registered_text(event), registrations_conference_program_proposal_path(conference.short_title, event), + class: 'btn btn-xs btn-danger' + + %td.col-md-2{ style: 'padding:20px 8px 20px 8px;' } + - event_schedule = event.event_schedules.find_by(schedule_id: program.selected_schedule_id) + - if event_schedule.present? + = inyourtz(event_schedule.start_time, conference.timezone) do + = event_schedule.start_time.strftime('%Y %B %e - %H:%M') diff --git a/app/views/proposals/index.html.haml b/app/views/proposals/index.html.haml index cf57c6444..41a17e7da 100644 --- a/app/views/proposals/index.html.haml +++ b/app/views/proposals/index.html.haml @@ -33,10 +33,10 @@ .row .col-md-12 - %p.text-right - = link_to '#status-help', class: 'btn btn-default', "data-toggle"=>"collapse" do + -# %p.text-right + = link_to '#status-help', class: 'btn btn-default', "data-toggle": "collapse" do Help? - .collapse#status-help + -# .collapse#status-help %p %strong What happens next with my proposal? @@ -67,7 +67,7 @@ - @events.each do |event| %tr %td{style: "padding:20px 8px 20px 8px;"} - %span{ title: event.state.humanize, class: "fa #{status_icon(event)}" } + %span{ title: event.state.humanize, class: "fa-solid #{status_icon(event)}" } %td.col-md-7{style: "padding:20px 8px 20px 8px;"} = link_to event.title, conference_program_proposal_path(@conference.short_title, event.id) @@ -80,8 +80,11 @@ %br = link_to registered_text(event), registrations_conference_program_proposal_path(@conference.short_title, event), class: 'btn btn-xs btn-danger' - %td.col-md-2{style: "padding:20px 8px 20px 8px;"} - = link_to 'Complete your proposal', 'javascript: void(0)', "type"=>"button", "data-trigger"=>"focus", "data-toggle"=>"popover", "title"=>"Your todo list", "data-content"=>"#{render partial: 'tooltip', locals: { event: event} }" + -# %td.col-md-2{style: "padding:20px 8px 20px 8px;"} + %a{href: '#', type: "button", "data-trigger": "click focus", "data-toggle": "popover", + title: "Your todo list", + "data-content": "#{render 'tooltip', event: event}" } + Complete your proposal - if can? :create, @conference.registrations.new - progress_percentage = event.calculate_progress .progress @@ -113,6 +116,16 @@ method: :patch, class: 'btn btn-mini btn-success', id: "review_event_#{event.id}" = link_to 'Edit', edit_conference_program_proposal_path(@conference.short_title, event.id), class: 'btn btn-default', id: "edit_proposal_#{event.id}" + + - if @volunteer_events.any? + .row + .col-md-12 + %h2 + Volunteer Duties + %small + Thanks for being a host at #{@conference.title} + = render 'volunteers_table', events: @volunteer_events, conference: @conference, program: @program + .row .col-md-12 - if can? :create, @event diff --git a/app/views/proposals/new.html.haml b/app/views/proposals/new.html.haml index 76e2860a0..8b68eeceb 100644 --- a/app/views/proposals/new.html.haml +++ b/app/views/proposals/new.html.haml @@ -2,12 +2,13 @@ .row .col-md-12 .page-header - %h1 New Proposal + %h1 New #{@conference.title} Proposal - if @program.cfp_open? - if @program.cfp.description.present? + %h2 Call for Proposals .row .col-md-12 - = markdown(@program.cfp.description) + = markdown(@program.cfp.description, escape_html=false) .row .col-md-12 = render partial: 'encouragement_text' @@ -16,16 +17,17 @@ - unless current_user %legend %span - =link_to('#signup', role: 'tab', 'aria-controls' => 'home', 'data-toggle' => 'tab') do - = ENV.fetch('OSEM_NAME', 'OSEM') - Account + = link_to('#signup', role: 'tab', 'aria-controls' => 'home', 'data-toggle' => 'tab') do + Create a Snap!Con Account + %span.pull-right#account-already - =link_to('#signin', role: 'tab', 'aria-controls' => 'home', 'data-toggle' => 'tab') do - Already have an account? + = link_to('#signin', role: 'tab', 'aria-controls' => 'home', 'data-toggle' => 'tab') do + Already have a Snap!Con account? .tab-content .tab-pane.active{role: 'tabpanel', id: 'signup'} = form_for(@event, url: @url) do |f| - = render partial: 'devise/registrations/new_embedded' - = render partial: 'form', locals: { f: f } + = render 'devise/registrations/new_embedded' + = render 'form', f: f + = render 'shared/user_selectize' .tab-pane{role: 'tabpanel', id: 'signin'} - = render partial: 'devise/sessions/new_embedded' + = render 'devise/sessions/new_embedded' diff --git a/app/views/proposals/show.html.haml b/app/views/proposals/show.html.haml index fa2e2f107..d1f32975c 100644 --- a/app/views/proposals/show.html.haml +++ b/app/views/proposals/show.html.haml @@ -4,127 +4,202 @@ %meta{ property: "og:description", content: @event.abstract } %meta{ property: "og:site_name", content: ENV.fetch('OSEM_NAME', 'OSEM') } - if @speakers_ordered.any? - %meta{ property: "og:image", content: @speakers_ordered.first.gravatar_url } - %meta{ property: "og:image:secure_url", content: @speakers_ordered.first.gravatar_url } + %meta{ property: "og:image", content: @speakers_ordered.first.profile_picture } + %meta{ property: "og:image:secure_url", content: @speakers_ordered.first.profile_picture } + +- provide :title, "#{@event.title} #{@conference.title}" + += content_for :splash_nav do + %li + = link_to('Schedule', events_conference_schedule_path(@conference)) .container - .row - .col-md-12.page-header - %h2 - = @event.title - - if @event.subtitle - %br - %small - = @event.subtitle - .btn-group.pull-right - - if can? :update, @event - = link_to 'Registrations', registrations_conference_program_proposal_path(@conference.short_title, @event), class: 'btn btn-mini btn-success' - - if can? :edit, @event - = link_to "Edit", edit_conference_program_proposal_path(@conference.short_title, @event), class: 'btn btn-mini btn-primary' - - if can? :schedule, @conference - = link_to "Schedule", conference_schedule_path(@conference.short_title), class: 'btn btn-success' + .row.page-header + .col-md-10{ style: 'display: flex; flex-direction: row;' } + = render 'proposals/toggle_favorite_event', + event: @event, color: '#000000', conference: @conference, + is_favourite: event_favourited?(@event, current_user) + + .div{ style: 'margin: 3px; flex: 1' } + %h2{ style: 'margin: 0' } + = @event.title + - if @event.subtitle.present? + %br + %small + = @event.subtitle + .div{ style: 'margin: 3px; flex: 1' } + - if @event.event_type.present? + - color = css_background_color(@event.event_type&.color || '#f5f5f5') + %span.h3 + .label{ style: "#{color} margin: 4px; display: inline-block"} + = @event.event_type.title + = join_event_link(@event, @event_schedule, current_user) + + .btn-group + - if can?(:update, @event) && @event.require_registration? + = link_to 'Registrations', registrations_conference_program_proposal_path(@conference.short_title, @event), class: 'btn btn-success' + - if can? :schedule, @conference + = link_to "Schedule", conference_schedule_path(@conference.short_title), class: 'btn btn-success' + - if can?(:edit, @event) + = link_to "Edit", edit_conference_program_proposal_path(@conference.short_title, @event), class: 'btn btn-primary' .row .col-md-3 - %h3 - if @event.speakers.any? - Presented by: + %h3 + Presented by: - @speakers_ordered.each do |speaker| - .speakerinfo - .row - .col-md-4 - = image_tag speaker.gravatar_url(:size => 120), class: 'img-responsive img-rounded' - .col-md-8 - %h4 - = link_to speaker.name, user_path(speaker.id) - %br - - if speaker.email_public? - = mail_to "#{ speaker.email }" do - %i.fa-solid.fa-envelope.fa-2x - - if speaker.affiliation? - .text-muted - from - = speaker.affiliation - -if speaker.biography? - .row.speakerbio - .col-md-12 - = markdown(speaker.biography) - .col-md-9 - .row - .col-md-12 - .lead - = canceled_replacement_event_label(@event, @event_schedule) - = replacement_event_notice(@event_schedule) + = render 'speaker_info', speaker: speaker, show_bio: true, cache: true - - if @event.commercials.empty? - %h5.text-warning - No video of the event yet, sorry! - - unless @conference.commercials.empty? - Meanwhile... - - unless @conference.commercials.empty? - = render partial: 'shared/media_items', locals: { commercials: @conference.commercials } - - else - %p - = render partial: 'shared/media_items', locals: { commercials: @event.commercials } - .row - .col-md-12 - %p - = markdown(@event.abstract) - %dl#proposal-info - .col-md-12 - %dt Date: - %dd= @event_schedule.start_time.strftime("%Y %B %e - %H:%M") if @event_schedule - .col-md-12 - %dt Duration: - %dd= show_time(@event.event_type.length) - .col-md-12 - %dt Room: - %dd - - if @event.room - = @event.room.name - .col-md-12 - %dt Conference: - %dd= link_to @event.program.conference.title, conference_path(@conference) - .col-md-12 - %dt Language: - %dd= @event.language if @event.language - .col-md-12 - %dt Track: - %dd - - if @event.track - %span.label{style: "background-color: #{@event.track.color}; color: #{ contrast_color(@event.track.color) }"} - = @event.track.name + - if @event.volunteers.any? + %h3 + Volunteer Hosts + %br + %small Thanks for helping with #{@conference.title}! + - @event.volunteers.each do |volunteer| + = render 'speaker_info', speaker: volunteer, show_bio: false + + %section{class: @happening_now ? 'col-md-6' : 'col-md-9'} + - cache [@conference, @event, @event_schedule, '#events#main-panel'] do + .row .col-md-12 - %dt Difficulty: - %dd - - if @event.difficulty_level - %span.label{style: "background-color: #{@event.difficulty_level.color}; color: #{ contrast_color(@event.difficulty_level.color) };"} - = @event.difficulty_level.title + .lead + = canceled_replacement_event_label(@event, @event_schedule) + = replacement_event_notice(@event_schedule) - - if @event.require_registration + - if @event.commercials.empty? + %h5.text-warning + No materials for the event yet, sorry! + - unless @conference.commercials.empty? + Meanwhile... + - unless @conference.commercials.empty? + = render 'shared/media_items', commercials: @conference.commercials, cache: true + - else + %p + = render 'shared/media_items', commercials: @event.commercials + .row + .col-md-12 + = markdown(@event.abstract) + - if @event.superevent && @event.subevents.present? + .col-md-12 + %h3 This session will feature: + - @event.program_subevents.each do |subevent| + .col-xs-12.col-md-10 + - subevent_schedule = subevent.event_schedules.find_by(schedule_id: @program.selected_schedule_id) + - happening_now = @event_schedule&.happening_now? || 'unscheduled' + - cache [subevent_schedule, subevent, current_user, happening_now, '#scheduled#full#panel'] do + = render 'schedules/event_mini', event: subevent, event_schedule: subevent_schedule + %dl#proposal-info + .col-md-12 + %dt Date: + %dd + - if @event_schedule.present? + - tz_object = current_user&.timezone ? current_user : @conference + - start_time = convert_timezone(@event_schedule.start_time, @conference.timezone, tz_object.timezone) + = inyourtz(@event_schedule.start_time, @conference.timezone) do + = start_time.strftime('%Y %B %e - %H:%M') + ' ' + timezone_text(tz_object) + - else + Unscheduled + .col-md-12 + %dt Duration: + %dd= show_time(@event.event_type.length) .col-md-12 - %dt Requires Registration: + %dt Room: %dd - = link_to "Yes (#{registered_text(@event)})", new_conference_conference_registration_path(@conference.short_title), class: 'btn btn-xs btn-danger', disabled: !@event.registration_possible? - - if concurrent_events(@event).present? + = @event.room&.name + .col-md-12 + %dt Conference: + %dd= link_to @event.program.conference.title, conference_path(@conference) + - if @event.language + .col-md-12 + %dt Language: + %dd= @event.language + - if @event.event_type + .col-md-12 + %dt Type: + %dd= @event.event_type&.title + - if @event.presentation_mode + .col-md-12 + %dt Presented via: + %dd + %span.fa-solid.fa-person-chalkboard + = @event.presentation_mode.humanize + - if @event.track + .col-md-12 + %dt Track: + %dd + %span.label{style: "background-color: #{@event.track.color}; color: #{ contrast_color(@event.track.color) }"} + = @event.track.name + - if @event.difficulty_level + .col-md-12 + %dt Difficulty: + %dd + %span.label{style: "background-color: #{@event.difficulty_level.color}; color: #{ contrast_color(@event.difficulty_level.color) };"} + = @event.difficulty_level.title + + - if @event.require_registration + .col-md-12 + %dt Requires Registration: + %dd + = link_to "Yes (#{registered_text(@event)})", new_conference_conference_registration_path(@conference.short_title), class: 'btn btn-xs btn-danger', disabled: !@event.registration_possible? + + - if @event.parent_event.present? + .col-md-12 + %h3 This session is a part of: + = render 'schedules/event_mini', event: @event.parent_event, event_schedule: @event_schedules + + -# TODO-SNAPCON: This is currently disabled due to performance. + - concurrent = [] # concurrent_events(@event) + - if concurrent.present? .col-md-12 %hr %h4 Happening at the same time: %ol - - concurrent_events(@event).each do |event| + - concurrent.each do |event| %li = link_to conference_program_proposal_path(@conference.short_title, event.id) do = event.title %dl %dt Start Time: %dd - = event.program.selected_event_schedules.find { |es| es.event == event }.start_time.strftime("%Y %B %e %H:%M") + - event_schedule = event.program.selected_event_schedules.find { |es| es.event == event } + = inyourtz(event_schedule.start_time, @conference.timezone) do + = event_schedule.start_time.strftime("%Y %B %e %H:%M") %br %dt Room: - %dd - = event.room.name + %dd= event.room&.name + .row .col-md-12 - if @surveys_after_event.any? && @event.ended? .page-header = render partial: 'surveys/list', locals: { surveys: @surveys_after_event, conference: @conference } + + - if @happening_now + .col-md-3 + #happening-now + = render 'conferences/happening_now', conference: @conference, + events_schedules: @events_schedules, pagy: @pagy, + events_schedules_length: @events_schedules_length, + events_schedules_limit: @events_schedules_limit, + is_happening_next: @is_happening_next + + / TODO: Cache this with current_user or at least can edit + - if @event.committee_review.present? || @event.submission_text.present? && can?(:edit, @event) + %hr + %p Information for event authors only: + .panel.panel-info + .panel-heading + Committee Feedback + %button.btn.btn-primary.btn-xs{ type: "button", 'data-toggle': "collapse", 'data-target': "#committee-panel", 'aria-expanded': "false", 'aria-controls': "committee-panel" } + Toggle + .panel-body#committee-panel{ class: @event.committee_review.present? ? 'collapse' : '' } + = markdown(@event.committee_review) + + .panel.panel-info + .panel-heading + Submission Details + %button.btn.btn-primary.btn-xs{ type: "button", 'data-toggle': "collapse", 'data-target': "#submission-panel", 'aria-expanded': "false", 'aria-controls': "submission-panel" } + Toggle + .panel-body.collapse#submission-panel + = markdown(@event.submission_text) diff --git a/app/views/proposals/show.js.erb b/app/views/proposals/show.js.erb new file mode 100644 index 000000000..53220cb89 --- /dev/null +++ b/app/views/proposals/show.js.erb @@ -0,0 +1,6 @@ +$('#happening-now').html("<%= j(render 'conferences/happening_now', conference: @conference, + events_schedules: @events_schedules, pagy: @pagy, + events_schedules_length: @events_schedules_length, + events_schedules_limit: @events_schedules_limit, + is_happening_next: @is_happening_next) %>"); +Pagy.init(document.getElementById('happening-now')); diff --git a/app/views/rooms/live_session.html.haml b/app/views/rooms/live_session.html.haml new file mode 100644 index 000000000..db4a0ba15 --- /dev/null +++ b/app/views/rooms/live_session.html.haml @@ -0,0 +1,17 @@ += content_for :splash_nav do + %li + = link_to('< Back', :back) + %li + = link_to('Schedule', events_conference_schedule_path(@conference)) + +- content_for :head do + :css + body { padding-top: 50px; margin-bottom: 0; } + .navbar { margin-bottom: 0; } + #content { padding-bottom: 0; } + #footer { display: none; } + +%div{ style: 'display: flex; flex: 1 1 auto; height: 100%' } + %iframe{ src: @room.embed_url, with: '67%', style: 'flex-grow: 1' } + - if @room.discussion_url.present? + %iframe{ src: @room.discussion_url, width: '33%' } diff --git a/app/views/schedules/_carousel.html.haml b/app/views/schedules/_carousel.html.haml deleted file mode 100644 index 785a0681d..000000000 --- a/app/views/schedules/_carousel.html.haml +++ /dev/null @@ -1,63 +0,0 @@ -- interval_count = hrs_per_slide * 60 / @conference.program.schedule_interval + 1 -- width = 85 / interval_count -- carousel_number = (@conf_period / hrs_per_slide.to_f).ceil -.carousel.slide{ id: "carousel-#{ date }-#{ hrs_per_slide }", | - "data-ride" => "carousel", | - "data-wrap" => "false", | - "data-interval" => "false" } - / Wrapper for slides - .carousel-inner - - start_time = DateTime.parse("#{date} #{@conf_start}:00") - - (0..carousel_number-1).each do |number| - %div{ class: "#{ carousel_item_class(number, carousel_number, hrs_per_slide, @hour_column)}" } - %table.table.table-bordered.schedule-table#schedule - %tr - %th - - td_start_time = start_time - - interval_count.times do - %th.date - %span - = (td_start_time).strftime("%H:") - %span - = (td_start_time).strftime("%M") - - td_start_time += @step_minutes - - start_room_time = start_time - - @rooms.each do |room| - - start_room_time = start_time - - span = 1 - %tr - %td.room{ style: "height: #{ td_height(@rooms) }px;" } - .room.elipsis.break-words{ style: "-webkit-line-clamp: #{ room_lines(@rooms) }; height: #{ room_height(@rooms) }px;" } - = room.name - - - event_schedules = (@event_schedules_by_room_id[room.id] || []).select{ |e| (e.end_time > start_time) && (e.start_time <= (start_time + hrs_per_slide.hour)) } - - - interval_count.times do |offset| - - if span > 1 - - span -= 1 - - else - - event_schedule = event_schedules.find{ |e| e.start_time <= start_room_time and e.end_time > start_room_time } - - if event_schedule && (event_schedule.event.state == 'canceled' || event_schedule.event.state == 'withdrawn') && event_schedule.intersecting_event_schedules.confirmed.exists? - - replacement_event = event_schedule.intersecting_event_schedules.confirmed.first - - event_schedule = (replacement_event.start_time <= start_room_time && replacement_event.end_time > start_room_time) ? replacement_event : nil - - - if event_schedule - -# There is an event, calculate the span and show it - - event_span = (event_schedule.end_time.to_i - start_room_time.to_i) / 60 / @conference.program.schedule_interval - - span = ((event_span + offset) > interval_count ? interval_count - offset : event_span) - = render partial: 'schedule_item', locals: {event: event_schedule.event, event_schedule: event_schedule, span: span, width: width} - - else - -# if span equals 1 show an empty td - %td.no-padding{ width: "#{ width }%"} - - start_room_time += @step_minutes - - start_time = start_room_time - @step_minutes - - / Controls - %a.left.carousel-control{ href: "#carousel-#{ date }-#{ hrs_per_slide }", | - role: "button", | - "data-slide" => "prev" } - %span.fa-solid.fa-chevron-left - %a.right.carousel-control{ href: "#carousel-#{ date }-#{ hrs_per_slide }", | - role: "button", | - "data-slide" => "next" } - %span.fa-solid.fa-chevron-right diff --git a/app/views/schedules/_date_event_types.haml b/app/views/schedules/_date_event_types.haml new file mode 100644 index 000000000..2ba3cd4d2 --- /dev/null +++ b/app/views/schedules/_date_event_types.haml @@ -0,0 +1,16 @@ +%p.text-center + - selected_timezone = current_user&.timezone.presence || conference.timezone + All events are currently displayed in #{timezone_mapping(selected_timezone)}. + - if selected_timezone != conference.timezone + The conference timezone is #{timezone_mapping(conference.timezone)}. + - if current_user + Visit your #{link_to('user profile page', edit_user_path(current_user.id))} to set your timezone. +- if current_user + %p.text-center + Click the star next to each event to add or remove it from + = link_to 'your personal schedule.', events_conference_schedule_path(conference.short_title, favourites: true) + += render 'event_types_key', event_types: conference.program.event_types, + conference: conference, favourites: favourites +%p.text-center + Select an event type to filter the list of events. diff --git a/app/views/schedules/_event.html.haml b/app/views/schedules/_event.html.haml index 25f4e1a03..122d3e1e1 100644 --- a/app/views/schedules/_event.html.haml +++ b/app/views/schedules/_event.html.haml @@ -1,38 +1,73 @@ -.panel.panel-default.event-panel{ onClick: 'eventClicked(event, this);', "data-url" => "#{url_for(conference_program_proposal_path(@conference.short_title, event.id))}" } - .panel-body - - event.speakers_ordered.each do |speaker| - = image_tag speaker.gravatar_url, :class => "img-circle pull-right all-speaker-pic", | - :alt => speaker.name, | - :title => speaker.name | +:ruby + header_color = event.event_type&.color || '#f5f5f5' + color_style = css_background_color(header_color) + program = event.conference.program + tz_object = current_user&.timezone.present? ? current_user : event - %p - = canceled_replacement_event_label(event, event_schedule) - = replacement_event_notice(event_schedule) +.panel.panel-default + .trapezoid{ style: 'color: white; top: 12px; z-index: 100;' } + .panel-heading{ style: "#{color_style} border-radius: 4px" } + %div{ style: 'display: flex; flex-direction: row;' } + -# In the schedule view, favorited events show as false, until set by JS. (Caching perf) + = render 'proposals/toggle_favorite_event', + event: event, color: contrast_color(header_color), conference: @conference, is_favourite: false + + %h3.event-panel-title + = link_to conference_program_proposal_path(@conference.short_title, event.id), + style: color_style do + = event.title + - if event.subtitle.present? + %br + %small{style: color_style}= event.subtitle + %span + - event.speakers_ordered.each do |speaker| + = image_tag speaker.profile_picture, class: 'img-circle', alt: speaker.name + + .trapezoid{ style: "color: #{header_color}; border-top-color: #{header_color}; top: 12px;" } + + .panel-body + %div{ onClick: 'eventClicked(event, this);', 'data-url': conference_program_proposal_url(@conference.short_title, event.id) } + - if event.speakers.any? + %h4= event.speaker_names + - else + %br + - if !event.parent_event.present? && event_schedule.present? + = join_event_link(event, event_schedule, current_user) + %p + = truncate(markdown(event.abstract), length: 400, escape: false) do + %br + = link_to 'view more', conference_program_proposal_path(@conference.short_title, event.id) - %span.h3 - = event.title - %br - %small - = event.subtitle - %h4 - - if(event.speakers.any?) - presented by #{event.speaker_names} - %p - = markdown(truncate(event.abstract, length: 400)) - = link_to 'more', conference_program_proposal_path(@conference.short_title, event.id) if event.abstract.length > 400 - if event_schedule.present? - %span.track + - new_start_time = convert_timezone(event_schedule.start_time, event.timezone, tz_object.timezone) + - new_end_time = convert_timezone(event_schedule.end_time, event.timezone, tz_object.timezone) + .track %span.fa-solid.fa-clock - %span.label{ style: "background-color: grey" } - = event_schedule.start_time.strftime('%H:%M') - \- - = event_schedule.end_time.strftime('%H:%M') - %span.track + = inyourtz(event_schedule.start_time, event.timezone) do + .label.label-success + = new_start_time.strftime('%l:%M %P') + \- + = "#{new_end_time.strftime('%l:%M %P')} #{timezone_text(tz_object)}" + .track %span.fa-solid.fa-location-dot - %span.label{ style: "background-color: grey" } - = event_schedule.room.name + .label.label-info= event_schedule.room.name + - if event.presentation_mode + -# TODO: Use fa-podium pro icon + %span.fa-solid.fa-person-chalkboard + .label.label-info= event.presentation_mode.humanize + .track + .label{ style: css_background_color(event.event_type.color) }= event.event_type.name - if event.track - %span.track + .track %span.fa-solid.fa-road - %span.label{ style: "background-color: #{event.track.color}; color: #{ contrast_color(event.track.color) }" } + .label{ style: css_background_color(event.track.color) } = event.track.name + - if event_schedule.present? && event.superevent && event.subevents.present? + %br + %br + - event.program_subevents.each do |subevent| + .col-12 + / TODO-SNAPCON: REDUCE THE QUERIES + - subevent_schedule = subevent.event_schedules.find_by(schedule_id: program.selected_schedule_id) + - cache [program, subevent_schedule, subevent, current_user, event_schedule.happening_now?, '#scheduled#full#panel'] do + = render 'schedules/event_mini', event: subevent, event_schedule: subevent_schedule diff --git a/app/views/schedules/_event_mini.html.haml b/app/views/schedules/_event_mini.html.haml new file mode 100644 index 000000000..b0e29509c --- /dev/null +++ b/app/views/schedules/_event_mini.html.haml @@ -0,0 +1,32 @@ +:ruby + header_color = event.event_type&.color || '#f5f5f5' + color_style = css_background_color(header_color) + program = event.conference.program + tz_object = current_user&.timezone.present? ? current_user : event + +.panel.panel-default + .trapezoid{ style: 'color: white; top: 12px; z-index: 100;' } + .panel-heading{ style: "#{color_style} border-radius: 4px" } + %div{ style: 'display: flex; flex-direction: row;' } + -# In the schedule view, favorited events show as false, until set by JS. (Caching perf) + = render 'proposals/toggle_favorite_event', + event: event, color: contrast_color(header_color), conference: @conference, is_favourite: false + %h3.event-panel-title + = link_to(event.title, conference_program_proposal_path(@conference.short_title, event.id),style: color_style) + .trapezoid{ style: "color: #{header_color}; border-top-color: #{header_color}; top: 12px;" } + + .panel-body + %div{ onClick: 'eventClicked(event, this);', 'data-url': conference_program_proposal_url(@conference.short_title, event.id) } + - if event.speakers.any? + %h4 + = event.speaker_names + - if !event.parent_event.present? && event_schedule.present? + - new_start_time = current_user ? convert_timezone(event_schedule.start_time, event.timezone, current_user.timezone) || event_schedule.start_time : event_schedule.start_time + %span= " at #{new_start_time.strftime('%l:%M %P')}" + = join_event_link(event, event_schedule, current_user, small: true) + %p + = truncate(markdown(event.abstract), length: 250, escape: false) + - if event.subevents.present? + %ul + - event.program_subevents.each do |subevent| + %li= link_to(subevent.title, conference_program_proposal_path(@conference.short_title, subevent.id)) diff --git a/app/views/schedules/_event_types_key.haml b/app/views/schedules/_event_types_key.haml new file mode 100644 index 000000000..924d92e24 --- /dev/null +++ b/app/views/schedules/_event_types_key.haml @@ -0,0 +1,15 @@ +.row.text-center + - event_types.each_with_index do |type, index| + - if (index % 6).zero? && index != 0 + %br + %br + = link_to url_for(controller: controller_name, action: action_name, favourites: favourites, event_type: type.title), + class: 'btn btn-sm', style: css_background_color(type.color) do + - if params[:event_type] == type.title + %i.fa-solid.fa-check + #{type.title} (#{type.length} minutes) +   + - if params[:event_type] + = link_to url_for(controller: controller_name, action: action_name, favourites: favourites), class: 'btn btn-xs btn-primary' do + %i.fa-solid.fa-circle-xmark + Show all events diff --git a/app/views/schedules/_schedule_item.html.haml b/app/views/schedules/_schedule_item.html.haml deleted file mode 100644 index b6d8708ed..000000000 --- a/app/views/schedules/_schedule_item.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -%td.event{ width: "#{ width * span }%" , | - colspan: span, | - role: "button" } - %a.unstyled-link{href: url_for(conference_program_proposal_path(@conference.short_title, event.id))} - %div{ class: "elipsis break-words event-title", | - style: "-webkit-line-clamp: #{ event_lines(@rooms) }; height: #{ event_height(@rooms) }px;"} | - - = canceled_replacement_event_label(event, event_schedule, 'schedule-label') - - = event.title - - - event.speakers_ordered.each do |speaker| - = image_tag speaker.gravatar_url, :class => "img-circle pull-right speaker-pic", | - :alt => speaker.name, | - :title => speaker.name, | - :style => "height: #{ speaker_height(@rooms) }px; width: #{ speaker_width(@rooms) }px;" - diff --git a/app/views/schedules/_schedule_tabs.html.haml b/app/views/schedules/_schedule_tabs.html.haml index 0317f8bc5..198ea74c4 100644 --- a/app/views/schedules/_schedule_tabs.html.haml +++ b/app/views/schedules/_schedule_tabs.html.haml @@ -1,7 +1,12 @@ -%div{ role: "tabpanel" } - / Nav tabs - %ul.nav.nav-tabs{ role: "tablist" } - %li{ class: "schedule #{ 'active' if active == 'schedule' }", role: "presentation" } - = link_to('Schedule', conference_schedule_path(@conference.short_title)) - %li{ class: "program #{ 'active' if active == 'program' }", role: "presentation" } - = link_to('All events', events_conference_schedule_path(@conference.short_title)) +%div{ role: 'tabpanel' } + %ul.nav.nav-tabs{ role: 'tablist' } + %li.program{ class: ('active' if active == 'program'), role: 'presentation' } + = link_to('All events', events_conference_schedule_path(@conference.short_title, favourites: @favourites)) + %li.program{ class: ('active' if active == 'now'), role: 'presentation' } + = link_to('Happening Now', happening_now_conference_schedule_path(@conference.short_title)) + %li.schedule{ class: ('active' if active == 'vertical_schedule'), role: 'presentation' } + = link_to('Schedule', vertical_schedule_conference_schedule_path(@conference.short_title, favourites: @favourites)) + .pull-right + - if current_user + = link_to (@favourites ? 'All events' : 'My Personal Schedule'), + "#{request.path}?favourites=#{!@favourites}", class: 'btn btn-success' diff --git a/app/views/schedules/events.html.haml b/app/views/schedules/events.html.haml index b6e87e31a..4c45a80b6 100644 --- a/app/views/schedules/events.html.haml +++ b/app/views/schedules/events.html.haml @@ -1,45 +1,70 @@ -.container#program - -if @events_schedules.any? - = render partial: 'schedule_tabs', locals: { active: 'program' } +- provide :title, "#{@conference.title} Program" += content_for :splash_nav do + %li + = link_to('Schedule', events_conference_schedule_path(@conference)) - %h1.text-center - Program for - = @conference.title - .dropdown.program-dropdown - %button{ type: "button", class: "btn btn-default dropdown-toggle", 'data-toggle' => "dropdown" } - Dates - %span.caret - %ul.dropdown-menu - - @dates.each do |date| - %li.li-dropdown-program - = link_to date, "##{date}", class: "program-selector#{ ' no-events-day' unless @conference.program.any_event_for_this_date?(date) }" - - if @unscheduled_events.any? - %li.li-dropdown-program - = link_to('Unscheduled', "#unscheduled", class: 'program-selector') +.container#program{ style: 'width :92%' } + .row{style: 'padding-top: 1em'} + = render partial: 'schedule_tabs', locals: { active: 'program' } - .row + %h1.text-center + - if @favourites && current_user + #{current_user.name}'s Program for #{@conference.title} + - else + Program for #{@conference.title} + + = render 'date_event_types', conference: @conference, favourites: @favourites + + .dropdown.program-dropdown + %button.btn.btn-default.dropdown-toggle{ type: "button", 'data-toggle': "dropdown" } + Dates + %span.caret + %ul.dropdown-menu + - @dates.each do |date| + %li.li-dropdown-program + - new_date = current_user ? convert_timezone(date.to_datetime.change(hour: @conference.start_hour), @conference.timezone, current_user.timezone) || date : date + = link_to new_date.strftime('%Y-%m-%d'), "##{new_date.strftime('%Y-%m-%d')}", class: "program-selector#{ ' no-events-day' unless @conference.program.any_event_for_this_date?(date) }" + - if @unscheduled_events.any? + %li.li-dropdown-program + = link_to('Unscheduled', "#unscheduled", class: 'program-selector') + + - if @favourites && current_user && @events_schedules.empty? + .row + %strong + You have no events on your agenda. + %br + %strong + #{link_to 'View the full program', events_conference_schedule_path(@conference.short_title, favourites: false)} and add events to your schedule? + + .row / scheduled events - - date = nil - - time = nil + :ruby + date = nil + time = nil + tz_object = current_user&.timezone ? current_user : @conference + + / TODO-SNAPCON: Explore caching this. - @events_schedules.each do |event_schedule| - - unless event_schedule.start_time.strftime('%Y-%m-%d').eql?(date) + - next if event_schedule.event.parent_event.present? + - new_start_time = convert_timezone(event_schedule.start_time, @conference.timezone, tz_object.timezone) + - start_ymd = new_start_time.strftime('%Y-%m-%d') + - unless start_ymd.eql?(date) .col-xs-12.col-md-12 .date-content - %span{ class: 'date-title', id: "#{ event_schedule.start_time.strftime('%Y-%m-%d') }" } - = date = event_schedule.start_time.strftime('%Y-%m-%d') - %a{ title: "Go up", class: "pull-right", href: "#program" } - %i{ class: "fa-solid fa-angles-up fa-lg", 'aria-hidden' => true } - - unless event_schedule.start_time.strftime('%H:%M').eql?(time) - .col-xs-12.col-md-1 - .start-time - = time = event_schedule.start_time.strftime('%H:%M') - .col-xs-12.col-md-11 - .new-time-event - = render partial: 'event', locals: { event: event_schedule.event, event_schedule: event_schedule } - - else - .col-xs-12.col-md-11.col-md-offset-1 - = render partial: 'event', locals: { event: event_schedule.event, event_schedule: event_schedule } + %span.date-title{ id: start_ymd } + = inyourtz(event_schedule.start_time, @conference.timezone) do + = date = start_ymd + %a.pull-right{ title: "Go up", href: "#program" } + %i.fa-solid.fa-angles-up.fa-lg{ 'aria-hidden': true } + .col-xs-12.col-md-1 + - if !event_schedule.start_time.strftime('%H:%M').eql?(time) || !start_ymd.eql?(date) + - time = new_start_time.strftime('%H:%M') + = inyourtz(event_schedule.start_time, @conference.timezone) do + = time + ' ' + timezone_text(tz_object) + .col-xs-12.col-md-11 + - cache [@program, event_schedule, event_schedule.event, current_user, event_schedule.happening_now?, '#scheduled#full#panel'] do + = render 'event', event: event_schedule.event, event_schedule: event_schedule / confirmed events that are not scheduled - if @unscheduled_events.any? @@ -47,22 +72,24 @@ .date-content %span.date-title#unscheduled Unscheduled events - %a{ title: "Go up", class: "pull-right", href: "#program" } - %i{ class: "fa-solid fa-angles-up fa-lg", 'aria-hidden' => true } + %a.pull-right{ title: "Go up", href: "#program" } + %i.fa-solid.fa-angles-up.fa-lg{ 'aria-hidden': true } - @unscheduled_events.each do |event| .col-xs-12.col-md-12 .unscheduled-event - = render partial: 'event', locals: { event: event, event_schedule: nil } + - cache [@program, event, current_user, '#unscheduled#full#panel'] do + = render 'event', event: event, event_schedule: nil :javascript $('.program-selector').on('click', function(e) { $('.li-dropdown-program').removeClass('active'); }); - // Go to current date and time $(document).ready(function(){ tag = "#{ @tag }"; if(tag !== ""){ document.getElementById(tag).scrollIntoView(); } + + updateFavouriteStatus({ events: #{@favourited_events}, loggedIn: #{current_user.present?} }); }); diff --git a/app/views/schedules/happening_now.haml b/app/views/schedules/happening_now.haml new file mode 100644 index 000000000..9bc6bdae3 --- /dev/null +++ b/app/views/schedules/happening_now.haml @@ -0,0 +1,66 @@ +- timezone = display_timezone(current_user, @conference) +- provide :title, "#{@conference.title} Happening Now" += content_for :splash_nav do + %li + = link_to('Schedule', events_conference_schedule_path(@conference)) + +.container#program{ style: 'width: 92%' } + .row{style: 'padding-top: 1em'} + = render 'schedule_tabs', active: 'now' + + %h1.text-center + Happening Now at + = @conference.title + + = render 'date_event_types', conference: @conference, favourites: @favourites + + .row + .col-md-12 + %h2 + #{pluralize(@events_schedules.count, 'Event')} Occurring Within The Next 30 minutes. + %br + %small + This page was loaded at + = inyourtz(Time.now, @conference.timezone) do + = Time.now.in_time_zone(timezone).strftime('%a %b %d at %I:%M %P (%z)') + \. + + .row + / TODO: Clean this up, merge with all events page. + - date = nil + - time = nil + - @events_schedules.each do |event_schedule| + - new_start_time = convert_timezone(event_schedule.start_time, @conference.timezone, timezone) + - start_ymd = new_start_time.strftime('%Y-%m-%d') + - unless start_ymd.eql?(date) + .col-xs-12.col-md-12 + .date-content + %span.date-title{ id: start_ymd } + = inyourtz(event_schedule.start_time, timezone) do + = date = start_ymd + %a.pull-right{ title: 'Go up', href: '#program' } + %i.fa.fa-angle-double-up.fa-lg{ 'aria-hidden': true } + - if new_start_time.strftime('%H:%M').eql?(time) + .col-xs-12.col-md-11.col-md-offset-1 + - cache [event_schedule, event_schedule.event, current_user, event_schedule.happening_now?, '#scheduled#full#panel'] do + = render 'event', event: event_schedule.event, event_schedule: event_schedule + - else + .col-xs-12.col-md-1 + .start-time + - time = new_start_time.strftime('%H:%M') + = inyourtz(event_schedule.start_time, timezone) do + = time + ' ' + Time.now.in_time_zone(timezone).strftime('%Z') + .col-xs-12.col-md-11 + .new-time-event + - cache [event_schedule, event_schedule.event, current_user, event_schedule.happening_now?, '#scheduled#full#panel'] do + = render 'event', event: event_schedule.event, event_schedule: event_schedule + +:javascript + // Refresh the page every 5 minutes. + $(document).ready(function(){ + setTimeout(function() { + window.location = window.location; + }, 5 * 60 * 1000); + }); + + updateFavouriteStatus({ events: #{@favourited_events}, loggedIn: #{current_user.present?} }); diff --git a/app/views/schedules/show.html.haml b/app/views/schedules/show.html.haml index 8000b1ad0..3b07a5161 100644 --- a/app/views/schedules/show.html.haml +++ b/app/views/schedules/show.html.haml @@ -1,80 +1,32 @@ -.container - = render partial: 'schedule_tabs', locals: { active: 'schedule' } +- provide :title, "#{@conference.title} Schedule" += content_for :splash_nav do + %li + = link_to('Schedule', events_conference_schedule_path(@conference)) - #schedule-content - %h1.text-center - Schedule for - = @conference.title - .dropdown.schedule-dropdown - %button{ type: "button", class: "btn btn-default dropdown-toggle", 'data-toggle' => "dropdown" } - = @day - %span.caret - %ul.dropdown-menu - - @dates.each do |date| - %li.li-dropdown-schedule{ class: "#{ 'active' if @day == date }" } - = link_to date, "#" + "#{date}", "data-toggle" => "tab", "class" => "date-tab" - - .tab-content - - @dates.each do |date| - %div{ class: "tab-pane #{ 'active' if @day == date }", id: "#{ date }" } - - .visible-xs-inline - = render partial: 'carousel', locals: { date: date, hrs_per_slide: 1 } - - .visible-sm-inline - = render partial: 'carousel', locals: { date: date, hrs_per_slide: 2 } - - .visible-md-inline.visible-lg-inline - = render partial: 'carousel', locals: { date: date, hrs_per_slide: 3 } - %p - %span - = link_to conference_schedule_url(protocol: 'webcal', format: 'ics') do - Add the schedule to your calendar - %span.pull-right - = link_to app_conference_schedule_path do - Get the mobile app! +.container#program{ style: 'width: 92%' } + .row{style: 'padding-top: 1em'} + = render 'schedule_tabs', active: 'vertical_schedule' -:javascript - // change of active tab and the button title when a date is clicked - $(function() { - $('.date-tab').on('click', function(e) { - $('.li-dropdown-schedule').removeClass('active'); - $('.schedule-dropdown').find('button').text($(this).text()); - }); - }); - - // hide the right and left controls when neccesary after moving the carousel - $('.carousel').on('slid.bs.carousel', '', - function(){ - $(this).children('.left.carousel-control').show(); - $(this).children('.right.carousel-control').show(); - if($(this).find('.first').hasClass('active')) { - $(this).children('.left.carousel-control').hide(); - } - if($(this).find('.last').hasClass('active')) { - $(this).children('.right.carousel-control').hide(); - } - }); - - $(document).ready(function(){ - // hide the left control when the page is ready - $('.carousel').each(function() { - if($(this).find('.first').hasClass('active')) { - $(this).children('.left.carousel-control').hide(); - } - if($(this).find('.last').hasClass('active')) { - $(this).children('.right.carousel-control').hide(); - } - }); - - var day = "#{@current_day}"; - // we only go to the date tag in the url if the conference is not taking place now - if(day === ""){ - // use the date tag in the url to select a tab and the title of the button - var hash = window.location.hash; - if(hash && !(hash === '#schedule')){ - hash && $('ul a[href="' + hash + '"]').tab('show'); - $('button.dropdown-toggle').text(hash.substr(1)); - } - } - }); + %h1.text-center + - if @favourites && current_user + #{current_user.name}'s Schedule for #{@conference.title} + - else + Schedule for #{@conference.title} + + %p.text-center + %strong + ⏰ This schedule uses your browser's local timezone. + %span.js-localTimezone + + = render 'event_types_key', event_types: @program.event_types, + conference: @conference, favourites: @favourites + + %hr + + #vert-schedule-full-calendar + - cache [@conference, @program, @rooms, @event_schedules, @day, @favourites, current_user] do + #fullcalendar{ data: { rooms: @rooms, events: @event_schedules, day: @day, + 'start-hour': @conference.start_hour, 'end-hour': @conference.end_hour, + 'start-date': @conference.start_date, 'end-date': @conference.end_date + 1.day, + 'tz-offset': timezone_offset(@conference), + 'now': @now, 'min-interval': @conference.program.schedule_interval } } diff --git a/app/views/shared/_help.html.haml b/app/views/shared/_help.html.haml new file mode 100644 index 000000000..8bf1ffc33 --- /dev/null +++ b/app/views/shared/_help.html.haml @@ -0,0 +1,76 @@ +.template-help{ id: id } + Valid attributes: + %table.table + %tr + %td {email} + %td The user's email address + %tr + %td {name} + %td The user's full name + %tr + %td {conference} + %td The full conference title + - if show_event_variables.present? + %tr + %td {proposalslink} + %td A link to the user's proposal page + %tr + %td {eventtitle} + %td The title of an accepted or rejected proposal + %tr + %td {committee_review} + %td The raw text in the committee review for the proposal + %tr + %td {committee_review_html} + %td The committee review markdown rendered as HTML. + %tr + %td {conference_start_date} + %td The start date of the conference + %tr + %td {conference_end_date} + %td The end date of the conference + - if @conference.registration_dates_given? + %tr + %td {registration_start_date} + %td The start date of the registration period + %tr + %td {registration_end_date} + %td The end date of the registration period + %tr + %td {registrationlink} + %td A link to the registration page + - if (@conference.venue.present?) + %tr + %td {venue} + %td The name of the venue + %tr + %td {venue_address} + %td The address of the venue + - unless @conference.program.cfp.blank? || @conference.program.cfp.start_date.blank? || @conference.program.cfp.end_date.blank? + %tr + %td {cfp_start_date} + %td The call for papers start date + %tr + %td {cfp_end_date} + %td The call for papers end date + - if @conference.program.schedule_public + %td {schedule_link} + %td The link to complete schedule of the conference + - if (@conference.splashpage.present?) && @conference.splashpage.public? + %tr + %td {conference_splash_link} + %td The link to conference splash page + - if show_ticket_variables + %tr + %td {ticket_quantity} + %td The quantity of tickets purchased + %tr + %td {ticket_title} + %td The ticket title + %tr + %td {ticket_purchase_id} + %td The id of the ticket purchase transaction + - if @conference.booths + %tr + %td {booth_title} + %td Booth's title diff --git a/app/views/shared/_media_item.html.haml b/app/views/shared/_media_item.html.haml index 9357c9e2b..367d04a2f 100644 --- a/app/views/shared/_media_item.html.haml +++ b/app/views/shared/_media_item.html.haml @@ -1,6 +1,7 @@ -- if commercial.url - = Commercial.render_from_url(commercial.url)[:html] -- else +- if commercial.title.present? + .panel-heading= commercial.title + +.flexvideo - if commercial.commercial_type == 'SlideShare' %iframe{width: '560', height: '315', frameborder: '0', allowfullscreen: 'true', src: "https://www.slideshare.net/slideshow/embed_code/#{commercial.commercial_id}"} - elsif commercial.commercial_type == 'Flickr' @@ -11,5 +12,10 @@ %iframe{width: '560', height: '315', frameborder: '0', allowfullscreen: 'true', src: "https://speakerdeck.com/player/#{commercial.commercial_id}?"} - elsif commercial.commercial_type == 'Instagram' %iframe{width: '560', height: '315', frameborder: '0', allowfullscreen: 'true', scrolling: 'no', allowtransparency: 'true', src: "//instagram.com/p/#{commercial.commercial_id}/embed/"} - - else + - elsif commercial.commercial_type == 'YouTube' %iframe{width: '560', height: '315', frameborder: '0', allowfullscreen: 'true', src: "https://www.youtube.com/embed/#{commercial.commercial_id}?rel=0"} + - elsif commercial.url + = Commercial.render_from_url(commercial.url)[:html] +- if commercial.url + = link_to('Open in a new tab', commercial.url, target: '_blank', class: 'btn btn-info btn-xs') + %br diff --git a/app/views/shared/_media_items.html.haml b/app/views/shared/_media_items.html.haml index e4ef3c0d7..e4fcfaa07 100644 --- a/app/views/shared/_media_items.html.haml +++ b/app/views/shared/_media_items.html.haml @@ -1,20 +1,10 @@ - unless commercials.empty? - - if commercials.length == 1 - .flexvideo - = render partial: 'shared/media_item', locals: { commercial: commercials.first } - - else - %ul.nav.nav-tabs{ 'role'=>'tablist' } - - commercials.each.with_index(1) do |commercial, index| - %li{class: "#{'active' if (index == 1)}"} - %a{ 'href'=>"##{index}", 'role'=>'tab', 'data-toggle'=>'tab' } - = index - .tab-content - - commercials.each.with_index(1) do |commercial, index| - - if index == 1 - .tab-pane.active{'id'=>"#{index}"} - .flexvideo - = render partial: 'shared/media_item', locals: { commercial: commercial } - - else - .tab-pane{'id'=>"#{index}"} - .flexvideo - = render partial: 'shared/media_item', locals: { commercial: commercial } + %ul.nav.nav-tabs{ role: 'tablist' } + - commercials.each.with_index(1) do |commercial, index| + %li{class: "#{'active' if (index == 1)}"} + %a{ href: "##{index}", role: 'tab', 'data-toggle': 'tab' } + = commercial.title || index + .tab-content + - commercials.each.with_index(1) do |commercial, index| + .tab-pane{ id: index, class: "#{'active' if (index == 1)}" } + = render 'shared/media_item', commercial: commercial, cache: true diff --git a/app/views/shared/_user_selectize.html.haml b/app/views/shared/_user_selectize.html.haml index 95f3729fa..700c46830 100644 --- a/app/views/shared/_user_selectize.html.haml +++ b/app/views/shared/_user_selectize.html.haml @@ -1,13 +1,11 @@ :javascript $(document).ready(function() { - $('#users_selectize').selectize({ - persist: false, - create: false, + $('.js-userSelector, #users_selectize').selectize({ + plugins: ['remove_button'], valueField: 'id', - labelField: 'username', - searchField: 'username', + labelField: 'dropdwon_display', + searchField: ['username', 'name', 'email'], load: function(query, callback) { - if (!query.length) return callback(); $.ajax({ url: "#{search_users_path}.json", type: 'GET', @@ -16,12 +14,9 @@ query: query, }, error: function(res) { - console.log("selectize error"); callback(); }, success: function(res) { - console.log("selectize success"); - // console.log(res); callback(res.users); } }); diff --git a/app/views/tickets/_ticket.html.haml b/app/views/tickets/_ticket.html.haml index 501e41513..cce20a648 100644 --- a/app/views/tickets/_ticket.html.haml +++ b/app/views/tickets/_ticket.html.haml @@ -3,26 +3,25 @@ .media .media-body %h4.media-heading= ticket.title - %h5.media-heading= markdown(ticket.description) - - if @conference.tickets.for_registration.any? - %td.col-sm-1.col-md-2.text-center + .media-heading= markdown(ticket.description) + %td.col-sm-1.col-md-2 + %span.text-center = ticket.registration_ticket? ? 'Yes' : 'No' %td.col-sm-1.col-md-1 :ruby options = { type: 'number', min: 0, - max: 99, + max: ticket.registration_ticket? ? 1 : 99, + disabled: ticket.registration_ticket? && user_has_reg_ticket, class: 'form-control quantity', 'data-id' => ticket.id } - if ticket.registration_ticket? - options[:max] = 1 - options[:disabled] = current_user.tickets.for_registration( - ticket.conference - ).present? - end = text_field_tag("tickets[][#{ticket.id}]", 0, options) + - if ticket.registration_ticket? && user_has_reg_ticket + %em + You currently have a registraction ticket. + %td.col-sm-1.col-md-1.text-center = ticket.price.symbol %span{id: "price_#{ticket.id}"} diff --git a/app/views/tickets/index.html.haml b/app/views/tickets/index.html.haml index 104cdce01..495af35de 100644 --- a/app/views/tickets/index.html.haml +++ b/app/views/tickets/index.html.haml @@ -13,18 +13,17 @@ %thead %tr %th Ticket - - if @conference.tickets.for_registration.any? - %th Registration Ticket + %th Registration? %th Quantity %th Price %th Total %tbody - - @conference.tickets.each do |ticket| - = render 'ticket', f: f, ticket: ticket + - user_has_reg_ticket = current_user.tickets.for_registration(@conference).present? + - @conference.tickets.visible.each do |ticket| + = render 'ticket', f: f, ticket: ticket, user_has_reg_ticket: user_has_reg_ticket %tr %td - - if @conference.tickets.for_registration.any? - %td + %td %td %td.col-sm-1.col-md-1.text-center %h4 @@ -48,5 +47,5 @@ .col-md-13 %p.text-muted.text-center %small - Getting a registration ticket is mandatory. Your participation + Getting a registration ticket is required. Your participation will not be valid until you get a registration ticket. diff --git a/app/views/tracks/show.html.haml b/app/views/tracks/show.html.haml index 52448b984..a6aa066cb 100644 --- a/app/views/tracks/show.html.haml +++ b/app/views/tracks/show.html.haml @@ -45,3 +45,7 @@ Relevance %dd = markdown(@track.relevance) + + .row + - @track.events.each do |event| + = render 'schedules/event', event: event, event_schedule: event.event_schedules.first diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index fa85d57fa..d1b967620 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -6,6 +6,7 @@ .row .col-md-12 = form_for(@user) do |f| + %h2 Basic Information .form-group = f.label :name = f.text_field :name, class: 'form-control' @@ -14,6 +15,14 @@ .form-group = f.label :nickname = f.text_field :nickname, class: 'form-control' + .form-group + = f.label :timezone + %br + = f.select :timezone, time_zone_options_for_select(selected: @user.timezone), include_blank: true + %span.help-block + The timezone setting will update the event schedules to show using the time you selected. + Your browser is current set to + %span.js-localTimezone .form-group = f.label :avatar %br @@ -21,6 +30,14 @@ %span.help-block Change your avatar on = link_to 'gravatar.com', 'https://gravatar.com' + %p + Or upload a picture. + = f.file_field :picture, hint: 'If you upload a picture, it will be used in place of Gravatar.' + - if @user.picture? + %p + Current Picture + %br + = image_tag(@user.picture.thumb.url, width: '20%') .form-group = f.label :affiliation = f.text_field :affiliation, class: 'form-control' @@ -31,11 +48,21 @@ = f.text_area :biography, rows: 5, data: { provide: 'markdown' }, class: 'form-control' %span.help-block You have used - %span#bio_length + %span#bio-length = @user.biography ? @user.biography.split.length : 0 - words. Biographies are limited to 150 words. + words. Biographies are limited to 200 words. = markdown_hint .form-group .text-right - = f.submit nil, class: 'btn btn-primary' + = f.submit nil, class: 'btn btn-primary' + + :javascript + $(document).ready(function() { + $('#user_timezone').selectize({}) + }); + + let localOffset = (new Date()).getTimezoneOffset()/60; + // UTC JS offsets are "-1 *" of how they're displayed. + let operator = localOffset < 0 ? '+' : '-'; + $('.js-localTimezone').text(`(GMT ${operator}${Math.abs(localOffset)}).`); diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index b20891f8d..65f8e853e 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -3,7 +3,7 @@ .col-md-12 .page-header %h1 - = image_tag(@user.gravatar_url(size: '48'), title: "Yo #{@user.name}!", alt: '') + = image_tag(@user.profile_picture(size: '48'), title: "Yo #{@user.name}!", alt: '') = @user.name %small = @user.nickname diff --git a/bin/travis_script.sh b/bin/travis_script.sh new file mode 100755 index 000000000..dfcbfcaca --- /dev/null +++ b/bin/travis_script.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# This script runs the test suites for the CI build + +# Be verbose and fail script on the first error +set -xe + +# By default: all test runs +if [ -z $1 ]; then + TEST_SUITE="all" +else + TEST_SUITE="$1" +fi + +case $TEST_SUITE in + linters|all) + bundle exec rubocop -Dc .rubocop.yml + bundle exec haml-lint app/views + ;;& + models|all) + bundle exec rspec --format documentation spec/models + ;;& + features|all) + bundle exec rspec --format documentation spec/features + ;;& + controllers|all) + bundle exec rspec --format documentation spec/controllers + ;;& + ability|all) + bundle exec rspec --format documentation spec/ability + ;;& + rest|all) + bundle exec rspec --format documentation --exclude-pattern "spec/{models,features,controllers,ability}/**/*_spec.rb" + ;; +esac diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..06e1a350e --- /dev/null +++ b/codecov.yml @@ -0,0 +1,3 @@ +comment: on +codecov: + token: 795947fc-2620-4f31-b287-d98f062a6add diff --git a/config/amazon-rds-ca-cert.pem b/config/amazon-rds-ca-cert.pem new file mode 100644 index 000000000..864a818dd --- /dev/null +++ b/config/amazon-rds-ca-cert.pem @@ -0,0 +1,720 @@ +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSUwIwYDVQQDDBxBbWF6b24gUkRTIGFwLWVhc3QtMSBSb290IENBMB4XDTE5MDIx +NzAyNDcwMFoXDTIyMDYwMTEyMDAwMFowgY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6b24g +V2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMSAwHgYDVQQD +DBdBbWF6b24gUkRTIGFwLWVhc3QtMSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOcJAUofyJuBuPr5ISHi/Ha5ed8h3eGdzn4MBp6rytPOg9NVGRQs +O93fNGCIKsUT6gPuk+1f1ncMTV8Y0Fdf4aqGWme+Khm3ZOP3V1IiGnVq0U2xiOmn +SQ4Q7LoeQC4lC6zpoCHVJyDjZ4pAknQQfsXb77Togdt/tK5ahev0D+Q3gCwAoBoO +DHKJ6t820qPi63AeGbJrsfNjLKiXlFPDUj4BGir4dUzjEeH7/hx37na1XG/3EcxP +399cT5k7sY/CR9kctMlUyEEUNQOmhi/ly1Lgtihm3QfjL6K9aGLFNwX35Bkh9aL2 +F058u+n8DP/dPeKUAcJKiQZUmzuen5n57x8CAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFlqgF4FQlb9yP6c+Q3E +O3tXv+zOMB8GA1UdIwQYMBaAFK9T6sY/PBZVbnHcNcQXf58P4OuPMA0GCSqGSIb3 +DQEBCwUAA4IBAQDeXiS3v1z4jWAo1UvVyKDeHjtrtEH1Rida1eOXauFuEQa5tuOk +E53Os4haZCW4mOlKjigWs4LN+uLIAe1aFXGo92nGIqyJISHJ1L+bopx/JmIbHMCZ +0lTNJfR12yBma5VQy7vzeFku/SisKwX0Lov1oHD4MVhJoHbUJYkmAjxorcIHORvh +I3Vj5XrgDWtLDPL8/Id/roul/L+WX5ir+PGScKBfQIIN2lWdZoqdsx8YWqhm/ikL +C6qNieSwcvWL7C03ri0DefTQMY54r5wP33QU5hJ71JoaZI3YTeT0Nf+NRL4hM++w +Q0veeNzBQXg1f/JxfeA39IDIX1kiCf71tGlT +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEDCCAvigAwIBAgIJAJF3HxEqKM4lMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzElMCMGA1UEAwwcQW1hem9uIFJEUyBhcC1lYXN0LTEgUm9vdCBDQTAe +Fw0xOTAyMTcwMjQ2MTFaFw0yNDAyMTYwMjQ2MTFaMIGUMQswCQYDVQQGEwJVUzEQ +MA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UECgwZ +QW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEl +MCMGA1UEAwwcQW1hem9uIFJEUyBhcC1lYXN0LTEgUm9vdCBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAOCVr1Yj5IW4XWa9QOLGJDSz4pqIM6BAbqQp +gYvzIO4Lv8c8dEnuuuCY8M/zOrJ1iQJ3cDiKGa32HVBVcH+nUdXzw4Jq5jw0hsb6 +/WW2RD2aUe4jCkRD5wNzmeHM4gTgtMZnXNVHpELgKR4wVhSHEfWFTiMsZi35y8mj +PL98Mz/m/nMnB/59EjMvcJMrsUljHO6B9BMEcvNkwvre9xza0BQWKyiVRcbOpoj1 +w4BPtYYZ+dW2QKw9AmYXwAmCLeATsxrHIJ/IbzS7obxv2QN2Eh4pJ3ghRCFv1XM9 +XVkm13oiCjj7jsxAwF7o+VggPl/GG+/Gwk+TLuaTFNAtROpPxL8CAwEAAaNjMGEw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9T6sY/ +PBZVbnHcNcQXf58P4OuPMB8GA1UdIwQYMBaAFK9T6sY/PBZVbnHcNcQXf58P4OuP +MA0GCSqGSIb3DQEBCwUAA4IBAQBBY+KATaT7ndYT3Ky0VWaiwNfyl1u3aDxr+MKP +VeDhtOhlob5u0E+edOXUvEXd4A+ntS+U0HmwvtMXtQbQ2EJbsNRqZnS8KG9YB2Yc +Q99auphW3wMjwHRtflLO5h14aa9SspqJJgcM1R7Z3pAYeq6bpBDxZSGrYtWI64q4 +h4i67qWAGDFcXSTW1kJ00GMlBCIGTeYiu8LYutdsDWzYKkeezJRjx9VR4w7A7e1G +WmY4aUg/8aPxCioY2zEQKNl55Ghg6Dwy+6BxaV6RlV9r9EaSCai11p1bgS568WQn +4WNQK36EGe37l2SOpDB6STrq57/rjREvmq803Ylg/Gf6qqzK +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQTAeFw0xOTA1 +MTAyMTU4NDNaFw0yNTA2MDExMjAwMDBaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBtZS1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAudOYPZH+ihJAo6hNYMB5izPVBe3TYhnZm8+X3IoaaYiKtsp1 +JJhkTT0CEejYIQ58Fh4QrMUyWvU8qsdK3diNyQRoYLbctsBPgxBR1u07eUJDv38/ +C1JlqgHmMnMi4y68Iy7ymv50QgAMuaBqgEBRI1R6Lfbyrb2YvH5txjJyTVMwuCfd +YPAtZVouRz0JxmnfsHyxjE+So56uOKTDuw++Ho4HhZ7Qveej7XB8b+PIPuroknd3 +FQB5RVbXRvt5ZcVD4F2fbEdBniF7FAF4dEiofVCQGQ2nynT7dZdEIPfPdH3n7ZmE +lAOmwHQ6G83OsiHRBLnbp+QZRgOsjkHJxT20bQIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUOEVDM7VomRH4HVdA +QvIMNq2tXOcwHwYDVR0jBBgwFoAU54cfDjgwBx4ycBH8+/r8WXdaiqYwDQYJKoZI +hvcNAQELBQADggEBAHhvMssj+Th8IpNePU6RH0BiL6o9c437R3Q4IEJeFdYL+nZz +PW/rELDPvLRUNMfKM+KzduLZ+l29HahxefejYPXtvXBlq/E/9czFDD4fWXg+zVou +uDXhyrV4kNmP4S0eqsAP/jQHPOZAMFA4yVwO9hlqmePhyDnszCh9c1PfJSBh49+b +4w7i/L3VBOMt8j3EKYvqz0gVfpeqhJwL4Hey8UbVfJRFJMJzfNHpePqtDRAY7yjV +PYquRaV2ab/E+/7VFkWMM4tazYz/qsYA2jSH+4xDHvYk8LnsbcrF9iuidQmEc5sb +FgcWaSKG4DJjcI5k7AJLWcXyTDt21Ci43LE+I9Q= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJANew34ehz5l8MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkwNTEwMjE0ODI3WhcNMjQwNTA4MjE0ODI3WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgbWUtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp7BYV88MukcY+rq0r79+C8UzkT30fEfT +aPXbx1d6M7uheGN4FMaoYmL+JE1NZPaMRIPTHhFtLSdPccInvenRDIatcXX+jgOk +UA6lnHQ98pwN0pfDUyz/Vph4jBR9LcVkBbe0zdoKKp+HGbMPRU0N2yNrog9gM5O8 +gkU/3O2csJ/OFQNnj4c2NQloGMUpEmedwJMOyQQfcUyt9CvZDfIPNnheUS29jGSw +ERpJe/AENu8Pxyc72jaXQuD+FEi2Ck6lBkSlWYQFhTottAeGvVFNCzKszCntrtqd +rdYUwurYsLTXDHv9nW2hfDUQa0mhXf9gNDOBIVAZugR9NqNRNyYLHQIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU54cf +DjgwBx4ycBH8+/r8WXdaiqYwHwYDVR0jBBgwFoAU54cfDjgwBx4ycBH8+/r8WXda +iqYwDQYJKoZIhvcNAQELBQADggEBAIIMTSPx/dR7jlcxggr+O6OyY49Rlap2laKA +eC/XI4ySP3vQkIFlP822U9Kh8a9s46eR0uiwV4AGLabcu0iKYfXjPkIprVCqeXV7 +ny9oDtrbflyj7NcGdZLvuzSwgl9SYTJp7PVCZtZutsPYlbJrBPHwFABvAkMvRtDB +hitIg4AESDGPoCl94sYHpfDfjpUDMSrAMDUyO6DyBdZH5ryRMAs3lGtsmkkNUrso +aTW6R05681Z0mvkRdb+cdXtKOSuDZPoe2wJJIaz3IlNQNSrB5TImMYgmt6iAsFhv +3vfTSTKrZDNTJn4ybG6pq1zWExoXsktZPylJly6R3RBwV6nwqBM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEETCCAvmgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSUwIwYDVQQDDBxBbWF6b24gUkRTIEJldGEgUm9vdCAyMDE5IENBMB4XDTE5MDgy +MDE3MTAwN1oXDTI0MDgxOTE3MzgyNlowgZkxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6b24g +V2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMSowKAYDVQQD +DCFBbWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDTNCOlotQcLP8TP82U2+nk0bExVuuMVOgFeVMx +vbUHZQeIj9ikjk+jm6eTDnnkhoZcmJiJgRy+5Jt69QcRbb3y3SAU7VoHgtraVbxF +QDh7JEHI9tqEEVOA5OvRrDRcyeEYBoTDgh76ROco2lR+/9uCvGtHVrMCtG7BP7ZB +sSVNAr1IIRZZqKLv2skKT/7mzZR2ivcw9UeBBTUf8xsfiYVBvMGoEsXEycjYdf6w +WV+7XS7teNOc9UgsFNN+9AhIBc1jvee5E//72/4F8pAttAg/+mmPUyIKtekNJ4gj +OAR2VAzGx1ybzWPwIgOudZFHXFduxvq4f1hIRPH0KbQ/gkRrAgMBAAGjZjBkMA4G +A1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTkvpCD +6C43rar9TtJoXr7q8dkrrjAfBgNVHSMEGDAWgBStoQwVpbGx87fxB3dEGDqKKnBT +4TANBgkqhkiG9w0BAQsFAAOCAQEAJd9fOSkwB3uVdsS+puj6gCER8jqmhd3g/J5V +Zjk9cKS8H0e8pq/tMxeJ8kpurPAzUk5RkCspGt2l0BSwmf3ahr8aJRviMX6AuW3/ +g8aKplTvq/WMNGKLXONa3Sq8591J+ce8gtOX/1rDKmFI4wQ/gUzOSYiT991m7QKS +Fr6HMgFuz7RNJbb3Fy5cnurh8eYWA7mMv7laiLwTNsaro5qsqErD5uXuot6o9beT +a+GiKinEur35tNxAr47ax4IRubuIzyfCrezjfKc5raVV2NURJDyKP0m0CCaffAxE +qn2dNfYc3v1D8ypg3XjHlOzRo32RB04o8ALHMD9LSwsYDLpMag== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEDCCAvigAwIBAgIJAKFMXyltvuRdMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzElMCMGA1UEAwwcQW1hem9uIFJEUyBCZXRhIFJvb3QgMjAxOSBDQTAe +Fw0xOTA4MTkxNzM4MjZaFw0yNDA4MTkxNzM4MjZaMIGUMQswCQYDVQQGEwJVUzEQ +MA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UECgwZ +QW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEl +MCMGA1UEAwwcQW1hem9uIFJEUyBCZXRhIFJvb3QgMjAxOSBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMkZdnIH9ndatGAcFo+DppGJ1HUt4x+zeO+0 +ZZ29m0sfGetVulmTlv2d5b66e+QXZFWpcPQMouSxxYTW08TbrQiZngKr40JNXftA +atvzBqIImD4II0ZX5UEVj2h98qe/ypW5xaDN7fEa5e8FkYB1TEemPaWIbNXqchcL +tV7IJPr3Cd7Z5gZJlmujIVDPpMuSiNaal9/6nT9oqN+JSM1fx5SzrU5ssg1Vp1vv +5Xab64uOg7wCJRB9R2GC9XD04odX6VcxUAGrZo6LR64ZSifupo3l+R5sVOc5i8NH +skdboTzU9H7+oSdqoAyhIU717PcqeDum23DYlPE2nGBWckE+eT8CAwEAAaNjMGEw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFK2hDBWl +sbHzt/EHd0QYOooqcFPhMB8GA1UdIwQYMBaAFK2hDBWlsbHzt/EHd0QYOooqcFPh +MA0GCSqGSIb3DQEBCwUAA4IBAQAO/718k8EnOqJDx6wweUscGTGL/QdKXUzTVRAx +JUsjNUv49mH2HQVEW7oxszfH6cPCaupNAddMhQc4C/af6GHX8HnqfPDk27/yBQI+ +yBBvIanGgxv9c9wBbmcIaCEWJcsLp3HzXSYHmjiqkViXwCpYfkoV3Ns2m8bp+KCO +y9XmcCKRaXkt237qmoxoh2sGmBHk2UlQtOsMC0aUQ4d7teAJG0q6pbyZEiPyKZY1 +XR/UVxMJL0Q4iVpcRS1kaNCMfqS2smbLJeNdsan8pkw1dvPhcaVTb7CvjhJtjztF +YfDzAI5794qMlWxwilKMmUvDlPPOTen8NNHkLwWvyFCH7Doh +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFzCCAv+gAwIBAgICFSUwDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSgwJgYDVQQDDB9BbWF6b24gUkRTIFByZXZpZXcgUm9vdCAyMDE5IENBMB4XDTE5 +MDgyMTIyMzk0N1oXDTI0MDgyMTIyMjk0OVowgZwxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6 +b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMS0wKwYD +VQQDDCRBbWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIDIwMTkgQ0EwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0dB/U7qRnSf05wOi7m10Pa2uPMTJv +r6U/3Y17a5prq5Zr4++CnSUYarG51YuIf355dKs+7Lpzs782PIwCmLpzAHKWzix6 +pOaTQ+WZ0+vUMTxyqgqWbsBgSCyP7pVBiyqnmLC/L4az9XnscrbAX4pNaoJxsuQe +mzBo6yofjQaAzCX69DuqxFkVTRQnVy7LCFkVaZtjNAftnAHJjVgQw7lIhdGZp9q9 +IafRt2gteihYfpn+EAQ/t/E4MnhrYs4CPLfS7BaYXBycEKC5Muj1l4GijNNQ0Efo +xG8LSZz7SNgUvfVwiNTaqfLP3AtEAWiqxyMyh3VO+1HpCjT7uNBFtmF3AgMBAAGj +ZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW +BBQtinkdrj+0B2+qdXngV2tgHnPIujAfBgNVHSMEGDAWgBRp0xqULkNh/w2ZVzEI +o2RIY7O03TANBgkqhkiG9w0BAQsFAAOCAQEAtJdqbCxDeMc8VN1/RzCabw9BIL/z +73Auh8eFTww/sup26yn8NWUkfbckeDYr1BrXa+rPyLfHpg06kwR8rBKyrs5mHwJx +bvOzXD/5WTdgreB+2Fb7mXNvWhenYuji1MF+q1R2DXV3I05zWHteKX6Dajmx+Uuq +Yq78oaCBSV48hMxWlp8fm40ANCL1+gzQ122xweMFN09FmNYFhwuW+Ao+Vv90ZfQG +PYwTvN4n/gegw2TYcifGZC2PNX74q3DH03DXe5fvNgRW5plgz/7f+9mS+YHd5qa9 +tYTPUvoRbi169ou6jicsMKUKPORHWhiTpSCWR1FMMIbsAcsyrvtIsuaGCQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFjCCAv6gAwIBAgIJAMzYZJ+R9NBVMA0GCSqGSIb3DQEBCwUAMIGXMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEoMCYGA1UEAwwfQW1hem9uIFJEUyBQcmV2aWV3IFJvb3QgMjAxOSBD +QTAeFw0xOTA4MjEyMjI5NDlaFw0yNDA4MjEyMjI5NDlaMIGXMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEoMCYGA1UEAwwfQW1hem9uIFJEUyBQcmV2aWV3IFJvb3QgMjAxOSBDQTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7kkS6vjgKKQTPynC2NjdN5aPPV +O71G0JJS/2ARVBVJd93JLiGovVJilfWYfwZCs4gTRSSjrUD4D4HyqCd6A+eEEtJq +M0DEC7i0dC+9WNTsPszuB206Jy2IUmxZMIKJAA1NHSbIMjB+b6/JhbSUi7nKdbR/ +brj83bF+RoSA+ogrgX7mQbxhmFcoZN9OGaJgYKsKWUt5Wqv627KkGodUK8mDepgD +S3ZfoRQRx3iceETpcmHJvaIge6+vyDX3d9Z22jmvQ4AKv3py2CmU2UwuhOltFDwB +0ddtb39vgwrJxaGfiMRHpEP1DfNLWHAnA69/pgZPwIggidS+iBPUhgucMp8CAwEA +AaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FGnTGpQuQ2H/DZlXMQijZEhjs7TdMB8GA1UdIwQYMBaAFGnTGpQuQ2H/DZlXMQij +ZEhjs7TdMA0GCSqGSIb3DQEBCwUAA4IBAQC3xz1vQvcXAfpcZlngiRWeqU8zQAMQ +LZPCFNv7PVk4pmqX+ZiIRo4f9Zy7TrOVcboCnqmP/b/mNq0gVF4O+88jwXJZD+f8 +/RnABMZcnGU+vK0YmxsAtYU6TIb1uhRFmbF8K80HHbj9vSjBGIQdPCbvmR2zY6VJ +BYM+w9U9hp6H4DVMLKXPc1bFlKA5OBTgUtgkDibWJKFOEPW3UOYwp9uq6pFoN0AO +xMTldqWFsOF3bJIlvOY0c/1EFZXu3Ns6/oCP//Ap9vumldYMUZWmbK+gK33FPOXV +8BQ6jNC29icv7lLDpRPwjibJBXX+peDR5UK4FdYcswWEB1Tix5X8dYu6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgICVIYwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MDQxNzEz +MDRaFw0yNDA4MjIxNzA4NTBaMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEmMCQGA1UEAwwdQW1h +em9uIFJEUyBhcC1zb3V0aC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDUYOz1hGL42yUCrcsMSOoU8AeD/3KgZ4q7gP+vAz1WnY9K/kim +eWN/2Qqzlo3+mxSFQFyD4MyV3+CnCPnBl9Sh1G/F6kThNiJ7dEWSWBQGAB6HMDbC +BaAsmUc1UIz8sLTL3fO+S9wYhA63Wun0Fbm/Rn2yk/4WnJAaMZcEtYf6e0KNa0LM +p/kN/70/8cD3iz3dDR8zOZFpHoCtf0ek80QqTich0A9n3JLxR6g6tpwoYviVg89e +qCjQ4axxOkWWeusLeTJCcY6CkVyFvDAKvcUl1ytM5AiaUkXblE7zDFXRM4qMMRdt +lPm8d3pFxh0fRYk8bIKnpmtOpz3RIctDrZZxAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT99wKJftD3jb4sHoHG +i3uGlH6W6TAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAZ17hhr3dII3hUfuHQ1hPWGrpJOX/G9dLzkprEIcCidkmRYl+ +hu1Pe3caRMh/17+qsoEErmnVq5jNY9X1GZL04IZH8YbHc7iRHw3HcWAdhN8633+K +jYEB2LbJ3vluCGnCejq9djDb6alOugdLMJzxOkHDhMZ6/gYbECOot+ph1tQuZXzD +tZ7prRsrcuPBChHlPjmGy8M9z8u+kF196iNSUGC4lM8vLkHM7ycc1/ZOwRq9aaTe +iOghbQQyAEe03MWCyDGtSmDfr0qEk+CHN+6hPiaL8qKt4s+V9P7DeK4iW08ny8Ox +AVS7u0OK/5+jKMAMrKwpYrBydOjTUTHScocyNw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBjCCAu6gAwIBAgIJAMc0ZzaSUK51MA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkw +ODIyMTcwODUwWhcNMjQwODIyMTcwODUwWjCBjzELMAkGA1UEBhMCVVMxEDAOBgNV +BAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoMGUFtYXpv +biBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxIDAeBgNV +BAMMF0FtYXpvbiBSRFMgUm9vdCAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEArXnF/E6/Qh+ku3hQTSKPMhQQlCpoWvnIthzX6MK3p5a0eXKZ +oWIjYcNNG6UwJjp4fUXl6glp53Jobn+tWNX88dNH2n8DVbppSwScVE2LpuL+94vY +0EYE/XxN7svKea8YvlrqkUBKyxLxTjh+U/KrGOaHxz9v0l6ZNlDbuaZw3qIWdD/I +6aNbGeRUVtpM6P+bWIoxVl/caQylQS6CEYUk+CpVyJSkopwJlzXT07tMoDL5WgX9 +O08KVgDNz9qP/IGtAcRduRcNioH3E9v981QO1zt/Gpb2f8NqAjUUCUZzOnij6mx9 +McZ+9cWX88CRzR0vQODWuZscgI08NvM69Fn2SQIDAQABo2MwYTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUc19g2LzLA5j0Kxc0LjZa +pmD/vB8wHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJKoZIhvcN +AQELBQADggEBAHAG7WTmyjzPRIM85rVj+fWHsLIvqpw6DObIjMWokpliCeMINZFV +ynfgBKsf1ExwbvJNzYFXW6dihnguDG9VMPpi2up/ctQTN8tm9nDKOy08uNZoofMc +NUZxKCEkVKZv+IL4oHoeayt8egtv3ujJM6V14AstMQ6SwvwvA93EP/Ug2e4WAXHu +cbI1NAbUgVDqp+DRdfvZkgYKryjTWd/0+1fS8X1bBZVWzl7eirNVnHbSH2ZDpNuY +0SBd8dj5F6ld3t58ydZbrTHze7JJOd8ijySAp4/kiu9UfZWuTPABzDa/DSdz9Dk/ +zPW4CXXvhLmE02TA9/HeCw3KEHIwicNuEfw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICQ2QwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MDUxODQ2 +MjlaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBzYS1lYXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMMvR+ReRnOzqJzoaPipNTt1Z2VA968jlN1+SYKUrYM3No+Vpz0H +M6Tn0oYB66ByVsXiGc28ulsqX1HbHsxqDPwvQTKvO7SrmDokoAkjJgLocOLUAeld +5AwvUjxGRP6yY90NV7X786MpnYb2Il9DIIaV9HjCmPt+rjy2CZjS0UjPjCKNfB8J +bFjgW6GGscjeyGb/zFwcom5p4j0rLydbNaOr9wOyQrtt3ZQWLYGY9Zees/b8pmcc +Jt+7jstZ2UMV32OO/kIsJ4rMUn2r/uxccPwAc1IDeRSSxOrnFKhW3Cu69iB3bHp7 +JbawY12g7zshE4I14sHjv3QoXASoXjx4xgMCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFI1Fc/Ql2jx+oJPgBVYq +ccgP0pQ8MB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQB4VVVabVp70myuYuZ3vltQIWqSUMhkaTzehMgGcHjMf9iLoZ/I +93KiFUSGnek5cRePyS9wcpp0fcBT3FvkjpUdCjVtdttJgZFhBxgTd8y26ImdDDMR +4+BUuhI5msvjL08f+Vkkpu1GQcGmyFVPFOy/UY8iefu+QyUuiBUnUuEDd49Hw0Fn +/kIPII6Vj82a2mWV/Q8e+rgN8dIRksRjKI03DEoP8lhPlsOkhdwU6Uz9Vu6NOB2Q +Ls1kbcxAc7cFSyRVJEhh12Sz9d0q/CQSTFsVJKOjSNQBQfVnLz1GwO/IieUEAr4C +jkTntH0r1LX5b/GwN4R887LvjAEdTbg1his7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIDAIkHMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkwOTA2MTc0 +MDIxWhcNMjQwODIyMTcwODUwWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldh +c2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIjAgBgNVBAoMGUFtYXpvbiBXZWIg +U2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxJTAjBgNVBAMMHEFt +YXpvbiBSRFMgdXMtd2VzdC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDD2yzbbAl77OofTghDMEf624OvU0eS9O+lsdO0QlbfUfWa1Kd6 +0WkgjkLZGfSRxEHMCnrv4UPBSK/Qwn6FTjkDLgemhqBtAnplN4VsoDL+BkRX4Wwq +/dSQJE2b+0hm9w9UMVGFDEq1TMotGGTD2B71eh9HEKzKhGzqiNeGsiX4VV+LJzdH +uM23eGisNqmd4iJV0zcAZ+Gbh2zK6fqTOCvXtm7Idccv8vZZnyk1FiWl3NR4WAgK +AkvWTIoFU3Mt7dIXKKClVmvssG8WHCkd3Xcb4FHy/G756UZcq67gMMTX/9fOFM/v +l5C0+CHl33Yig1vIDZd+fXV1KZD84dEJfEvHAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBR+ap20kO/6A7pPxo3+ +T3CfqZpQWjAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAHCJky2tPjPttlDM/RIqExupBkNrnSYnOK4kr9xJ3sl8UF2DA +PAnYsjXp3rfcjN/k/FVOhxwzi3cXJF/2Tjj39Bm/OEfYTOJDNYtBwB0VVH4ffa/6 +tZl87jaIkrxJcreeeHqYMnIxeN0b/kliyA+a5L2Yb0VPjt9INq34QDc1v74FNZ17 +4z8nr1nzg4xsOWu0Dbjo966lm4nOYIGBRGOKEkHZRZ4mEiMgr3YLkv8gSmeitx57 +Z6dVemNtUic/LVo5Iqw4n3TBS0iF2C1Q1xT/s3h+0SXZlfOWttzSluDvoMv5PvCd +pFjNn+aXLAALoihL1MJSsxydtsLjOBro5eK0Vw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICOFAwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTAxNzQ2 +MjFaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMiAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAzU72e6XbaJbi4HjJoRNjKxzUEuChKQIt7k3CWzNnmjc5 +8I1MjCpa2W1iw1BYVysXSNSsLOtUsfvBZxi/1uyMn5ZCaf9aeoA9UsSkFSZBjOCN +DpKPCmfV1zcEOvJz26+1m8WDg+8Oa60QV0ou2AU1tYcw98fOQjcAES0JXXB80P2s +3UfkNcnDz+l4k7j4SllhFPhH6BQ4lD2NiFAP4HwoG6FeJUn45EPjzrydxjq6v5Fc +cQ8rGuHADVXotDbEhaYhNjIrsPL+puhjWfhJjheEw8c4whRZNp6gJ/b6WEes/ZhZ +h32DwsDsZw0BfRDUMgUn8TdecNexHUw8vQWeC181hwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUwW9bWgkWkr0U +lrOsq2kvIdrECDgwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAEugF0Gj7HVhX0ehPZoGRYRt3PBuI2YjfrrJRTZ9X5wc +9T8oHmw07mHmNy1qqWvooNJg09bDGfB0k5goC2emDiIiGfc/kvMLI7u+eQOoMKj6 +mkfCncyRN3ty08Po45vTLBFZGUvtQmjM6yKewc4sXiASSBmQUpsMbiHRCL72M5qV +obcJOjGcIdDTmV1BHdWT+XcjynsGjUqOvQWWhhLPrn4jWe6Xuxll75qlrpn3IrIx +CRBv/5r7qbcQJPOgwQsyK4kv9Ly8g7YT1/vYBlR3cRsYQjccw5ceWUj2DrMVWhJ4 +prf+E3Aa4vYmLLOUUvKnDQ1k3RGNu56V0tonsQbfsaM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgICEzUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTAyMDUy +MjVaFw0yNDA4MjIxNzA4NTBaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEoMCYGA1UEAwwfQW1h +em9uIFJEUyBjYS1jZW50cmFsLTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOxHqdcPSA2uBjsCP4DLSlqSoPuQ/X1kkJLusVRKiQE2zayB +viuCBt4VB9Qsh2rW3iYGM+usDjltGnI1iUWA5KHcvHszSMkWAOYWLiMNKTlg6LCp +XnE89tvj5dIH6U8WlDvXLdjB/h30gW9JEX7S8supsBSci2GxEzb5mRdKaDuuF/0O +qvz4YE04pua3iZ9QwmMFuTAOYzD1M72aOpj+7Ac+YLMM61qOtU+AU6MndnQkKoQi +qmUN2A9IFaqHFzRlSdXwKCKUA4otzmz+/N3vFwjb5F4DSsbsrMfjeHMo6o/nb6Nh +YDb0VJxxPee6TxSuN7CQJ2FxMlFUezcoXqwqXD0CAwEAAaNmMGQwDgYDVR0PAQH/ +BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFDGGpon9WfIpsggE +CxHq8hZ7E2ESMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqG +SIb3DQEBCwUAA4IBAQAvpeQYEGZvoTVLgV9rd2+StPYykMsmFjWQcyn3dBTZRXC2 +lKq7QhQczMAOhEaaN29ZprjQzsA2X/UauKzLR2Uyqc2qOeO9/YOl0H3qauo8C/W9 +r8xqPbOCDLEXlOQ19fidXyyEPHEq5WFp8j+fTh+s8WOx2M7IuC0ANEetIZURYhSp +xl9XOPRCJxOhj7JdelhpweX0BJDNHeUFi0ClnFOws8oKQ7sQEv66d5ddxqqZ3NVv +RbCvCtEutQMOUMIuaygDlMn1anSM8N7Wndx8G6+Uy67AnhjGx7jw/0YPPxopEj6x +JXP8j0sJbcT9K/9/fPVLNT25RvQ/93T2+IQL4Ca2 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICYpgwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTExNzMx +NDhaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMk3YdSZ64iAYp6MyyKtYJtNzv7zFSnnNf6vv0FB4VnfITTMmOyZ +LXqKAT2ahZ00hXi34ewqJElgU6eUZT/QlzdIu359TEZyLVPwURflL6SWgdG01Q5X +O++7fSGcBRyIeuQWs9FJNIIqK8daF6qw0Rl5TXfu7P9dBc3zkgDXZm2DHmxGDD69 +7liQUiXzoE1q2Z9cA8+jirDioJxN9av8hQt12pskLQumhlArsMIhjhHRgF03HOh5 +tvi+RCfihVOxELyIRTRpTNiIwAqfZxxTWFTgfn+gijTmd0/1DseAe82aYic8JbuS +EMbrDduAWsqrnJ4GPzxHKLXX0JasCUcWyMECAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPLtsq1NrwJXO13C9eHt +sLY11AGwMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQAnWBKj5xV1A1mYd0kIgDdkjCwQkiKF5bjIbGkT3YEFFbXoJlSP +0lZZ/hDaOHI8wbLT44SzOvPEEmWF9EE7SJzkvSdQrUAWR9FwDLaU427ALI3ngNHy +lGJ2hse1fvSRNbmg8Sc9GBv8oqNIBPVuw+AJzHTacZ1OkyLZrz1c1QvwvwN2a+Jd +vH0V0YIhv66llKcYDMUQJAQi4+8nbRxXWv6Gq3pvrFoorzsnkr42V3JpbhnYiK+9 +nRKd4uWl62KRZjGkfMbmsqZpj2fdSWMY1UGyN1k+kDmCSWYdrTRDP0xjtIocwg+A +J116n4hV/5mbA0BaPiS2krtv17YAeHABZcvz +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgICV2YwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTExOTM2 +MjBaFw0yNDA4MjIxNzA4NTBaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEoMCYGA1UEAwwfQW1h +em9uIFJEUyBldS1jZW50cmFsLTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMEx54X2pHVv86APA0RWqxxRNmdkhAyp2R1cFWumKQRofoFv +n+SPXdkpIINpMuEIGJANozdiEz7SPsrAf8WHyD93j/ZxrdQftRcIGH41xasetKGl +I67uans8d+pgJgBKGb/Z+B5m+UsIuEVekpvgpwKtmmaLFC/NCGuSsJoFsRqoa6Gh +m34W6yJoY87UatddCqLY4IIXaBFsgK9Q/wYzYLbnWM6ZZvhJ52VMtdhcdzeTHNW0 +5LGuXJOF7Ahb4JkEhoo6TS2c0NxB4l4MBfBPgti+O7WjR3FfZHpt18A6Zkq6A2u6 +D/oTSL6c9/3sAaFTFgMyL3wHb2YlW0BPiljZIqECAwEAAaNmMGQwDgYDVR0PAQH/ +BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFOcAToAc6skWffJa +TnreaswAfrbcMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqG +SIb3DQEBCwUAA4IBAQA1d0Whc1QtspK496mFWfFEQNegLh0a9GWYlJm+Htcj5Nxt +DAIGXb+8xrtOZFHmYP7VLCT5Zd2C+XytqseK/+s07iAr0/EPF+O2qcyQWMN5KhgE +cXw2SwuP9FPV3i+YAm11PBVeenrmzuk9NrdHQ7TxU4v7VGhcsd2C++0EisrmquWH +mgIfmVDGxphwoES52cY6t3fbnXmTkvENvR+h3rj+fUiSz0aSo+XZUGHPgvuEKM/W +CBD9Smc9CBoBgvy7BgHRgRUmwtABZHFUIEjHI5rIr7ZvYn+6A0O6sogRfvVYtWFc +qpyrW1YX8mD0VlJ8fGKM3G+aCOsiiPKDV/Uafrm+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgICGAcwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTIxODE5 +NDRaFw0yNDA4MjIxNzA4NTBaMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEmMCQGA1UEAwwdQW1h +em9uIFJEUyBldS1ub3J0aC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCiIYnhe4UNBbdBb/nQxl5giM0XoVHWNrYV5nB0YukA98+TPn9v +Aoj1RGYmtryjhrf01Kuv8SWO+Eom95L3zquoTFcE2gmxCfk7bp6qJJ3eHOJB+QUO +XsNRh76fwDzEF1yTeZWH49oeL2xO13EAx4PbZuZpZBttBM5zAxgZkqu4uWQczFEs +JXfla7z2fvWmGcTagX10O5C18XaFroV0ubvSyIi75ue9ykg/nlFAeB7O0Wxae88e +uhiBEFAuLYdqWnsg3459NfV8Yi1GnaitTym6VI3tHKIFiUvkSiy0DAlAGV2iiyJE +q+DsVEO4/hSINJEtII4TMtysOsYPpINqeEzRAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRR0UpnbQyjnHChgmOc +hnlc0PogzTAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAKJD4xVzSf4zSGTBJrmamo86jl1NHQxXUApAZuBZEc8tqC6TI +T5CeoSr9CMuVC8grYyBjXblC4OsM5NMvmsrXl/u5C9dEwtBFjo8mm53rOOIm1fxl +I1oYB/9mtO9ANWjkykuLzWeBlqDT/i7ckaKwalhLODsRDO73vRhYNjsIUGloNsKe +pxw3dzHwAZx4upSdEVG4RGCZ1D0LJ4Gw40OfD69hfkDfRVVxKGrbEzqxXRvovmDc +tKLdYZO/6REoca36v4BlgIs1CbUXJGLSXUwtg7YXGLSVBJ/U0+22iGJmBSNcoyUN +cjPFD9JQEhDDIYYKSGzIYpvslvGc4T5ISXFiuQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICZIEwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTIyMTMy +MzJaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTIgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBALGiwqjiF7xIjT0Sx7zB3764K2T2a1DHnAxEOr+/EIftWKxWzT3u +PFwS2eEZcnKqSdRQ+vRzonLBeNLO4z8aLjQnNbkizZMBuXGm4BqRm1Kgq3nlLDQn +7YqdijOq54SpShvR/8zsO4sgMDMmHIYAJJOJqBdaus2smRt0NobIKc0liy7759KB +6kmQ47Gg+kfIwxrQA5zlvPLeQImxSoPi9LdbRoKvu7Iot7SOa+jGhVBh3VdqndJX +7tm/saj4NE375csmMETFLAOXjat7zViMRwVorX4V6AzEg1vkzxXpA9N7qywWIT5Y +fYaq5M8i6vvLg0CzrH9fHORtnkdjdu1y+0MCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFOhOx1yt3Z7mvGB9jBv +2ymdZwiOMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQBehqY36UGDvPVU9+vtaYGr38dBbp+LzkjZzHwKT1XJSSUc2wqM +hnCIQKilonrTIvP1vmkQi8qHPvDRtBZKqvz/AErW/ZwQdZzqYNFd+BmOXaeZWV0Q +oHtDzXmcwtP8aUQpxN0e1xkWb1E80qoy+0uuRqb/50b/R4Q5qqSfJhkn6z8nwB10 +7RjLtJPrK8igxdpr3tGUzfAOyiPrIDncY7UJaL84GFp7WWAkH0WG3H8Y8DRcRXOU +mqDxDLUP3rNuow3jnGxiUY+gGX5OqaZg4f4P6QzOSmeQYs6nLpH0PiN00+oS1BbD +bpWdZEttILPI+vAYkU4QuBKKDjJL6HbSd+cn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIDAIVCMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkwOTEzMTcw +NjQxWhcNMjQwODIyMTcwODUwWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldh +c2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIjAgBgNVBAoMGUFtYXpvbiBXZWIg +U2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxJTAjBgNVBAMMHEFt +YXpvbiBSRFMgdXMtZWFzdC0yIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDE+T2xYjUbxOp+pv+gRA3FO24+1zCWgXTDF1DHrh1lsPg5k7ht +2KPYzNc+Vg4E+jgPiW0BQnA6jStX5EqVh8BU60zELlxMNvpg4KumniMCZ3krtMUC +au1NF9rM7HBh+O+DYMBLK5eSIVt6lZosOb7bCi3V6wMLA8YqWSWqabkxwN4w0vXI +8lu5uXXFRemHnlNf+yA/4YtN4uaAyd0ami9+klwdkZfkrDOaiy59haOeBGL8EB/c +dbJJlguHH5CpCscs3RKtOOjEonXnKXldxarFdkMzi+aIIjQ8GyUOSAXHtQHb3gZ4 +nS6Ey0CMlwkB8vUObZU9fnjKJcL5QCQqOfwvAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQUPuRHohPxx4VjykmH +6usGrLL1ETAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAUdR9Vb3y33Yj6X6KGtuthZ08SwjImVQPtknzpajNE5jOJAh8 +quvQnU9nlnMO85fVDU1Dz3lLHGJ/YG1pt1Cqq2QQ200JcWCvBRgdvH6MjHoDQpqZ +HvQ3vLgOGqCLNQKFuet9BdpsHzsctKvCVaeBqbGpeCtt3Hh/26tgx0rorPLw90A2 +V8QSkZJjlcKkLa58N5CMM8Xz8KLWg3MZeT4DmlUXVCukqK2RGuP2L+aME8dOxqNv +OnOz1zrL5mR2iJoDpk8+VE/eBDmJX40IJk6jBjWoxAO/RXq+vBozuF5YHN1ujE92 +tO8HItgTp37XT8bJBAiAnt5mxw+NLSqtxk2QdQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICY4kwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTMyMDEx +NDJaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAr5u9OuLL/OF/fBNUX2kINJLzFl4DnmrhnLuSeSnBPgbb +qddjf5EFFJBfv7IYiIWEFPDbDG5hoBwgMup5bZDbas+ZTJTotnnxVJTQ6wlhTmns +eHECcg2pqGIKGrxZfbQhlj08/4nNAPvyYCTS0bEcmQ1emuDPyvJBYDDLDU6AbCB5 +6Z7YKFQPTiCBblvvNzchjLWF9IpkqiTsPHiEt21sAdABxj9ityStV3ja/W9BfgxH +wzABSTAQT6FbDwmQMo7dcFOPRX+hewQSic2Rn1XYjmNYzgEHisdUsH7eeXREAcTw +61TRvaLH8AiOWBnTEJXPAe6wYfrcSd1pD0MXpoB62wIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUytwMiomQOgX5 +Ichd+2lDWRUhkikwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBACf6lRDpfCD7BFRqiWM45hqIzffIaysmVfr+Jr+fBTjP +uYe/ba1omSrNGG23bOcT9LJ8hkQJ9d+FxUwYyICQNWOy6ejicm4z0C3VhphbTPqj +yjpt9nG56IAcV8BcRJh4o/2IfLNzC/dVuYJV8wj7XzwlvjysenwdrJCoLadkTr1h +eIdG6Le07sB9IxrGJL9e04afk37h7c8ESGSE4E+oS4JQEi3ATq8ne1B9DQ9SasXi +IRmhNAaISDzOPdyLXi9N9V9Lwe/DHcja7hgLGYx3UqfjhLhOKwp8HtoZORixAmOI +HfILgNmwyugAbuZoCazSKKBhQ0wgO0WZ66ZKTMG8Oho= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICUYkwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTYxODIx +MTVaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyB1cy13ZXN0LTIgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANCEZBZyu6yJQFZBJmSUZfSZd3Ui2gitczMKC4FLr0QzkbxY+cLa +uVONIOrPt4Rwi+3h/UdnUg917xao3S53XDf1TDMFEYp4U8EFPXqCn/GXBIWlU86P +PvBN+gzw3nS+aco7WXb+woTouvFVkk8FGU7J532llW8o/9ydQyDIMtdIkKTuMfho +OiNHSaNc+QXQ32TgvM9A/6q7ksUoNXGCP8hDOkSZ/YOLiI5TcdLh/aWj00ziL5bj +pvytiMZkilnc9dLY9QhRNr0vGqL0xjmWdoEXz9/OwjmCihHqJq+20MJPsvFm7D6a +2NKybR9U+ddrjb8/iyLOjURUZnj5O+2+OPcCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEBxMBdv81xuzqcK5TVu +pHj+Aor8MB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQBZkfiVqGoJjBI37aTlLOSjLcjI75L5wBrwO39q+B4cwcmpj58P +3sivv+jhYfAGEbQnGRzjuFoyPzWnZ1DesRExX+wrmHsLLQbF2kVjLZhEJMHF9eB7 +GZlTPdTzHErcnuXkwA/OqyXMpj9aghcQFuhCNguEfnROY9sAoK2PTfnTz9NJHL+Q +UpDLEJEUfc0GZMVWYhahc0x38ZnSY2SKacIPECQrTI0KpqZv/P+ijCEcMD9xmYEb +jL4en+XKS1uJpw5fIU5Sj0MxhdGstH6S84iAE5J3GM3XHklGSFwwqPYvuTXvANH6 +uboynxRgSae59jIlAK6Jrr6GWMwQRbgcaAlW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICEkYwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTYxOTUz +NDdaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMiAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAufodI2Flker8q7PXZG0P0vmFSlhQDw907A6eJuF/WeMo +GHnll3b4S6nC3oRS3nGeRMHbyU2KKXDwXNb3Mheu+ox+n5eb/BJ17eoj9HbQR1cd +gEkIciiAltf8gpMMQH4anP7TD+HNFlZnP7ii3geEJB2GGXSxgSWvUzH4etL67Zmn +TpGDWQMB0T8lK2ziLCMF4XAC/8xDELN/buHCNuhDpxpPebhct0T+f6Arzsiswt2j +7OeNeLLZwIZvVwAKF7zUFjC6m7/VmTQC8nidVY559D6l0UhhU0Co/txgq3HVsMOH +PbxmQUwJEKAzQXoIi+4uZzHFZrvov/nDTNJUhC6DqwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUwaZpaCme+EiV +M5gcjeHZSTgOn4owHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAAR6a2meCZuXO2TF9bGqKGtZmaah4pH2ETcEVUjkvXVz +sl+ZKbYjrun+VkcMGGKLUjS812e7eDF726ptoku9/PZZIxlJB0isC/0OyixI8N4M +NsEyvp52XN9QundTjkl362bomPnHAApeU0mRbMDRR2JdT70u6yAzGLGsUwMkoNnw +1VR4XKhXHYGWo7KMvFrZ1KcjWhubxLHxZWXRulPVtGmyWg/MvE6KF+2XMLhojhUL ++9jB3Fpn53s6KMx5tVq1x8PukHmowcZuAF8k+W4gk8Y68wIwynrdZrKRyRv6CVtR +FZ8DeJgoNZT3y/GT254VqMxxfuy2Ccb/RInd16tEvVk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICOYIwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTcyMDA1 +MjlaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA4dMak8W+XW8y/2F6nRiytFiA4XLwePadqWebGtlIgyCS +kbug8Jv5w7nlMkuxOxoUeD4WhI6A9EkAn3r0REM/2f0aYnd2KPxeqS2MrtdxxHw1 +xoOxk2x0piNSlOz6yog1idsKR5Wurf94fvM9FdTrMYPPrDabbGqiBMsZZmoHLvA3 +Z+57HEV2tU0Ei3vWeGIqnNjIekS+E06KhASxrkNU5vi611UsnYZlSi0VtJsH4UGV +LhnHl53aZL0YFO5mn/fzuNG/51qgk/6EFMMhaWInXX49Dia9FnnuWXwVwi6uX1Wn +7kjoHi5VtmC8ZlGEHroxX2DxEr6bhJTEpcLMnoQMqwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUsUI5Cb3SWB8+ +gv1YLN/ABPMdxSAwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAJAF3E9PM1uzVL8YNdzb6fwJrxxqI2shvaMVmC1mXS+w +G0zh4v2hBZOf91l1EO0rwFD7+fxoI6hzQfMxIczh875T6vUXePKVOCOKI5wCrDad +zQbVqbFbdhsBjF4aUilOdtw2qjjs9JwPuB0VXN4/jY7m21oKEOcnpe36+7OiSPjN +xngYewCXKrSRqoj3mw+0w/+exYj3Wsush7uFssX18av78G+ehKPIVDXptOCP/N7W +8iKVNeQ2QGTnu2fzWsGUSvMGyM7yqT+h1ILaT//yQS8er511aHMLc142bD4D9VSy +DgactwPDTShK/PXqhvNey9v/sKXm4XatZvwcc8KYlW4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICcEUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTgxNjU2 +MjBaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMSAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAndtkldmHtk4TVQAyqhAvtEHSMb6pLhyKrIFved1WO3S7 ++I+bWwv9b2W/ljJxLq9kdT43bhvzonNtI4a1LAohS6bqyirmk8sFfsWT3akb+4Sx +1sjc8Ovc9eqIWJCrUiSvv7+cS7ZTA9AgM1PxvHcsqrcUXiK3Jd/Dax9jdZE1e15s +BEhb2OEPE+tClFZ+soj8h8Pl2Clo5OAppEzYI4LmFKtp1X/BOf62k4jviXuCSst3 +UnRJzE/CXtjmN6oZySVWSe0rQYuyqRl6//9nK40cfGKyxVnimB8XrrcxUN743Vud +QQVU0Esm8OVTX013mXWQXJHP2c0aKkog8LOga0vobQIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQULmoOS1mFSjj+ +snUPx4DgS3SkLFYwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAAkVL2P1M2/G9GM3DANVAqYOwmX0Xk58YBHQu6iiQg4j +b4Ky/qsZIsgT7YBsZA4AOcPKQFgGTWhe9pvhmXqoN3RYltN8Vn7TbUm/ZVDoMsrM +gwv0+TKxW1/u7s8cXYfHPiTzVSJuOogHx99kBW6b2f99GbP7O1Sv3sLq4j6lVvBX +Fiacf5LAWC925nvlTzLlBgIc3O9xDtFeAGtZcEtxZJ4fnGXiqEnN4539+nqzIyYq +nvlgCzyvcfRAxwltrJHuuRu6Maw5AGcd2Y0saMhqOVq9KYKFKuD/927BTrbd2JVf +2sGWyuPZPCk3gq+5pCjbD0c6DkhcMGI6WwxvM5V/zSM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICJDQwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTgxNzAz +MTVaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTMgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL9bL7KE0n02DLVtlZ2PL+g/BuHpMYFq2JnE2RgompGurDIZdjmh +1pxfL3nT+QIVMubuAOy8InRfkRxfpxyjKYdfLJTPJG+jDVL+wDcPpACFVqoV7Prg +pVYEV0lc5aoYw4bSeYFhdzgim6F8iyjoPnObjll9mo4XsHzSoqJLCd0QC+VG9Fw2 +q+GDRZrLRmVM2oNGDRbGpGIFg77aRxRapFZa8SnUgs2AqzuzKiprVH5i0S0M6dWr +i+kk5epmTtkiDHceX+dP/0R1NcnkCPoQ9TglyXyPdUdTPPRfKCq12dftqll+u4mV +ARdN6WFjovxax8EAP2OAUTi1afY+1JFMj+sCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLfhrbrO5exkCVgxW0x3 +Y2mAi8lNMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQAigQ5VBNGyw+OZFXwxeJEAUYaXVoP/qrhTOJ6mCE2DXUVEoJeV +SxScy/TlFA9tJXqmit8JH8VQ/xDL4ubBfeMFAIAo4WzNWDVoeVMqphVEcDWBHsI1 +AETWzfsapRS9yQekOMmxg63d/nV8xewIl8aNVTHdHYXMqhhik47VrmaVEok1UQb3 +O971RadLXIEbVd9tjY5bMEHm89JsZDnDEw1hQXBb67Elu64OOxoKaHBgUH8AZn/2 +zFsL1ynNUjOhCSAA15pgd1vjwc0YsBbAEBPcHBWYBEyME6NLNarjOzBl4FMtATSF +wWCKRGkvqN8oxYhwR2jf2rR5Mu4DWkK5Q8Ep +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICJVUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTkxODE2 +NTNaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyB1cy1lYXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAM3i/k2u6cqbMdcISGRvh+m+L0yaSIoOXjtpNEoIftAipTUYoMhL +InXGlQBVA4shkekxp1N7HXe1Y/iMaPEyb3n+16pf3vdjKl7kaSkIhjdUz3oVUEYt +i8Z/XeJJ9H2aEGuiZh3kHixQcZczn8cg3dA9aeeyLSEnTkl/npzLf//669Ammyhs +XcAo58yvT0D4E0D/EEHf2N7HRX7j/TlyWvw/39SW0usiCrHPKDLxByLojxLdHzso +QIp/S04m+eWn6rmD+uUiRteN1hI5ncQiA3wo4G37mHnUEKo6TtTUh+sd/ku6a8HK +glMBcgqudDI90s1OpuIAWmuWpY//8xEG2YECAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPqhoWZcrVY9mU7tuemR +RBnQIj1jMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQB6zOLZ+YINEs72heHIWlPZ8c6WY8MDU+Be5w1M+BK2kpcVhCUK +PJO4nMXpgamEX8DIiaO7emsunwJzMSvavSPRnxXXTKIc0i/g1EbiDjnYX9d85DkC +E1LaAUCmCZBVi9fIe0H2r9whIh4uLWZA41oMnJx/MOmo3XyMfQoWcqaSFlMqfZM4 +0rNoB/tdHLNuV4eIdaw2mlHxdWDtF4oH+HFm+2cVBUVC1jXKrFv/euRVtsTT+A6i +h2XBHKxQ1Y4HgAn0jACP2QSPEmuoQEIa57bEKEcZsBR8SDY6ZdTd2HLRIApcCOSF +MRM8CKLeF658I0XgF8D5EsYoKPsA+74Z+jDH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJAM2ZN/+nPi27MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkxMDI4MTgwNTU4WhcNMjQxMDI2MTgwNTU4WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgYWYtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR2351uPMZaJk2gMGT+1sk8HE9MQh2rc +/sCnbxGn2p1c7Oi9aBbd/GiFijeJb2BXvHU+TOq3d3Jjqepq8tapXVt4ojbTJNyC +J5E7r7KjTktKdLxtBE1MK25aY+IRJjtdU6vG3KiPKUT1naO3xs3yt0F76WVuFivd +9OHv2a+KHvPkRUWIxpmAHuMY9SIIMmEZtVE7YZGx5ah0iO4JzItHcbVR0y0PBH55 +arpFBddpIVHCacp1FUPxSEWkOpI7q0AaU4xfX0fe1BV5HZYRKpBOIp1TtZWvJD+X +jGUtL1BEsT5vN5g9MkqdtYrC+3SNpAk4VtpvJrdjraI/hhvfeXNnAwIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUEEi/ +WWMcBJsoGXg+EZwkQ0MscZQwHwYDVR0jBBgwFoAUEEi/WWMcBJsoGXg+EZwkQ0Ms +cZQwDQYJKoZIhvcNAQELBQADggEBAGDZ5js5Pc/gC58LJrwMPXFhJDBS8QuDm23C +FFUdlqucskwOS3907ErK1ZkmVJCIqFLArHqskFXMAkRZ2PNR7RjWLqBs+0znG5yH +hRKb4DXzhUFQ18UBRcvT6V6zN97HTRsEEaNhM/7k8YLe7P8vfNZ28VIoJIGGgv9D +wQBBvkxQ71oOmAG0AwaGD0ORGUfbYry9Dz4a4IcUsZyRWRMADixgrFv6VuETp26s +/+z+iqNaGWlELBKh3iQCT6Y/1UnkPLO42bxrCSyOvshdkYN58Q2gMTE1SVTqyo8G +Lw8lLAz9bnvUSgHzB3jRrSx6ggF/WRMRYlR++y6LXP4SAsSAaC0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQTAeFw0xOTEw +MjgxODA2NTNaFw0yNDEwMjgxODA2NTNaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBhZi1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAvtV1OqmFa8zCVQSKOvPUJERLVFtd4rZmDpImc5rIoeBk7w/P +9lcKUJjO8R/w1a2lJXx3oQ81tiY0Piw6TpT62YWVRMWrOw8+Vxq1dNaDSFp9I8d0 +UHillSSbOk6FOrPDp+R6AwbGFqUDebbN5LFFoDKbhNmH1BVS0a6YNKpGigLRqhka +cClPslWtPqtjbaP3Jbxl26zWzLo7OtZl98dR225pq8aApNBwmtgA7Gh60HK/cX0t +32W94n8D+GKSg6R4MKredVFqRTi9hCCNUu0sxYPoELuM+mHiqB5NPjtm92EzCWs+ ++vgWhMc6GxG+82QSWx1Vj8sgLqtE/vLrWddf5QIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuLB4gYVJrSKJj/Gz +pqc6yeA+RcAwHwYDVR0jBBgwFoAUEEi/WWMcBJsoGXg+EZwkQ0MscZQwDQYJKoZI +hvcNAQELBQADggEBABauYOZxUhe9/RhzGJ8MsWCz8eKcyDVd4FCnY6Qh+9wcmYNT +LtnD88LACtJKb/b81qYzcB0Em6+zVJ3Z9jznfr6buItE6es9wAoja22Xgv44BTHL +rimbgMwpTt3uEMXDffaS0Ww6YWb3pSE0XYI2ISMWz+xRERRf+QqktSaL39zuiaW5 +tfZMre+YhohRa/F0ZQl3RCd6yFcLx4UoSPqQsUl97WhYzwAxZZfwvLJXOc4ATt3u +VlCUylNDkaZztDJc/yN5XQoK9W5nOt2cLu513MGYKbuarQr8f+gYU8S+qOyuSRSP +NRITzwCRVnsJE+2JmcRInn/NcanB7uOGqTvJ9+c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJAJYM4LxvTZA6MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkxMDMwMjAyMDM2WhcNMjQxMDI4MjAyMDM2WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgZXUtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqM921jXCXeqpRNCS9CBPOe5N7gMaEt+D +s5uR3riZbqzRlHGiF1jZihkXfHAIQewDwy+Yz+Oec1aEZCQMhUHxZJPusuX0cJfj +b+UluFqHIijL2TfXJ3D0PVLLoNTQJZ8+GAPECyojAaNuoHbdVqxhOcznMsXIXVFq +yVLKDGvyKkJjai/iSPDrQMXufg3kWt0ISjNLvsG5IFXgP4gttsM8i0yvRd4QcHoo +DjvH7V3cS+CQqW5SnDrGnHToB0RLskE1ET+oNOfeN9PWOxQprMOX/zmJhnJQlTqD +QP7jcf7SddxrKFjuziFiouskJJyNDsMjt1Lf60+oHZhed2ogTeifGwIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUFBAF +cgJe/BBuZiGeZ8STfpkgRYQwHwYDVR0jBBgwFoAUFBAFcgJe/BBuZiGeZ8STfpkg +RYQwDQYJKoZIhvcNAQELBQADggEBAKAYUtlvDuX2UpZW9i1QgsjFuy/ErbW0dLHU +e/IcFtju2z6RLZ+uF+5A8Kme7IKG1hgt8s+w9TRVQS/7ukQzoK3TaN6XKXRosjtc +o9Rm4gYWM8bmglzY1TPNaiI4HC7546hSwJhubjN0bXCuj/0sHD6w2DkiGuwKNAef +yTu5vZhPkeNyXLykxkzz7bNp2/PtMBnzIp+WpS7uUDmWyScGPohKMq5PqvL59z+L +ZI3CYeMZrJ5VpXUg3fNNIz/83N3G0sk7wr0ohs/kHTP7xPOYB0zD7Ku4HA0Q9Swf +WX0qr6UQgTPMjfYDLffI7aEId0gxKw1eGYc6Cq5JAZ3ipi/cBFc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQTAeFw0xOTEw +MzAyMDIxMzBaFw0yNDEwMzAyMDIxMzBaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBldS1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAtEyjYcajx6xImJn8Vz1zjdmL4ANPgQXwF7+tF7xccmNAZETb +bzb3I9i5fZlmrRaVznX+9biXVaGxYzIUIR3huQ3Q283KsDYnVuGa3mk690vhvJbB +QIPgKa5mVwJppnuJm78KqaSpi0vxyCPe3h8h6LLFawVyWrYNZ4okli1/U582eef8 +RzJp/Ear3KgHOLIiCdPDF0rjOdCG1MOlDLixVnPn9IYOciqO+VivXBg+jtfc5J+L +AaPm0/Yx4uELt1tkbWkm4BvTU/gBOODnYziITZM0l6Fgwvbwgq5duAtKW+h031lC +37rEvrclqcp4wrsUYcLAWX79ZyKIlRxcAdvEhQIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU7zPyc0azQxnBCe7D +b9KAadH1QSEwHwYDVR0jBBgwFoAUFBAFcgJe/BBuZiGeZ8STfpkgRYQwDQYJKoZI +hvcNAQELBQADggEBAFGaNiYxg7yC/xauXPlaqLCtwbm2dKyK9nIFbF/7be8mk7Q3 +MOA0of1vGHPLVQLr6bJJpD9MAbUcm4cPAwWaxwcNpxOjYOFDaq10PCK4eRAxZWwF +NJRIRmGsl8NEsMNTMCy8X+Kyw5EzH4vWFl5Uf2bGKOeFg0zt43jWQVOX6C+aL3Cd +pRS5MhmYpxMG8irrNOxf4NVFE2zpJOCm3bn0STLhkDcV/ww4zMzObTJhiIb5wSWn +EXKKWhUXuRt7A2y1KJtXpTbSRHQxE++69Go1tWhXtRiULCJtf7wF2Ksm0RR/AdXT +1uR1vKyH5KBJPX3ppYkQDukoHTFR0CpB+G84NLo= +-----END CERTIFICATE----- diff --git a/config/application.rb b/config/application.rb index 1953c5290..c65e46dd0 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,6 +31,33 @@ class Application < Rails::Application config.time_zone = ENV.fetch('OSEM_TIME_ZONE') { 'UTC' } # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true + + # Use SQL instead of Active Record's schema dumper when creating the database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Enforce whitelist mode for mass assignment. + # This will create an empty whitelist of attributes available for mass-assignment for all models + # in your app. As such, your models will need to explicitly whitelist or blacklist accessible + # parameters by using an attr_accessible or attr_protected declaration. + # config.active_record.whitelist_attributes = true + + # Set cache headers + config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=31536000' } + + config.active_job.queue_adapter = :delayed_job + + config.conference = { + events_per_page: ENV.fetch('EVENTS_PER_PAGE', 5).to_i, + default_logo_filename: ENV.fetch('DEFAULT_LOGO_FILENAME', 'snapcon_logo.png'), + default_color: ENV.fetch('DEFAULT_COLOR', '#0B3559') + } + + config.fullcalendar = { + license_key: ENV.fetch('FULL_CALENDAR_LICENSE_KEY', nil) + } + # Don't generate system test files. config.generators.system_tests = nil # This is a nightmare with our current data model, no one ever thought about this. diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 000000000..5aa9c13b4 --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,9 @@ +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun = rerun.strip.gsub /\s/, ' ' +rerun_opts = rerun.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags 'not @wip'" +%> +default: <%= std_opts %> features +wip: --tags @wip:3 --wip features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags 'not @wip' diff --git a/config/database.yml b/config/database.yml index 96ef1936b..9609cd404 100644 --- a/config/database.yml +++ b/config/database.yml @@ -10,15 +10,17 @@ default: &default adapter: <%= ENV.fetch('OSEM_DB_ADAPTER', 'postgresql') %> encoding: <%= encoding %> host: <%= ENV.fetch('OSEM_DB_HOST', 'database') %> - port: <%= ENV.fetch('OSEM_DB_PORT', '5432') %> - username: <%= ENV.fetch('OSEM_DB_USER', 'postgres') %> + port: <%= ENV.fetch('OSEM_DB_PORT', '5432') %> + username: <%= ENV.fetch('OSEM_DB_USER', ENV.fetch('USER', 'postgres')) %> password: <%= ENV.fetch('OSEM_DB_PASSWORD', 'mysecretpassword') %> database: <%= ENV.fetch('OSEM_DB_NAME', 'postgres') %> - pool: 5 - timeout: 5000 + pool: 30 + timeout: 60000 development: <<: *default + host: localhost + # username: <%= ENV['OSEM_DB_USER'] || 'postgres' %> database: osem_development # Warning: The database defined as "test" will be erased and @@ -26,6 +28,7 @@ development: # Do not set this db to the same as development or production. test: <<: *default + host: localhost database: osem_test production: diff --git a/config/deploy.rb b/config/deploy.rb index af9ebaf48..65b9821bd 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -22,7 +22,7 @@ set :shared_dirs, fetch(:shared_dirs, []).push('public/system', '.bundle', 'tmp/pids') set :shared_files, fetch(:shared_files, []).push('.env.production') -desc "Deploys the current version to the server." +desc 'Deploys the current version to the server.' task :deploy do deploy do # Put things that will set up an empty directory into a fully set-up @@ -36,8 +36,8 @@ on :launch do in_path(fetch(:current_path)) do - command %{sudo systemctl restart osem} - command %{sudo systemctl restart osem-dj} + command %(sudo systemctl restart osem) + command %(sudo systemctl restart osem-dj) end end end diff --git a/config/environments/development.rb b/config/environments/development.rb index 55daeeda9..1512c020b 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -3,6 +3,8 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. + config.action_mailer.delivery_method = :letter_opener_web + # In the development environment your application's code is reloaded any time # it changes. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. @@ -62,72 +64,87 @@ # Access all mails sent at http://localhost:3000/letter_opener config.action_mailer.delivery_method = :letter_opener + # Test mailbot settings + config.mailbot = { + ytlf_ticket_id: 50, + bcc_address: 'test@test.com' + } + # Use omniauth mock credentials OmniAuth.config.test_mode = true OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new( - provider: 'facebook', - uid: 'facebook-test-uid-1', - info: { - name: 'facebook user', - email: 'user-facebook@example.com', - username: 'user_facebook' - }, - credentials: { - token: 'fb_mock_token', - secret: 'fb_mock_secret' - } - ) + provider: 'facebook', + uid: 'facebook-test-uid-1', + info: { + name: 'facebook user', + email: 'user-facebook@example.com', + username: 'user_facebook' + }, + credentials: { + token: 'fb_mock_token', + secret: 'fb_mock_secret' + } + ) OmniAuth.config.mock_auth[:google] = OmniAuth::AuthHash.new( - provider: 'google', - uid: 'google-test-uid-1', - info: { - name: 'google user', - email: 'user-google@example.com', - username: 'user_google' - }, - credentials: { - token: 'google_mock_token', - secret: 'google_mock_secret' - } - ) + provider: 'google', + uid: 'google-test-uid-1', + info: { + name: 'google user', + email: 'user-google@example.com', + username: 'user_google' + }, + credentials: { + token: 'google_mock_token', + secret: 'google_mock_secret' + } + ) OmniAuth.config.mock_auth[:suse] = OmniAuth::AuthHash.new( - provider: 'suse', - uid: 'suse-test-uid-1', - info: { - name: 'suse user', - email: 'user-suse@example.com', - username: 'user_suse' - }, - credentials: { - token: 'suse_mock_token', - secret: 'suse_mock_secret' - } - ) + provider: 'suse', + uid: 'suse-test-uid-1', + info: { + name: 'suse user', + email: 'user-suse@example.com', + username: 'user_suse' + }, + credentials: { + token: 'suse_mock_token', + secret: 'suse_mock_secret' + } + ) OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new( - provider: 'github', - uid: 'github-test-uid-1', - info: { - name: 'github user', - email: 'user-github@example.com', - username: 'user_github' - }, - credentials: { - token: 'github_mock_token', - secret: 'github_mock_secret' - } - ) + provider: 'github', + uid: 'github-test-uid-1', + info: { + name: 'github user', + email: 'user-github@example.com', + username: 'user_github' + }, + credentials: { + token: 'github_mock_token', + secret: 'github_mock_secret' + } + ) config.after_initialize do Devise.setup do |devise_config| # Enable ichain test mode devise_config.ichain_test_mode = true end + + Bullet.enable = true + Bullet.bullet_logger = true + Bullet.console = true + Bullet.rails_logger = true + Bullet.add_footer = true + Bullet.skip_html_injection = false end + + config.active_record.verbose_query_logs = true end diff --git a/config/environments/production.rb b/config/environments/production.rb index 06e298b86..59431a74d 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,4 @@ -require "active_support/core_ext/integer/time" +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -20,6 +20,11 @@ # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). # config.require_master_key = true + # TODO-SNAPCON: Figure out if these are necessary. + # config.assets.css_compressor = :sass + # config.assets.js_compressor = Uglifier.new(harmony: true) + # config.assets.gzip = true + # Set the secret_key_base from the env, if not set by any other means config.secret_key_base ||= ENV.fetch('SECRET_KEY_BASE', nil) @@ -43,9 +48,12 @@ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true if ENV['FORCE_SSL'].present? - # Include generic and useful information about system operation, but avoid logging too much - # information to avoid inadvertent exposure of personally identifiable information (PII). - config.log_level = :info + # Disable view rendering logs. + config.action_view.logger = nil + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Prepend all log lines with the following tags. config.log_tags = [:request_id] @@ -53,6 +61,23 @@ # Use a different cache store in production. # config.cache_store = :mem_cache_store + if ENV['OSEM_REDIS_CACHE_STORE'] + config.cache_store = :redis_cache_store, { + url: ENV['OSEM_REDIS_CACHE_STORE'], + reconnect_attempts: 1, # Defaults to 0 + error_handler: lambda do |method:, returning:, exception:| + # Report errors to Sentry as warnings + Raven.capture_exception( + exception, + level: 'warning', + tags: { method: method, returning: returning } + ) + end + } + end + + # Enable serving of images, stylesheets, and JavaScripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true @@ -102,12 +127,12 @@ password: ENV.fetch('OSEM_SMTP_PASSWORD', nil), authentication: ENV.fetch('OSEM_SMTP_AUTHENTICATION', 'plain').try(:to_sym), domain: ENV.fetch('OSEM_SMTP_DOMAIN', nil), - enable_starttls_auto: ENV.fetch('OSEM_SMTP_ENABLE_STARTTLS_AUTO', nil), + enable_starttls_auto: ENV.fetch('OSEM_SMTP_ENABLE_STARTTLS_AUTO', 'false').downcase == 'true', openssl_verify_mode: ENV.fetch('OSEM_SMTP_OPENSSL_VERIFY_MODE', nil) }.compact # Use memcache cluster as cache store in production - if ENV["OSEM_MEMCACHED_SERVERS"] + if ENV['OSEM_MEMCACHED_SERVERS'] config.cache_store = :mem_cache_store, ENV['OSEM_MEMCACHED_SERVERS'].split(','), { username: ENV.fetch('OSEM_MEMCACHED_USERNAME', nil), password: ENV.fetch('OSEM_MEMCACHED_PASSWORD', nil) @@ -116,4 +141,15 @@ # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false + + # Mailbot settings + config.mailbot = { + ytlf_ticket_id: ENV.fetch('YTLF_TICKET_ID', 50), + bcc_address: ENV.fetch('OSEM_MESSAGE_BCC_ADDRESS', nil) + } + + config.after_initialize do + Bullet.enable = false + Bullet.sentry = false + end end diff --git a/config/environments/test.rb b/config/environments/test.rb index 44b816ebb..a7723d22d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -53,7 +53,13 @@ config.after_initialize do # Set Time.now to May 1, 2014 00:01:00 AM (at this instant), but allow it to move forward - t = Time.local(2014, 05, 01, 00, 01, 00) + t = Time.local(2014, 5, 1, 0, 1, 0) Timecop.travel(t) end + + # Test mailbot settings + config.mailbot = { + ytlf_ticket_id: 50, + bcc_address: 'test@test.com' + } end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index fe48fc34e..e472e83a8 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -5,8 +5,9 @@ # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path +Rails.application.config.assets.paths << Rails.root.join('node_modules') # Precompile additional assets. -# application.js, application.css, and all non-JS/CSS in the app/assets -# folder are already added. -# Rails.application.config.assets.precompile += %w( admin.js admin.css ) +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) +# Rails.application.config.assets.precompile += ['mailbot.css'] diff --git a/config/initializers/csp.rb b/config/initializers/csp.rb new file mode 100644 index 000000000..0d61e0e5f --- /dev/null +++ b/config/initializers/csp.rb @@ -0,0 +1,15 @@ +Rails.application.config.content_security_policy do |policy| + # policy.default_src :self, :https + # policy.font_src :self, :https, :data + # policy.img_src :self, :https, :data + # policy.object_src :none + # policy.script_src :self, :https + # policy.style_src :self, :https, :unsafe_inline + # policy.report_uri "/csp-violation-report-endpoint" + # TODO: Use scott helme report-uri + # Determine where this can be put in an