Merge branch 'master' into master

This commit is contained in:
James Mason 2017-10-27 18:26:21 -07:00 • committed by GitHub
commit 7048a37eb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
294 changed files with 10474 additions and 3147 deletions

2
.dockerignore Normal file
View file

@ -0,0 +1,2 @@
Dockerfile
docker-compose.*

4
.gitignore vendored
View file

@ -36,3 +36,7 @@ pickle-email-*.html
.env.development .env.development
.env.test .env.test
.env.local .env.local
docker-compose.env
docker-compose.yml
.DS_Store
.byebug_history

View file

@ -1,6 +1,6 @@
# This configuration was generated by # This configuration was generated by
# `haml-lint --auto-gen-config` # `haml-lint --auto-gen-config`
# on 2017-04-25 04:36:49 +0530 using Haml-Lint version 0.24.0. # on 2017-06-09 04:02:20 +0530 using Haml-Lint version 0.24.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base. # one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
@ -8,9 +8,13 @@
linters: linters:
# Offense count: 952 # Offense count: 945
LineLength: LineLength:
exclude: exclude:
- "app/views/admin/booths/_change_state_dropdown.html.haml"
- "app/views/admin/booths/_form.html.haml"
- "app/views/admin/booths/index.html.haml"
- "app/views/admin/booths/show.html.haml"
- "app/views/admin/campaigns/_form.html.haml" - "app/views/admin/campaigns/_form.html.haml"
- "app/views/admin/campaigns/index.html.haml" - "app/views/admin/campaigns/index.html.haml"
- "app/views/admin/cfps/_form.html.haml" - "app/views/admin/cfps/_form.html.haml"
@ -49,10 +53,11 @@ linters:
- "app/views/admin/events/_voting_index.html.haml" - "app/views/admin/events/_voting_index.html.haml"
- "app/views/admin/events/index.html.haml" - "app/views/admin/events/index.html.haml"
- "app/views/admin/events/registrations.html.haml" - "app/views/admin/events/registrations.html.haml"
- "app/views/admin/events/reports.html.haml"
- "app/views/admin/events/show.html.haml" - "app/views/admin/events/show.html.haml"
- "app/views/admin/lodgings/_form.html.haml" - "app/views/admin/lodgings/_form.html.haml"
- "app/views/admin/lodgings/index.html.haml" - "app/views/admin/lodgings/index.html.haml"
- "app/views/admin/organizations/_form.html.haml"
- "app/views/admin/organizations/index.html.haml"
- "app/views/admin/programs/_form.html.haml" - "app/views/admin/programs/_form.html.haml"
- "app/views/admin/programs/show.html.haml" - "app/views/admin/programs/show.html.haml"
- "app/views/admin/questions/_form.html.haml" - "app/views/admin/questions/_form.html.haml"
@ -73,6 +78,7 @@ linters:
- "app/views/admin/resources/show.html.haml" - "app/views/admin/resources/show.html.haml"
- "app/views/admin/roles/_form.html.haml" - "app/views/admin/roles/_form.html.haml"
- "app/views/admin/roles/_users.html.haml" - "app/views/admin/roles/_users.html.haml"
- "app/views/admin/roles/_users_with_org_admin_role.haml"
- "app/views/admin/roles/index.html.haml" - "app/views/admin/roles/index.html.haml"
- "app/views/admin/roles/show.html.haml" - "app/views/admin/roles/show.html.haml"
- "app/views/admin/rooms/_form.html.haml" - "app/views/admin/rooms/_form.html.haml"
@ -105,6 +111,9 @@ linters:
- "app/views/admin/versions/_object_desc_and_link.html.haml" - "app/views/admin/versions/_object_desc_and_link.html.haml"
- "app/views/admin/versions/index.html.haml" - "app/views/admin/versions/index.html.haml"
- "app/views/admin/volunteers/index.html.haml" - "app/views/admin/volunteers/index.html.haml"
- "app/views/booths/_form.html.haml"
- "app/views/booths/index.html.haml"
- "app/views/booths/show.html.haml"
- "app/views/commercials/edit.html.haml" - "app/views/commercials/edit.html.haml"
- "app/views/commercials/new.html.haml" - "app/views/commercials/new.html.haml"
- "app/views/conference_registrations/_form.html.haml" - "app/views/conference_registrations/_form.html.haml"
@ -112,6 +121,7 @@ linters:
- "app/views/conference_registrations/_registration_info.html.haml" - "app/views/conference_registrations/_registration_info.html.haml"
- "app/views/conference_registrations/_volunteer.html.haml" - "app/views/conference_registrations/_volunteer.html.haml"
- "app/views/conference_registrations/show.html.haml" - "app/views/conference_registrations/show.html.haml"
- "app/views/conferences/_booths.html.haml"
- "app/views/conferences/_call_for_paper.html.haml" - "app/views/conferences/_call_for_paper.html.haml"
- "app/views/conferences/_conference_details.html.haml" - "app/views/conferences/_conference_details.html.haml"
- "app/views/conferences/_gallery.html.haml" - "app/views/conferences/_gallery.html.haml"
@ -143,7 +153,9 @@ linters:
- "app/views/layouts/_admin_sidebar_index.html.haml" - "app/views/layouts/_admin_sidebar_index.html.haml"
- "app/views/layouts/_messages.html.haml" - "app/views/layouts/_messages.html.haml"
- "app/views/layouts/_navigation.html.haml" - "app/views/layouts/_navigation.html.haml"
- "app/views/layouts/_user_menu.html.haml"
- "app/views/layouts/application.html.haml" - "app/views/layouts/application.html.haml"
- "app/views/organizations/index.html.haml"
- "app/views/payments/_payment.html.haml" - "app/views/payments/_payment.html.haml"
- "app/views/proposals/_form.html.haml" - "app/views/proposals/_form.html.haml"
- "app/views/proposals/_proposal_form.html.haml" - "app/views/proposals/_proposal_form.html.haml"
@ -169,11 +181,21 @@ linters:
- "app/views/tickets/index.html.haml" - "app/views/tickets/index.html.haml"
- "app/views/users/edit.html.haml" - "app/views/users/edit.html.haml"
- "app/views/users/show.html.haml" - "app/views/users/show.html.haml"
- "app/views/admin/cfps/index.html.haml"
- "app/views/tracks/_form.html.haml"
- "app/views/tracks/index.html.haml"
- "app/views/tracks/show.html.haml"
- "app/views/conferences/_call_for_tracks.html.haml"
- "app/views/admin/tracks/_change_state_dropdown.html.haml"
- "app/views/proposals/_encouragement_text.html.haml"
- "app/views/admin/schedules/_form.html.haml"
# Offense count: 222 # Offense count: 223
InstanceVariables: InstanceVariables:
exclude: exclude:
- "app/views/admin/booths/_change_state_dropdown.html.haml"
- "app/views/admin/campaigns/_form.html.haml" - "app/views/admin/campaigns/_form.html.haml"
- "app/views/admin/cfps/_booths_cfp.html.haml"
- "app/views/admin/cfps/_form.html.haml" - "app/views/admin/cfps/_form.html.haml"
- "app/views/admin/conferences/_todo_list.html.haml" - "app/views/admin/conferences/_todo_list.html.haml"
- "app/views/admin/difficulty_levels/_form.html.haml" - "app/views/admin/difficulty_levels/_form.html.haml"
@ -184,6 +206,7 @@ linters:
- "app/views/admin/events/_voting.html.haml" - "app/views/admin/events/_voting.html.haml"
- "app/views/admin/events/_voting_index.html.haml" - "app/views/admin/events/_voting_index.html.haml"
- "app/views/admin/lodgings/_form.html.haml" - "app/views/admin/lodgings/_form.html.haml"
- "app/views/admin/organizations/_form.html.haml"
- "app/views/admin/questions/_questions.html.haml" - "app/views/admin/questions/_questions.html.haml"
- "app/views/admin/registration_periods/_form.html.haml" - "app/views/admin/registration_periods/_form.html.haml"
- "app/views/admin/reports/_all_events.html.haml" - "app/views/admin/reports/_all_events.html.haml"
@ -226,6 +249,12 @@ linters:
- "app/views/schedules/_schedule.html.haml" - "app/views/schedules/_schedule.html.haml"
- "app/views/schedules/_schedule_item.html.haml" - "app/views/schedules/_schedule_item.html.haml"
- "app/views/schedules/_schedule_tabs.html.haml" - "app/views/schedules/_schedule_tabs.html.haml"
- "app/views/admin/cfps/_events_cfp.html.haml"
- "app/views/tracks/_form.html.haml"
- "app/views/admin/cfps/_tracks_cfp.html.haml"
- "app/views/conferences/_call_for_tracks.html.haml"
- "app/views/admin/tracks/_change_state_dropdown.html.haml"
- "app/views/admin/schedules/_form.html.haml"
# Offense count: 32 # Offense count: 32
IdNames: IdNames:
@ -244,15 +273,14 @@ linters:
- "app/views/admin/users/_event_registrations.html.haml" - "app/views/admin/users/_event_registrations.html.haml"
- "app/views/admin/users/show.html.haml" - "app/views/admin/users/show.html.haml"
- "app/views/users/edit.html.haml" - "app/views/users/edit.html.haml"
- "app/views/admin/cfps/_events_cfp.html.haml"
- "app/views/admin/cfps/_tracks_cfp.html.haml"
# Offense count: 8 # Offense count: 4
UnnecessaryInterpolation: UnnecessaryInterpolation:
exclude: exclude:
- "app/views/admin/conferences/_doughnut_chart.html.haml" - "app/views/admin/conferences/_doughnut_chart.html.haml"
- "app/views/admin/conferences/_recent_submissions.html.haml" - "app/views/admin/conferences/_recent_submissions.html.haml"
- "app/views/admin/events/reports.html.haml"
- "app/views/admin/reports/_events_with_requirements.html.haml"
- "app/views/admin/reports/_events_without_commercials.html.haml"
- "app/views/proposals/_proposal_form.html.haml" - "app/views/proposals/_proposal_form.html.haml"
- "app/views/proposals/new.html.haml" - "app/views/proposals/new.html.haml"
@ -282,6 +310,7 @@ linters:
SpaceInsideHashAttributes: SpaceInsideHashAttributes:
exclude: exclude:
- "app/views/admin/conferences/_todo_list.html.haml" - "app/views/admin/conferences/_todo_list.html.haml"
- "app/views/admin/emails/index.html.haml"
- "app/views/admin/questions/_form.html.haml" - "app/views/admin/questions/_form.html.haml"
- "app/views/admin/questions/index.html.haml" - "app/views/admin/questions/index.html.haml"
- "app/views/admin/registrations/index.html.haml" - "app/views/admin/registrations/index.html.haml"
@ -327,12 +356,13 @@ linters:
- "app/views/shared/_object_changes.html.haml" - "app/views/shared/_object_changes.html.haml"
- "app/views/tickets/_ticket.html.haml" - "app/views/tickets/_ticket.html.haml"
- "app/views/tickets/index.html.haml" - "app/views/tickets/index.html.haml"
- "app/views/tracks/index.html.haml"
- "app/views/tracks/show.html.haml"
# Offense count: 27 # Offense count: 23
ClassesBeforeIds: ClassesBeforeIds:
exclude: exclude:
- "app/views/admin/emails/index.html.haml" - "app/views/admin/emails/index.html.haml"
- "app/views/admin/events/reports.html.haml"
- "app/views/admin/events/show.html.haml" - "app/views/admin/events/show.html.haml"
- "app/views/admin/reports/index.html.haml" - "app/views/admin/reports/index.html.haml"
- "app/views/admin/users/show.html.haml" - "app/views/admin/users/show.html.haml"
@ -356,6 +386,7 @@ linters:
- "app/views/admin/tracks/show.html.haml" - "app/views/admin/tracks/show.html.haml"
- "app/views/admin/venues/show.html.haml" - "app/views/admin/venues/show.html.haml"
- "app/views/admin/versions/index.html.haml" - "app/views/admin/versions/index.html.haml"
- "app/views/booths/index.html.haml"
- "app/views/conference_registrations/_form.html.haml" - "app/views/conference_registrations/_form.html.haml"
- "app/views/conference_registrations/_ticket.html.haml" - "app/views/conference_registrations/_ticket.html.haml"
- "app/views/conference_registrations/show.html.haml" - "app/views/conference_registrations/show.html.haml"
@ -380,12 +411,27 @@ linters:
# Offense count: 14 # Offense count: 14
ConsecutiveSilentScripts: ConsecutiveSilentScripts:
exclude: exclude:
- "app/views/admin/booths/_change_state_dropdown.html.haml"
- "app/views/admin/events/index.html.haml" - "app/views/admin/events/index.html.haml"
- "app/views/admin/schedules/_day_tab.html.haml" - "app/views/admin/schedules/_day_tab.html.haml"
- "app/views/admin/schedules/_event.html.haml" - "app/views/admin/schedules/_event.html.haml"
- "app/views/admin/versions/_object_desc_and_link.html.haml" - "app/views/admin/versions/_object_desc_and_link.html.haml"
- "app/views/booths/index.html.haml"
- "app/views/schedules/_carousel.html.haml" - "app/views/schedules/_carousel.html.haml"
# Offense count: 29
TrailingWhitespace:
exclude:
- "app/views/admin/organizations/_form.html.haml"
- "app/views/admin/users/index.html.haml"
- "app/views/admin/users/show.html.haml"
- "app/views/admin/volunteers/index.html.haml"
- "app/views/admin/volunteers/show.html.haml"
- "app/views/conference_registrations/_volunteer.html.haml"
- "app/views/devise/passwords/new.html.haml"
- "app/views/payments/new.html.haml"
- "app/views/tickets/index.html.haml"
# Offense count: 2 # Offense count: 2
FinalNewline: FinalNewline:
exclude: exclude:
@ -406,19 +452,6 @@ linters:
- "app/views/schedules/_event.html.haml" - "app/views/schedules/_event.html.haml"
- "app/views/schedules/_schedule_item.html.haml" - "app/views/schedules/_schedule_item.html.haml"
# Offense count: 38
TrailingWhitespace:
exclude:
- "app/views/admin/users/_form.html.haml"
- "app/views/admin/users/index.html.haml"
- "app/views/admin/users/show.html.haml"
- "app/views/admin/volunteers/index.html.haml"
- "app/views/admin/volunteers/show.html.haml"
- "app/views/conference_registrations/_volunteer.html.haml"
- "app/views/devise/passwords/new.html.haml"
- "app/views/payments/new.html.haml"
- "app/views/tickets/index.html.haml"
# Offense count: 7 # Offense count: 7
ClassAttributeWithStaticValue: ClassAttributeWithStaticValue:
exclude: exclude:

View file

@ -13,100 +13,6 @@ AllCops:
- 'config/**/*' - 'config/**/*'
- 'bin/*' - 'bin/*'
#################### Style ###############################
# Align the elements of a hash literal if they span more than one line
Style/AlignHash:
Enabled: true
# Align the parameters of a method call if they span more than one line
Style/AlignParameters:
Enabled: true
# Use && instead of and, use || instead of or
Style/AndOr:
Enabled: true
# Avoid redundunt curly braces when it is obvious that hash is used
Style/BracesAroundHashParameters:
Enabled: true
# Avoid the use of the case equality operator `===`
Style/CaseEquality:
Enabled: true
# Use nested module/class definitions instead of compact style
Style/ClassAndModuleChildren:
Enabled: true
# Checks the . position in multi-line method calls.
Style/DotPosition:
Enabled: true
# Checks for uses of double negation (!!) to convert something to a boolean value.
Style/DoubleNegation:
Enabled: true
# Use one empty line between method definitions
Style/EmptyLineBetweenDefs:
Enabled: true
# There should be only one empty line in designated place
Style/EmptyLines:
Enabled: true
# Keep a blank line before and after private.
Style/EmptyLinesAroundAccessModifier:
Enabled: true
# Use hash literal {} instead of Hash.new
Style/EmptyLiteral:
Enabled: true
# Prefer `each` over `for i`
Style/For:
Enabled: true
# Use the new Ruby 1.9 hash syntax
Style/HashSyntax:
Enabled: true
EnforcedStyle: ruby19
# Checks for uses of if with negated condition. Use unless instead
Style/NegatedIf:
Enabled: true
# Do not compare with nil. Use .nil? instead
Style/NilComparison:
Enabled: true
# Checks for redundant uses of self.
Style/RedundantSelf:
Enabled: true
# Checks that operators have space around them, except for ** which should not have surrounding space.
Style/SpaceAroundOperators:
Enabled: true
# Use single quotes unless there's string interpolation
Style/StringLiterals:
Enabled: true
# This cop checks for tabs where spaces should be used.
Style/Tab:
Enabled: true
# Avoid trailing blank lines
Style/TrailingBlankLines:
Enabled: true
# Avoid trailing whitespace
Style/TrailingWhitespace:
Enabled: true
# This cop checks for numeric comparisons that can be replaced by a predicate method.
Style/ZeroLengthPredicate:
Enabled: true
#################### Metrics ############################### #################### Metrics ###############################
@ -123,77 +29,6 @@ Metrics/ClassLength:
Metrics/BlockLength: Metrics/BlockLength:
Exclude: Exclude:
- 'spec/models/conference_spec.rb' - 'spec/models/conference_spec.rb'
- 'spec/features/ability_spec.rb'
#################### Lint ############################### - 'spec/models/ability_spec.rb'
- 'spec/models/admin_ability_spec.rb'
# Wrap your assignment in condition if you mean it, otherwise it is most likely equality check
Lint/AssignmentInCondition:
Enabled: true
# Align blocks of code properly
Lint/BlockAlignment:
Enabled: true
# Things deprecated in current ruby API
Lint/DeprecatedClassMethods:
Enabled: true
# Do not use literal in conditions. We have it enabled for now
Lint/LiteralInCondition:
Enabled: false
# Prefer `Kernel#loop -> break` over `begin -> while`
Lint/Loop:
Enabled: true
# Do not put space before arguments when they are in parentheses
Lint/ParenthesesAsGroupedExpression:
Enabled: true
# Do not rescue Exceptions class itself
Lint/RescueException:
Enabled: true
# Do not shadow local variables in blocks, choose other name
Lint/ShadowingOuterLocalVariable:
Enabled: true
# Use _ or variable_name to explicitly mark variable as unused
Lint/UnusedBlockArgument:
Enabled: true
# Use _ or _argument_name to explicitly mark argument as unused
Lint/UnusedMethodArgument:
Enabled: true
# Avoid useless assignment
Lint/UselessAssignment:
Enabled: true
# Do not use variables in void context
Lint/Void:
Enabled: true
# Do not use duplicated keys in hash literals.
Lint/DuplicatedKey:
Enabled: true
#################### Rails ###############################
# Enforce Rails specific style
Rails:
Enabled: true
# Avoid use of old-style attribute validation
Rails/Validation:
Enabled: true
# Looks for delegations, that could have been created automatically with delegate method
Rails/Delegate:
Enabled: true
#################### Performance ###############################
# Identifies places where gsub can be replaced by tr or delete.
Performance/StringReplacement:
Enabled: true

File diff suppressed because it is too large Load diff

View file

@ -3,12 +3,12 @@ dist: trusty
language: ruby language: ruby
cache: bundler cache: bundler
rvm: rvm:
- 2.2.3 - 2.4.0
before_install: before_install:
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc" - "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
- "echo `phantomjs -v`" - "echo `phantomjs -v`"
addons: addons:
mariadb: '10.1' mariadb: '10.2'
notifications: notifications:
email: email:
on_success: change on_success: change
@ -24,6 +24,9 @@ before_script:
- mysql -u root -e 'create database osem_test;' - mysql -u root -e 'create database osem_test;'
- RAILS_ENV=test bundle exec rake db:migrate --trace - RAILS_ENV=test bundle exec rake db:migrate --trace
script: script:
- 'bundle exec rubocop -Dc .rubocop.yml' - "./travis_script.sh $TEST_SUITE"
- 'bundle exec haml-lint app/views' env:
- 'bundle exec rspec --color --format documentation' - TEST_SUITE=rspec
- TEST_SUITE=linters
matrix:
fast_finish: true

1
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1 @@
OSEM is part of the openSUSE project. We follow all the [openSUSE Guiding Principles!](http://en.opensuse.org/openSUSE:Guiding_principles) If you think someone doesn't do that, please let us know at maintainers@osem.io

View file

@ -71,59 +71,6 @@ You can access the app [localhost:3000](http://localhost:3000). Whatever you cha
* If you are already a contributor and you get a positive review, you can merge your pull-request yourself * If you are already a contributor and you get a positive review, you can merge your pull-request yourself
* If you are not already a contributor please request a merge via the pull-request comments * If you are not already a contributor please request a merge via the pull-request comments
### Getting Started
* When you get involved with OSEM for the first time, you can choose issues labeled as [Junior]( https://github.com/openSUSE/osem/issues?q=is%3Aissue+is%3Aopen+label%3AJunior)
* Leave a comment on the issue that you want to work on it
* We expect you to work on it and show progress by either opening a PR or commenting on the issue
* If you change your mind, and do not want to work on the issue any more, please be fair to others and leave a comment to let us know
* Do **not** work on issues that are assigned to others. If you are uncertain, ask and wait for a **contributor** to reply
* Avoid working on issues that have no label
* If you have opened a new issue, please wait for a contributor to add relevant labels
* If an issue is a feature, we should first have a rough idea on how we want to implement it
* If there is already such a discussion on the issue, you can go ahead and pick this up
* If not, please first leave a comment on how you want to implement it and wait for contributors' feedback
### Commits
* Commit title should be short and descriptive
* title (or summary line) is the first line of the commit message
* that says what the commit is doing
* in no more than 50 characters
* starting with a word like 'Fix' or 'Add' or 'Change'
* **without** a period (.) at the end
* followed by a blank line
* Commit messages are
* up to 72 characters
* with break lines
* Reference the issue(s) the commit closes
* https://help.github.com/articles/closing-issues-via-commit-messages
* If you haven't done so since the beginning, you should reference the issue when you squash your commits
### Pull Requests workflow
Please open a pull request (PR) only when you have finished coding, and your changes are ready to be reviewed for merging.
* Title
* Include a comprehensive title about what this PR is doing
* Referencing the issue number on the PR title is not giving any information about what this PR is about
* The title should be short (50 characters maximum); you can add more information in the description
* Description
* Add a couple of lines about what is the problem you are trying to solve and how you have addressed it
* Add bullet points about the new things you are introducing, if applicable
* Reference the issue(s) you are solving
* Add a screenshot of your change, if you are working on something that changes how the app looks like
* Automated checks
* We automatically run the [test suite](https://github.com/openSUSE/osem/blob/master/CONTRIBUTING.md#test-suite) and security checks on every PR
* Check back later to see if all checks were successful, if not, address them or leave a comment to ask for help
* Pushing new changes to your PR
* Always add **new** commits; this tremendously helps reviewers
* Do not squash commits, unless explicitly requested by the reviewer
* Take care of your PR
* Make sure you check the status of your PR regularly
* Address your reviews, make the necessary changes, ask if something is not clear to you
* Rebase against newest changes, when needed; we cannot properly review PRs that are not rebased
Reviewing your PR might take some time, as we are all volunteers. Please be responsive and respectful.
### Coding Style ### Coding Style
We are using [rubocop](https://github.com/bbatsov/rubocop) as a style checker. It is checking code style each time the test suite runs. You can run it locally with We are using [rubocop](https://github.com/bbatsov/rubocop) as a style checker. It is checking code style each time the test suite runs. You can run it locally with
@ -141,16 +88,6 @@ We are using [rspec](http://rspec.info/)+[capybara](http://jnicklas.github.io/ca
vagrant exec bundle exec rspec vagrant exec bundle exec rspec
``` ```
### Review App of your PR
OSEM uses [Review Apps](https://devcenter.heroku.com/articles/github-integration-review-apps) on Heroku.
* The review app can be manually created by a maintainer, and when that happens you will see a relevant message in the PR
* Please help reviewers by adding the necessary data relevant to your PR,
eg. if your PR is doing something related to conference registrations, go to the review app and make sure there is a conference with registrations.
### Email Notifications ### Email Notifications
**Note**: We use [letter_opener](https://github.com/ryanb/letter_opener) in development environment. You can check out your mails by visiting [localhost:3000/letter_opener](http://localhost:3000/letter_opener). **Note**: We use [letter_opener](https://github.com/ryanb/letter_opener) in development environment. You can check out your mails by visiting [localhost:3000/letter_opener](http://localhost:3000/letter_opener).
@ -181,44 +118,6 @@ OSEM_GITHUB_SECRET='sample'
If you don't already have a `.env` file you can use the `dotenv.example` as a template. If you don't already have a `.env` file you can use the `dotenv.example` as a template.
## Labels for issues and PRs
...and what they mean!
1. **Bug**
* A bug in the application, something is wrong and needs to be fixed!
* Ideally the issue includes details on how to reproduce the bug
* Reproduce the bug in master branch, and send a PR that solves it
2. **Design**
* Related to the looks and/or usability of the application; needs attention from someone who understands front-end and UX
* If you are good with graphics and design, give it a shot!
3. **Documentation**
* Related to the documentation of our application, eg our INSTALL.md file or a wiki page with instructions on how to use the app, or part of it.
* If you are working on a documentation issue, make sure you are covering all cases.
4. **Epic**
* We may, or may not, solve this, thus it is epic. It's bigger than a feature request, because it fundamentally changes or affects the app, or a significant part of it.
* Do **not** work on this without prior discussion with the maintainers, it's called epic for a reason!
5. **Feature**
* This is a new feature for something new in the app!
* If an issue is labeled *Feature*, don't work on the issue, unless the maintainers have decided on how to proceed
* Ideally, leave a comment with your proposed solution in the issue and wait for feedback
6. **Grooming**
* This is working, but could look better, thus needs some attention and grooming.
7. **Hacktoberfest**
* This is for the issues included in the coding event of Hacktoberfest. You can ignore it, when the event is not on
8. **in progress**
9. **Junior**
* For new comers! RoR beginners or people unfamiliar with the application. Where you must start if you are interested in a mentoring program we participate in.
10. **need feedback**
* Maintainers' attention is needed to decide if this is something we want in the app, and/or how it should be implemented
11. **Operation**
12. **ready**
13. **Refactorization**
* Our code needs to be re-written; to avoid code duplication, or make the code more readable, or do things in a simpler way!
14. **Research**
* Ideas to explore; and think if there is anything we want to include in our app.
15. **GSoC**
* To group all the issues and PRs related to Google Summer of Code together.
## Code of Conduct ## Code of Conduct
OSEM is part of the openSUSE project. We follow all the [openSUSE Guiding Principles!](http://en.opensuse.org/openSUSE:Guiding_principles) If you think someone doesn't do that, please let us know at maintainers@osem.io OSEM is part of the openSUSE project. We follow all the [openSUSE Guiding Principles!](http://en.opensuse.org/openSUSE:Guiding_principles) If you think someone doesn't do that, please let us know at maintainers@osem.io

45
Dockerfile Normal file
View file

@ -0,0 +1,45 @@
FROM ruby:2.3
MAINTAINER TheAssassin <theassassin@users.noreply.github.com>
# required for compiling assets
RUN apt-get update && \
apt-get install -y nodejs nodejs-legacy mariadb-client imagemagick
# used to run the container without root permissions
RUN adduser --home /osem/ --system --group --disabled-login --disabled-password osem
# required to detect when the database is up and running in init.sh
RUN cd /usr/bin && \
wget https://github.com/jwilder/dockerize/releases/download/v0.3.0/dockerize-linux-amd64-v0.3.0.tar.gz -O dockerize.tar.gz && \
echo "36e8319cdf9d2b07340f456ec61cfa0f495ec6c130b02ad9c116fd55a5c43fa1 dockerize.tar.gz" | sha256sum -c && \
tar -xf dockerize.tar.gz && \
rm dockerize.tar.gz
# explicitly add Gemfile and install dependencies using bundler to make use of
# Docker's caching
WORKDIR /osem/
COPY Gemfile /osem/
COPY Gemfile.lock /osem/
RUN bundle install --without test development
# add OSEM files and prepare them for use inside a Docker container
COPY . /osem/
RUN chown osem.osem /osem/ -R && \
mv /osem/config/database.yml.docker /osem/config/database.yml
# data directory is used to cache the secret key in a file
ENV DATA_DIR /data
RUN install -d -m 0700 -o osem $DATA_DIR
VOLUME ["$DATA_DIR"]
USER osem
EXPOSE 9292
COPY docker/init.sh /init.sh
# a user could override this if they wanted to serve the static files directly
# from a webserver
ENV RAILS_SERVE_STATIC_FILES 1
CMD ["bash", "/init.sh"]

25
Gemfile
View file

@ -6,7 +6,7 @@ if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('1.8.4')
end end
# as web framework # as web framework
gem 'rails', '~> 4.2' gem 'rails', '~> 4.2.8'
# enables serving assets in production and setting your logger to standard out # enables serving assets in production and setting your logger to standard out
# both of which are required to run an application on a twelve-factor provider # both of which are required to run an application on a twelve-factor provider
@ -18,10 +18,9 @@ gem 'rails_12factor', group: :production
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
# as the database for Active Record # as the database for Active Record
# choose only one
gem 'mysql2' gem 'mysql2'
# gem 'pg'
# for observing records
gem 'rails-observers'
# for tracking data changes # for tracking data changes
gem 'paper_trail' gem 'paper_trail'
@ -58,7 +57,7 @@ gem 'unobtrusive_flash', '>=3'
gem 'transitions', :require => %w( transitions active_record/transitions ) gem 'transitions', :require => %w( transitions active_record/transitions )
# for comments # for comments
gem 'awesome_nested_set', '~> 3.0.0.rc.5' gem 'awesome_nested_set', '~> 3.1.3'
gem 'acts_as_commentable_with_threading' gem 'acts_as_commentable_with_threading'
# as templating language # as templating language
@ -84,6 +83,8 @@ gem 'jquery-ui-rails', '~> 4.2.1'
# for languages validation # for languages validation
gem 'iso-639' gem 'iso-639'
gem 'thor', '0.19.1'
# frontend javascripts # frontend javascripts
source 'https://rails-assets.org' do source 'https://rails-assets.org' do
# for placeholder images # for placeholder images
@ -126,8 +127,11 @@ gem 'country_select'
# as PDF generator # as PDF generator
gem 'prawn_rails' gem 'prawn_rails'
gem 'rqrcode'
gem 'prawn-qrcode', '~> 0.2.2.1'
# to render XLS spreadsheets # to render XLS spreadsheets
gem 'axlsx', git: 'https://github.com/randym/axlsx.git'
gem 'axlsx_rails' gem 'axlsx_rails'
# as error catcher # as error catcher
@ -196,14 +200,19 @@ gem 'sprockets-rails'
# for multiple speakers select on proposal/event forms # for multiple speakers select on proposal/event forms
gem 'selectize-rails' gem 'selectize-rails'
# Nokogiri < 1.8.1 is subject to:
# CVE-2017-0663, CVE-2017-7375, CVE-2017-7376, CVE-2017-9047, CVE-2017-9048,
# CVE-2017-9049, CVE-2017-9050
gem 'nokogiri', '>= 1.8.1'
# Use guard and spring for testing in development # Use guard and spring for testing in development
group :development do group :development do
# to launch specs when files are modified # to launch specs when files are modified
gem 'guard-rspec', '~> 4.2.8' gem 'guard-rspec'
gem 'spring-commands-rspec' gem 'spring-commands-rspec'
gem 'haml_lint', '~> 0.24.0' gem 'haml_lint', '~> 0.24.0'
# for static code analisys # for static code analisys
gem 'rubocop', '~> 0.48.1', require: false gem 'rubocop', '~> 0.49.0', require: false
# as database # as database
gem 'sqlite3' gem 'sqlite3'
# to open mails # to open mails
@ -218,7 +227,7 @@ end
group :test do group :test do
# as test framework # as test framework
gem 'rspec-rails' gem 'rspec-rails', '~> 3.5', '>= 3.5.2'
gem 'database_cleaner' gem 'database_cleaner'
gem 'capybara' gem 'capybara'
gem 'poltergeist' gem 'poltergeist'

View file

@ -1,41 +1,50 @@
GIT
remote: https://github.com/randym/axlsx.git
revision: c8ac844572b25fda358cc01d2104720c4c42f450
specs:
axlsx (2.1.0.pre)
htmlentities (~> 4.3.4)
mimemagic (~> 0.3)
nokogiri (>= 1.6.6)
rubyzip (>= 1.2.1)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
remote: https://rails-assets.org/ remote: https://rails-assets.org/
specs: specs:
actionmailer (4.2.7.1) actionmailer (4.2.9)
actionpack (= 4.2.7.1) actionpack (= 4.2.9)
actionview (= 4.2.7.1) actionview (= 4.2.9)
activejob (= 4.2.7.1) activejob (= 4.2.9)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.7.1) actionpack (4.2.9)
actionview (= 4.2.7.1) actionview (= 4.2.9)
activesupport (= 4.2.7.1) activesupport (= 4.2.9)
rack (~> 1.6) rack (~> 1.6)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.7.1) actionview (4.2.9)
activesupport (= 4.2.7.1) activesupport (= 4.2.9)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_serializers (0.9.4) active_model_serializers (0.9.4)
activemodel (>= 3.2) activemodel (>= 3.2)
activejob (4.2.7.1) activejob (4.2.9)
activesupport (= 4.2.7.1) activesupport (= 4.2.9)
globalid (>= 0.3.0) globalid (>= 0.3.0)
activemodel (4.2.7.1) activemodel (4.2.9)
activesupport (= 4.2.7.1) activesupport (= 4.2.9)
builder (~> 3.1) builder (~> 3.1)
activerecord (4.2.7.1) activerecord (4.2.9)
activemodel (= 4.2.7.1) activemodel (= 4.2.9)
activesupport (= 4.2.7.1) activesupport (= 4.2.9)
arel (~> 6.0) arel (~> 6.0)
activesupport (4.2.7.1) activesupport (4.2.9)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4) thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1) tzinfo (~> 1.1)
@ -48,7 +57,8 @@ GEM
awesome_nested_set (>= 2.0) awesome_nested_set (>= 2.0)
acts_as_list (0.4.0) acts_as_list (0.4.0)
activerecord (>= 3.0) activerecord (>= 3.0)
addressable (2.3.6) addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
ahoy_matey (1.0.0) ahoy_matey (1.0.0)
addressable addressable
browser (>= 0.4.0) browser (>= 0.4.0)
@ -57,18 +67,13 @@ GEM
request_store request_store
user_agent_parser user_agent_parser
uuidtools uuidtools
arel (6.0.3) arel (6.0.4)
ast (2.3.0) ast (2.3.0)
autoprefixer-rails (5.1.9) autoprefixer-rails (7.1.1)
execjs execjs
json awesome_nested_set (3.1.3)
awesome_nested_set (3.0.0.rc.5) activerecord (>= 4.0.0, < 5.2)
activerecord (>= 4.0.0, < 5)
aws_cf_signer (0.1.3) aws_cf_signer (0.1.3)
axlsx (2.0.1)
htmlentities (~> 4.3.1)
nokogiri (>= 1.4.1)
rubyzip (~> 1.2.1)
axlsx_rails (0.2.0) axlsx_rails (0.2.0)
axlsx (>= 2.0.1) axlsx (>= 2.0.1)
rails (>= 3.1) rails (>= 3.1)
@ -82,10 +87,8 @@ GEM
bootstrap3-datetimepicker-rails (3.0.3) bootstrap3-datetimepicker-rails (3.0.3)
momentjs-rails (>= 2.8.1) momentjs-rails (>= 2.8.1)
browser (0.6.0) browser (0.6.0)
builder (3.2.2) builder (3.2.3)
byebug (3.1.2) byebug (9.0.6)
columnize (~> 0.8)
debugger-linecache (~> 1.2)
cancancan (1.13.1) cancancan (1.13.1)
capybara (2.6.2) capybara (2.6.2)
addressable addressable
@ -103,28 +106,24 @@ GEM
activesupport (>= 3.2.0) activesupport (>= 3.2.0)
carrierwave carrierwave
fastimage fastimage
celluloid (0.15.2)
timers (~> 1.1.0)
celluloid-io (0.15.0)
celluloid (>= 0.15.0)
nio4r (>= 0.5.0)
chart-js-rails (0.0.6) chart-js-rails (0.0.6)
railties (> 3.1) railties (> 3.1)
chronic (0.10.2) chronic (0.10.2)
chunky_png (1.3.8)
cliver (0.3.2) cliver (0.3.2)
cloudinary (1.1.6) cloudinary (1.1.6)
aws_cf_signer aws_cf_signer
rest-client rest-client
cocoon (1.2.6) cocoon (1.2.6)
coderay (1.1.0) coderay (1.1.1)
coffee-rails (4.1.1) coffee-rails (4.2.2)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.1.x) railties (>= 4.0.0)
coffee-script (2.4.1) coffee-script (2.4.1)
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.10.0) coffee-script-source (1.12.2)
columnize (0.8.9) concurrent-ruby (1.0.5)
countable-rails (0.0.1) countable-rails (0.0.1)
railties (>= 3.1) railties (>= 3.1)
countries (1.2.5) countries (1.2.5)
@ -139,18 +138,17 @@ GEM
term-ansicolor (~> 1.3) term-ansicolor (~> 1.3)
thor (~> 0.19.1) thor (~> 0.19.1)
tins (~> 1.6.0) tins (~> 1.6.0)
crack (0.4.2) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
currencies (0.4.2) currencies (0.4.2)
daemons (1.1.9) daemons (1.1.9)
dante (0.2.0) dante (0.2.0)
database_cleaner (1.3.0) database_cleaner (1.3.0)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-linecache (1.2.0) delayed_job (4.1.3)
delayed_job (4.1.1) activesupport (>= 3.0, < 5.2)
activesupport (>= 3.0, < 5.0) delayed_job_active_record (4.1.2)
delayed_job_active_record (4.1.0) activerecord (>= 3.0, < 5.2)
activerecord (>= 3.0, < 5)
delayed_job (>= 3.0, < 5) delayed_job (>= 3.0, < 5)
devise (4.2.0) devise (4.2.0)
bcrypt (~> 3.0) bcrypt (~> 3.0)
@ -160,14 +158,14 @@ GEM
warden (~> 1.2.3) warden (~> 1.2.3)
devise_ichain_authenticatable (0.3.1) devise_ichain_authenticatable (0.3.1)
devise (>= 2.2) devise (>= 2.2)
diff-lcs (1.2.5) diff-lcs (1.3)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20160310) domain_name (0.5.20160310)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
dotenv (2.1.1) dotenv (2.2.1)
dotenv-rails (2.1.1) dotenv-rails (2.2.1)
dotenv (= 2.1.1) dotenv (= 2.2.1)
railties (>= 4.0, < 5.1) railties (>= 3.2, < 5.2)
erubis (2.7.0) erubis (2.7.0)
execjs (2.6.0) execjs (2.6.0)
factory_girl (4.5.0) factory_girl (4.5.0)
@ -181,27 +179,32 @@ GEM
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
fastimage (2.0.0) fastimage (2.0.0)
addressable (~> 2) addressable (~> 2)
ffi (1.9.3) ffi (1.9.18)
font-awesome-rails (4.1.0.0) font-awesome-rails (4.7.0.2)
railties (>= 3.2, < 5.0) railties (>= 3.2, < 5.2)
formatador (0.2.4) formatador (0.2.5)
formtastic (3.1.3) formtastic (3.1.3)
actionpack (>= 3.2.13) actionpack (>= 3.2.13)
formtastic-bootstrap (3.1.1) formtastic-bootstrap (3.1.1)
formtastic (>= 3.0) formtastic (>= 3.0)
geocoder (1.2.2) geocoder (1.2.2)
globalid (0.3.6) globalid (0.4.0)
activesupport (>= 4.1.0) activesupport (>= 4.2.0)
gravtastic (3.2.6) gravtastic (3.2.6)
guard (2.6.0) guard (2.14.1)
formatador (>= 0.2.4) formatador (>= 0.2.4)
listen (~> 2.7) listen (>= 2.7, < 4.0)
lumberjack (~> 1.0) lumberjack (~> 1.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12) pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1) thor (>= 0.18.1)
guard-rspec (4.2.8) guard-compat (1.2.1)
guard-rspec (4.7.3)
guard (~> 2.1) guard (~> 2.1)
rspec (>= 2.14, < 4.0) guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
haml (4.0.5) haml (4.0.5)
tilt tilt
haml-rails (0.5.3) haml-rails (0.5.3)
@ -215,8 +218,8 @@ GEM
rake (>= 10, < 13) rake (>= 10, < 13)
rubocop (>= 0.47.0) rubocop (>= 0.47.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hashie (2.1.1) hashdiff (0.3.4)
hike (1.2.3) hashie (3.5.5)
hoptoad_notifier (2.4.11) hoptoad_notifier (2.4.11)
activesupport activesupport
builder builder
@ -225,14 +228,15 @@ GEM
domain_name (~> 0.5) domain_name (~> 0.5)
i18n (0.7.0) i18n (0.7.0)
i18n_data (0.7.0) i18n_data (0.7.0)
inversion (0.12.3) inversion (1.0.0)
loggability (~> 0.4) loggability (~> 0.12)
iso-639 (0.2.5) iso-639 (0.2.5)
jquery-datatables-rails (2.2.3) jquery-datatables-rails (2.2.3)
jquery-rails jquery-rails
sass-rails sass-rails
jquery-rails (3.1.4) jquery-rails (4.3.1)
railties (>= 3.0, < 5.0) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
jquery-ui-rails (4.2.1) jquery-ui-rails (4.2.1)
railties (>= 3.2.16) railties (>= 3.2.16)
@ -249,25 +253,25 @@ GEM
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
listen (2.7.2) listen (3.1.5)
celluloid (>= 0.15.2) rb-fsevent (~> 0.9, >= 0.9.4)
celluloid-io (>= 0.15.0) rb-inotify (~> 0.9, >= 0.9.7)
rb-fsevent (>= 0.9.3) ruby_dep (~> 1.2)
rb-inotify (>= 0.9) loggability (0.14.0)
loggability (0.11.0)
loofah (2.0.3) loofah (2.0.3)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lumberjack (1.0.5) lumberjack (1.0.12)
mail (2.6.3) mail (2.6.6)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 4)
method_source (0.8.2) method_source (0.8.2)
mime-types (2.99.1) mime-types (2.99.3)
mimemagic (0.3.2)
mina (0.3.8) mina (0.3.8)
open4 (~> 1.3.4) open4 (~> 1.3.4)
rake rake
mini_magick (4.5.1) mini_magick (4.5.1)
mini_portile2 (2.1.0) mini_portile2 (2.3.0)
minitest (5.10.1) minitest (5.10.2)
momentjs-rails (2.8.1) momentjs-rails (2.8.1)
railties (>= 3.1) railties (>= 3.1)
monetize (1.4.0) monetize (1.4.0)
@ -280,23 +284,26 @@ GEM
monetize (~> 1.4.0) monetize (~> 1.4.0)
money (~> 6.7) money (~> 6.7)
railties (>= 3.0) railties (>= 3.0)
multi_json (1.11.2) multi_json (1.12.1)
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (2.0.0) multipart-post (2.0.0)
mysql2 (0.4.2) mysql2 (0.4.9)
nenv (0.3.0)
netrc (0.11.0) netrc (0.11.0)
nio4r (1.2.1) nokogiri (1.8.1)
nokogiri (1.7.1) mini_portile2 (~> 2.3.0)
mini_portile2 (~> 2.1.0) notiffany (0.1.1)
oauth2 (0.9.4) nenv (~> 0.1)
faraday (>= 0.8, < 0.10) shellany (~> 0.0)
oauth2 (1.3.1)
faraday (>= 0.8, < 0.12)
jwt (~> 1.0) jwt (~> 1.0)
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (~> 1.2) rack (>= 1.2, < 3)
omniauth (1.2.1) omniauth (1.6.1)
hashie (>= 1.2, < 3) hashie (>= 3.4.6, < 3.6.0)
rack (~> 1.0) rack (>= 1.6.2, < 3)
omniauth-facebook (1.6.0) omniauth-facebook (1.6.0)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
omniauth-github (1.1.2) omniauth-github (1.1.2)
@ -305,20 +312,19 @@ GEM
omniauth-google-oauth2 (0.2.4) omniauth-google-oauth2 (0.2.4)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
omniauth-oauth2 (1.1.2) omniauth-oauth2 (1.4.0)
faraday (>= 0.8, < 0.10) oauth2 (~> 1.0)
multi_json (~> 1.3)
oauth2 (~> 0.9.3)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-openid (1.0.1) omniauth-openid (1.0.1)
omniauth (~> 1.0) omniauth (~> 1.0)
rack-openid (~> 1.3.1) rack-openid (~> 1.3.1)
open4 (1.3.4) open4 (1.3.4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
paper_trail (5.0.1) paper_trail (5.2.1)
activerecord (>= 3.0, < 6.0) activerecord (>= 3.0, < 6.0)
activesupport (>= 3.0, < 6.0) activesupport (>= 3.0, < 6.0)
request_store (~> 1.1) request_store (~> 1.1)
parallel (1.11.2)
parser (2.4.0.0) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
pdf-core (0.2.5) pdf-core (0.2.5)
@ -336,29 +342,33 @@ GEM
prawn (1.0.0) prawn (1.0.0)
pdf-core (~> 0.2.2) pdf-core (~> 0.2.2)
ttfunk (~> 1.1.1) ttfunk (~> 1.1.1)
prawn-qrcode (0.2.2.1)
prawn (>= 0.11.1)
rqrcode (>= 0.4.1)
prawn_rails (0.0.11) prawn_rails (0.0.11)
prawn (>= 0.11.1) prawn (>= 0.11.1)
railties (>= 3.0.0) railties (>= 3.0.0)
pry (0.9.12.6) pry (0.10.4)
coderay (~> 1.0) coderay (~> 1.1.0)
method_source (~> 0.8) method_source (~> 0.8.1)
slop (~> 3.4) slop (~> 3.4)
rack (1.6.4) public_suffix (2.0.5)
rack (1.6.8)
rack-openid (1.3.1) rack-openid (1.3.1)
rack (>= 1.1.0) rack (>= 1.1.0)
ruby-openid (>= 2.1.8) ruby-openid (>= 2.1.8)
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.7.1) rails (4.2.9)
actionmailer (= 4.2.7.1) actionmailer (= 4.2.9)
actionpack (= 4.2.7.1) actionpack (= 4.2.9)
actionview (= 4.2.7.1) actionview (= 4.2.9)
activejob (= 4.2.7.1) activejob (= 4.2.9)
activemodel (= 4.2.7.1) activemodel (= 4.2.9)
activerecord (= 4.2.7.1) activerecord (= 4.2.9)
activesupport (= 4.2.7.1) activesupport (= 4.2.9)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.2.7.1) railties (= 4.2.9)
sprockets-rails sprockets-rails
rails-assets-bootstrap (3.3.6) rails-assets-bootstrap (3.3.6)
rails-assets-jquery (>= 1.9.1, < 3) rails-assets-jquery (>= 1.9.1, < 3)
@ -388,65 +398,68 @@ GEM
rails-i18n (4.0.8) rails-i18n (4.0.8)
i18n (~> 0.7) i18n (~> 0.7)
railties (~> 4.0) railties (~> 4.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
rails_12factor (0.0.3) rails_12factor (0.0.3)
rails_serve_static_assets rails_serve_static_assets
rails_stdout_logging rails_stdout_logging
rails_serve_static_assets (0.0.4) rails_serve_static_assets (0.0.4)
rails_stdout_logging (0.0.3) rails_stdout_logging (0.0.3)
railties (4.2.7.1) railties (4.2.9)
actionpack (= 4.2.7.1) actionpack (= 4.2.9)
activesupport (= 4.2.7.1) activesupport (= 4.2.9)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.2.1) rainbow (2.2.2)
rake (10.5.0) rake
rb-fsevent (0.9.4) rake (12.0.0)
rb-inotify (0.9.4) rb-fsevent (0.10.2)
ffi (>= 0.5.0) rb-inotify (0.9.10)
rdoc (4.1.1) ffi (>= 0.5.0, < 2)
json (~> 1.4) rdoc (5.1.0)
rdoc-generator-fivefish (0.1.0) rdoc-generator-fivefish (0.3.0)
inversion (~> 0.12) inversion (~> 1.0)
loggability (~> 0.6) loggability (~> 0.12)
rdoc (~> 4.0) rdoc (~> 5.0)
yajl-ruby (~> 1.1) yajl-ruby (~> 1.3)
redcarpet (3.2.3) redcarpet (3.2.3)
referer-parser (0.2.1) referer-parser (0.2.1)
request_store (1.1.0) request_store (1.1.0)
responders (2.1.1) responders (2.4.0)
railties (>= 4.2.0, < 5.1) actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3)
rest-client (1.8.0) rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0) mime-types (>= 1.16, < 3.0)
netrc (~> 0.7) netrc (~> 0.7)
rolify (5.1.0) rolify (5.1.0)
rspec (3.0.0) rqrcode (0.10.1)
rspec-core (~> 3.0.0) chunky_png (~> 1.0)
rspec-expectations (~> 3.0.0) rspec (3.6.0)
rspec-mocks (~> 3.0.0) rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-activemodel-mocks (1.0.1) rspec-activemodel-mocks (1.0.1)
activemodel (>= 3.0) activemodel (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
rspec-mocks (>= 2.99, < 4.0) rspec-mocks (>= 2.99, < 4.0)
rspec-core (3.0.2) rspec-core (3.6.0)
rspec-support (~> 3.0.0) rspec-support (~> 3.6.0)
rspec-expectations (3.0.2) rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0) rspec-support (~> 3.6.0)
rspec-mocks (3.0.2) rspec-mocks (3.6.0)
rspec-support (~> 3.0.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-rails (3.0.0) rspec-support (~> 3.6.0)
rspec-rails (3.6.0)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
rspec-core (~> 3.0.0) rspec-core (~> 3.6.0)
rspec-expectations (~> 3.0.0) rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.0.0) rspec-mocks (~> 3.6.0)
rspec-support (~> 3.0.0) rspec-support (~> 3.6.0)
rspec-support (3.0.2) rspec-support (3.6.0)
rubocop (0.48.1) rubocop (0.49.1)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0) parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
@ -455,16 +468,19 @@ GEM
ruby-oembed (0.8.14) ruby-oembed (0.8.14)
ruby-openid (2.5.0) ruby-openid (2.5.0)
ruby-progressbar (1.8.1) ruby-progressbar (1.8.1)
ruby_dep (1.5.0)
rubyzip (1.2.1) rubyzip (1.2.1)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sass (3.2.19) sass (3.2.19)
sass-rails (4.0.4) sass-rails (5.0.6)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 6)
sass (~> 3.2.2) sass (~> 3.1)
sprockets (~> 2.8, < 2.12) sprockets (>= 2.8, < 4.0)
sprockets-rails (~> 2.0) sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
selectize-rails (0.12.4) selectize-rails (0.12.4)
shoulda-matchers (2.6.1) shellany (0.0.1)
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
simplecov (0.11.2) simplecov (0.11.2)
docile (~> 1.1.0) docile (~> 1.1.0)
@ -478,11 +494,9 @@ GEM
spring (1.6.3) spring (1.6.3)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
spring (>= 0.9.1) spring (>= 0.9.1)
sprockets (2.11.3) sprockets (3.7.1)
hike (~> 1.2) concurrent-ruby (~> 1.0)
multi_json (~> 1.0) rack (> 1, < 3)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-rails (2.3.3) sprockets-rails (2.3.3)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
@ -501,7 +515,6 @@ GEM
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (1.4.1) tilt (1.4.1)
timecop (0.7.1) timecop (0.7.1)
timers (1.1.0)
tins (1.6.0) tins (1.6.0)
transitions (0.1.12) transitions (0.1.12)
ttfunk (1.1.1) ttfunk (1.1.1)
@ -514,7 +527,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.2)
unicode-display_width (1.2.1) unicode-display_width (1.3.0)
unicode_utils (1.4.0) unicode_utils (1.4.0)
unobtrusive_flash (3.1.0) unobtrusive_flash (3.1.0)
railties railties
@ -527,9 +540,10 @@ GEM
binding_of_caller (>= 0.7.2) binding_of_caller (>= 0.7.2)
railties (>= 4.0) railties (>= 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
webmock (1.20.4) webmock (3.0.1)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff
websocket-driver (0.6.3) websocket-driver (0.6.3)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2) websocket-extensions (0.1.2)
@ -550,7 +564,8 @@ DEPENDENCIES
acts_as_list acts_as_list
ahoy_matey ahoy_matey
autoprefixer-rails autoprefixer-rails
awesome_nested_set (~> 3.0.0.rc.5) awesome_nested_set (~> 3.1.3)
axlsx!
axlsx_rails axlsx_rails
bootstrap-sass (~> 3.3.4.1) bootstrap-sass (~> 3.3.4.1)
bootstrap-switch-rails (~> 3.0.0) bootstrap-switch-rails (~> 3.0.0)
@ -578,7 +593,7 @@ DEPENDENCIES
formtastic (~> 3.1.1) formtastic (~> 3.1.1)
formtastic-bootstrap formtastic-bootstrap
gravtastic gravtastic
guard-rspec (~> 4.2.8) guard-rspec
haml-rails haml-rails
haml_lint (~> 0.24.0) haml_lint (~> 0.24.0)
hoptoad_notifier (~> 2.3) hoptoad_notifier (~> 2.3)
@ -594,6 +609,7 @@ DEPENDENCIES
mini_magick mini_magick
money-rails money-rails
mysql2 mysql2
nokogiri (>= 1.8.1)
omniauth omniauth
omniauth-facebook omniauth-facebook
omniauth-github omniauth-github
@ -603,8 +619,9 @@ DEPENDENCIES
phantomjs phantomjs
piwik_analytics (~> 1.0.1) piwik_analytics (~> 1.0.1)
poltergeist poltergeist
prawn-qrcode (~> 0.2.2.1)
prawn_rails prawn_rails
rails (~> 4.2) rails (~> 4.2.8)
rails-assets-bootstrap-markdown! rails-assets-bootstrap-markdown!
rails-assets-date.format! rails-assets-date.format!
rails-assets-holderjs! rails-assets-holderjs!
@ -617,15 +634,15 @@ DEPENDENCIES
rails-assets-trianglify! rails-assets-trianglify!
rails-assets-waypoints! rails-assets-waypoints!
rails-i18n (~> 4.0.0) rails-i18n (~> 4.0.0)
rails-observers
rails_12factor rails_12factor
rdoc-generator-fivefish rdoc-generator-fivefish
redcarpet redcarpet
responders (~> 2.0) responders (~> 2.0)
rolify rolify
rqrcode
rspec-activemodel-mocks rspec-activemodel-mocks
rspec-rails rspec-rails (~> 3.5, >= 3.5.2)
rubocop (~> 0.48.1) rubocop (~> 0.49.0)
ruby-oembed ruby-oembed
sass-rails (>= 4.0.2) sass-rails (>= 4.0.2)
selectize-rails selectize-rails
@ -635,6 +652,7 @@ DEPENDENCIES
sqlite3 sqlite3
stripe stripe
stripe-ruby-mock stripe-ruby-mock
thor (= 0.19.1)
timecop timecop
transitions transitions
turbolinks turbolinks
@ -645,4 +663,4 @@ DEPENDENCIES
whenever whenever
BUNDLED WITH BUNDLED WITH
1.14.5 1.15.4

View file

@ -11,7 +11,9 @@ OSEM is an [semantic versioned](http://semver.org/) app. That means given a vers
## Download ## Download
You can find the latest OSEM releases on our [release page](https://github.com/openSUSE/osem/releases/latest) ([older release here](https://github.com/openSUSE/osem/releases)) You can find the latest OSEM releases on our [release page](https://github.com/openSUSE/osem/releases/latest) ([older release here](https://github.com/openSUSE/osem/releases))
## Deploy ## Deploy
OSEM is a *Ruby on Rails* application. We recommend to run OSEM in production with [mod_passenger](https://www.phusionpassenger.com/download/#open_source) OSEM is a *Ruby on Rails* application. We recommend to run OSEM in production with [mod_passenger](https://www.phusionpassenger.com/download/#open_source)
and the [apache web-server](https://www.apache.org/). There are tons of guides on how to deploy rails apps on various and the [apache web-server](https://www.apache.org/). There are tons of guides on how to deploy rails apps on various
base operating systems. [Check Google](https://encrypted.google.com/search?hl=en&q=ruby%20on%20rails%20apache%20passenger) ;-) base operating systems. [Check Google](https://encrypted.google.com/search?hl=en&q=ruby%20on%20rails%20apache%20passenger) ;-)
@ -24,6 +26,55 @@ If you have an heroku account you can also
<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy"> <img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
</a> </a>
### Deploy with Docker
You can deploy OSEM using [Docker](https://docker.com/) and [Docker-Compose](https://docs.docker.com/compose/overview/).
*This is just a short guide and does not explain how to use Docker and/or Docker-Compose. You need some experience with these tools to be able to deploy OSEM with Docker properly.*
First of all, copy `docker-compose.yml.example` to `docker-compose.yml` and `docker-compose.env.example` to `docker-compose.env`. You should immediately change
`docker-compose.env`'s permissions to `0600` to make sure all the passphrases in it are kept secret.
(Tip: the easiest and most secure way to do it is to do it with a single command, for example `install -m 0600 docker-compose.env.example docker-compose.env`).
There are two configurations to deploy OSEM with Docker: *evaluation mode* and *production mode*.
#### Evaluation mode
If you want to evaluate OSEM to see if it fits your needs, the default configuration in `docker-compose.env` will work perfectly fine for you.
For convenience reasons, `docker-compose.yml` already contains a [MailHog](https://github.com/mailhog/MailHog) service configuration. MailHog
is going to catch every email sent by OSEM and displays them on a special web service. Thus, it eliminitates the need to set up an SMTP server just to try out OSEM.
Just point your browser to http://localhost:8025 to get access to registration confirmation links etc.
Run `docker-compose up --build` to start the services. On first run, it will take a few minutes to initialize the database. Thus, wait a few minutes before you open up
http://localhost:9292 in your browser.
#### Production mode
To deploy OSEM for production, you have to make a few changes to `docker-compose.yml`. First, remove (or comment) the `mailhog` service, as it is only useful for evaluation and
cannot be used for production.
You can change the forwarded port from port `9292` to any other value if this port is already in use or you just want to use another one.
Next, you have to modify `docker-compose.env`. This file works as a Docker-like replacement for the regular Rails `.env` files described below.
You can configure any of the configuration values shown in the **Configure** section below in it. The most essential variables are already configured to standard
values in `docker-compose.env` which you most likely want to change.
First, you need to modify the email related settings. You need a working SMTP server for OSEM to send out registration confirmation mails etc.
For security reasons, the following variables have to be changed, too:
- `MYSQL_PASSWORD`
- `MYSQL_ROOT_PASSWORD`
- `SECRET_KEY_BASE`
These variables need to be set to the correct values at first, as they are used to initialize everything. Modification of these variables after installation and initialization is more
complicated and out of this document's scope.
As with any other Docker-Compose configuration, run `docker-compose up --build` (or `docker-compose up --build -d` to run in background) to start the services. During the first
start, the database has to be initialized which can take several minutes. The web service is by default exposed on localhost only as it is intended to be served by a reverse proxy (for SSL
termination, caching etc.).
You should not directly expose the web server port unless you have a good reason to do so.
## Configure ## Configure
There are a couple of environment variables you can set to configure OSEM. Check out the *dotenv.example* file. There are a couple of environment variables you can set to configure OSEM. Check out the *dotenv.example* file.

View file

@ -1,5 +1,5 @@
# Translation # Translation
We are using [Transifex] (https://www.transifex.com/opensuse-community/osem/) to manage our translations. We are using [Transifex](https://www.transifex.com/opensuse-community/osem/) to manage our translations.
We are also using the _Live_ feature of Transifex. That means that all strings from OSEM instances are automatically collected and are available for translating. We are also using the _Live_ feature of Transifex. That means that all strings from OSEM instances are automatically collected and are available for translating.
>Automated collection only works if you use the API key (See at the end of this file) >Automated collection only works if you use the API key (See at the end of this file)
@ -7,7 +7,7 @@ We are also using the _Live_ feature of Transifex. That means that all strings f
## 1. Translate ## 1. Translate
* Start translating: * Start translating:
1. Navigate to the [project's page] (https://www.transifex.com/opensuse-community/osem) 1. Navigate to the [project's page](https://www.transifex.com/opensuse-community/osem)
2. Click **translation** button 2. Click **translation** button
3. Select **language** 3. Select **language**
4. Select **resource** (events.opensuse.org) 4. Select **resource** (events.opensuse.org)
@ -27,7 +27,7 @@ Eg. Assuming you have signed up yourself as a translator for OSEM, to translate
* How to request to publish translated strings * How to request to publish translated strings
After you make sure that you have properly reviewed the newly translated strings (they need to be marked as **reviewed** otherwise they won't go live), you can open a [new issue] (https://github.com/openSUSE/osem/issues/new) with the following information: After you make sure that you have properly reviewed the newly translated strings (they need to be marked as **reviewed** otherwise they won't go live), you can open a [new issue](https://github.com/openSUSE/osem/issues/new) with the following information:
Title: [Transifex] Publish translation for EN Title: [Transifex] Publish translation for EN

2
Vagrantfile vendored
View file

@ -12,7 +12,7 @@ Vagrant.configure(2) do |config|
# Every Vagrant development environment requires a box. You can search for # Every Vagrant development environment requires a box. You can search for
# boxes at https://atlas.hashicorp.com/search. # boxes at https://atlas.hashicorp.com/search.
config.vm.box = "opensuse/openSUSE-42.1-x86_64" config.vm.box = "opensuse/openSUSE-42.2-x86_64"
# Disable automatic box update checking. If you disable this, then # Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs # boxes will only be checked for updates when the user runs

View file

@ -55,7 +55,6 @@ $(function() {
} }
function draw_line_chart(animation, $canvas){ function draw_line_chart(animation, $canvas){
var options = get_animation({}, animation);
var chart_data = create_dataset($canvas); var chart_data = create_dataset($canvas);
var weeks = $canvas.parent().data('weeks'); var weeks = $canvas.parent().data('weeks');
var data = { var data = {
@ -63,10 +62,27 @@ $(function() {
datasets : chart_data datasets : chart_data
} }
var options = get_animation(wholeNumberAxisFix(data), animation);
var ctx = $canvas.get(0).getContext("2d"); var ctx = $canvas.get(0).getContext("2d");
new Chart(ctx).Line(data, options); new Chart(ctx).Line(data, options);
} }
function wholeNumberAxisFix(data){
var maxValue = false;
for(datasetIndex = 0; datasetIndex < data.datasets.length; ++datasetIndex){
var setMax = Math.max.apply(null, data.datasets[datasetIndex].data);
if (maxValue === false || setMax > maxValue) maxValue = setMax;
}
var steps = maxValue;
var stepWidth = 1;
if (maxValue > 10) {
stepWidth = Math.floor(maxValue / 10);
steps = Math.ceil(maxValue / stepWidth);
}
return { scaleOverride: true, scaleSteps: steps, scaleStepWidth: stepWidth, scaleStartValue: 0 };
}
function create_dataset($canvas){ function create_dataset($canvas){
var selected = getSelectedConferences($canvas); var selected = getSelectedConferences($canvas);
var chart_data = $canvas.parent().data('chart'); var chart_data = $canvas.parent().data('chart');

View file

@ -1,18 +1,14 @@
$(function () { $(function () {
$(document).ready(function() {
$('.datatable').DataTable({ $('.datatable').DataTable({
// ajax: ..., // ajax: ...,
stateSave: true, stateSave: true,
autoWidth: false, autoWidth: false,
pagingType: 'full_numbers', pagingType: 'full_numbers',
"lengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]] "lengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]],
}); });
$('#versionstable').DataTable({ $('#versionstable').DataTable({
pagingType: 'full_numbers', pagingType: 'full_numbers',
order: [[ 0, 'desc' ]] order: [[ 0, 'desc' ]]
}); });
});
}); });

View file

@ -78,11 +78,12 @@ var Schedule = {
}; };
$(document).ready( function() { $(document).ready( function() {
// hide the remove button for unscheduled events // hide the remove button for unscheduled and non schedulable events
$('.unscheduled-events .schedule-event-delete-button').hide(); $('.unscheduled-events .schedule-event-delete-button').hide();
$('.non_schedulable .schedule-event-delete-button').hide();
// set events as draggable // set events as draggable
$('.schedule-event').draggable({ $('.schedule-event').not('.non_schedulable').draggable({
snap: '.schedule-room-slot', snap: '.schedule-room-slot',
revertDuration: 200, revertDuration: 200,
revert: function (event, ui) { revert: function (event, ui) {
@ -99,7 +100,7 @@ $(document).ready( function() {
}); });
// set room cells as droppable // set room cells as droppable
$('.schedule-room-slot').droppable({ $('.schedule-room-slot').not('.non_schedulable .schedule-room-slot').droppable({
accept: '.schedule-event', accept: '.schedule-event',
tolerance: "pointer", tolerance: "pointer",
drop: function(event, ui) { drop: function(event, ui) {

View file

@ -4,14 +4,14 @@ function update_price($this){
// Calculate price for row // Calculate price for row
var value = $this.val(); var value = $this.val();
var price = $('#price_' + id).text(); var price = $('#price_' + id).text();
$('#total_row_' + id).text(value * price); $('#total_row_' + id).text((value * price).toFixed(2));
// Calculate total price // Calculate total price
var total = 0; var total = 0;
$('.total_row').each(function( index ) { $('.total_row').each(function( index ) {
total += parseInt($(this).text()); total += parseFloat($(this).text());
}); });
$('#total_price').text(total); $('#total_price').text(total.toFixed(2));
} }
$( document ).ready(function() { $( document ).ready(function() {

View file

@ -20,14 +20,6 @@
color: black; color: black;
background-color: #eeeeee; background-color: #eeeeee;
} }
input[type=text],
input[type=password] {
text-align: center;
padding: 5px;
display: block;
margin: auto;
width: 255px;
}
.btn-group { .btn-group {
width: 100%; width: 100%;
text-align: center; text-align: center;
@ -37,7 +29,7 @@
} }
} }
.dropdown-menu { .dropdown-menu {
padding: 17px 17px 0px 17px; padding: 17px;
min-width: 225px; min-width: 225px;
} }
} }

View file

@ -267,6 +267,10 @@ td.no-padding{
font-size: 7px; font-size: 7px;
} }
.non_schedulable{
opacity: 0.5;
}
/* Small devices (tablets, 768px and up) */ /* Small devices (tablets, 768px and up) */
@media (min-width: 768px) { @media (min-width: 768px) {
.room, .event-title{ .room, .event-title{

View file

@ -90,3 +90,17 @@ p.comment-body {
.changeset{ .changeset{
display: none; display: none;
} }
.box{
height: 230px;
}
/* sidebar hamburger btn */
.side-nav-btn{
margin-left: 10px;
float: left;
}
.qr-image{
margin-left: 120px;
}

View file

@ -2,15 +2,22 @@ module Admin
class BaseController < ApplicationController class BaseController < ApplicationController
before_filter :verify_user_admin before_filter :verify_user_admin
private
def current_ability
@current_ability ||= AdminAbility.new(current_user)
end
def verify_user_admin def verify_user_admin
if (current_user.nil?) if (current_user.nil?)
redirect_to sign_in_path redirect_to sign_in_path
return false return false
end end
unless (current_user.has_role? :organizer, :any) || (current_user.has_role? :cfp, :any) || unless (current_user.has_role? :organizer, :any) || (current_user.has_role? :cfp, :any) ||
(current_user.has_role? :info_desk, :any) || (current_user.has_role? :info_desk, :any) || (current_user.has_role? :organization_admin, :any) ||
(current_user.has_role? :volunteers_coordinator, :any) || current_user.is_admin (current_user.has_role? :volunteers_coordinator, :any) ||
raise CanCan::AccessDenied.new('You are not authorized to access this area!') (current_user.has_role? :track_organizer, :any) || current_user.is_admin
raise CanCan::AccessDenied.new('You are not authorized to access this page.')
end end
end end
end end

View file

@ -0,0 +1,116 @@
module Admin
class BoothsController < Admin::BaseController
load_and_authorize_resource :conference, find_by: :short_title
load_and_authorize_resource through: :conference
def index; end
def show; end
def new
@url = admin_conference_booths_path(@conference.short_title)
end
def create
@url = admin_conference_booths_path(@conference.short_title)
@booth = @conference.booths.new(booth_params)
@booth.submitter = current_user
if @booth.save
redirect_to admin_conference_booths_path,
notice: 'Booth successfully created.'
else
flash.now[:error] = "Creating booth failed. #{@booth.errors.full_messages.to_sentence}."
render :new
end
end
def edit
@url = admin_conference_booth_path(@conference.short_title, @booth.id)
end
def update
@url = admin_conference_booth_path(@conference.short_title, @booth.id)
@booth.update_attributes(booth_params)
if @booth.save
redirect_to admin_conference_booths_path,
notice: "Successfully updated booth for #{@booth.title}."
else
flash.now[:error] = "An error prohibited the Booth for #{@booth.title} "\
"#{@booth.errors.full_messages.join('. ')}."
render :edit
end
end
def accept
@booth.accept!
if @booth.save
if @conference.email_settings.send_on_booths_acceptance
Mailbot.conference_booths_acceptance_mail(@booth).deliver
end
redirect_to admin_conference_booths_path(conference_id: @conference.short_title),
notice: 'Booth successfully accepted!'
else
redirect_to admin_conference_booths_path(conference_id: @conference.short_title)
flash[:error] = "Booth could not be accepted. #{@booth.errors.full_messages.to_sentence}."
end
end
def to_accept
update_state(:to_accept, 'Booth to accept')
end
def to_reject
update_state(:to_reject, 'Booth to reject')
end
def reject
@booth.reject!
if @booth.save
Mailbot.conference_booths_rejection_mail(@booth).deliver
redirect_to admin_conference_booths_path(conference_id: @conference.short_title),
notice: 'Booth successfully rejected.'
else
redirect_to admin_conference_booths_path(conference_id: @conference.short_title)
flash[:error] = "Booth could not be rejected. #{@booth.errors.full_messages.to_sentence}."
end
end
def restart
update_state(:restart, 'Booth is submitted')
end
def cancel
update_state(:cancel, 'Booth is canceled')
end
def confirm
update_state(:confirm, 'Booth successfully confirmed')
end
private
def update_state(transition, notice)
alert = @booth.update_state(transition, notice)
if alert.blank?
flash[:notice] = 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
end
end
def booth_params
params.require(:booth).permit(:title, :description, :reasoning, :state, :picture, :conference_id,
:created_at, :updated_at, :submitter_relationship, :website_url, responsible_ids: [])
end
end
end

View file

@ -2,23 +2,25 @@ module Admin
class CfpsController < Admin::BaseController class CfpsController < Admin::BaseController
load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :conference, find_by: :short_title
load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :program, through: :conference, singleton: true
load_and_authorize_resource through: :program, singleton: true load_and_authorize_resource through: :program
def index; end
def show; end def show; end
def new def new
@cfp = @program.build_cfp @cfp = @program.cfps.new
end end
def edit; end def edit; end
def create def create
@cfp = @program.build_cfp(cfp_params) @cfp = @program.cfps.new(cfp_params)
send_mail_on_cfp_dates_updates = @cfp.notify_on_cfp_date_update? send_mail_on_cfp_dates_updates = @cfp.notify_on_cfp_date_update?
if @cfp.save if @cfp.save
ConferenceCfpUpdateMailJob.perform_later(@conference) if send_mail_on_cfp_dates_updates ConferenceCfpUpdateMailJob.perform_later(@conference) if send_mail_on_cfp_dates_updates
redirect_to admin_conference_program_cfp_path, redirect_to admin_conference_program_cfps_path,
notice: 'Call for papers successfully created.' notice: 'Call for papers successfully created.'
else else
flash.now[:error] = "Creating the call for papers failed. #{@cfp.errors.full_messages.join('. ')}." flash.now[:error] = "Creating the call for papers failed. #{@cfp.errors.full_messages.join('. ')}."
@ -27,14 +29,13 @@ module Admin
end end
def update def update
@cfp = @program.cfp
@cfp.assign_attributes(cfp_params) @cfp.assign_attributes(cfp_params)
send_mail_on_cfp_dates_updates = @cfp.notify_on_cfp_date_update? send_mail_on_cfp_dates_updates = @cfp.notify_on_cfp_date_update?
if @cfp.update_attributes(cfp_params) if @cfp.update_attributes(cfp_params)
ConferenceCfpUpdateMailJob.perform_later(@conference) if send_mail_on_cfp_dates_updates ConferenceCfpUpdateMailJob.perform_later(@conference) if send_mail_on_cfp_dates_updates
redirect_to admin_conference_program_cfp_path(@conference.short_title), redirect_to admin_conference_program_cfps_path(@conference.short_title),
notice: 'Call for papers successfully updated.' notice: 'Call for papers successfully updated.'
else else
flash.now[:error] = "Updating call for papers failed. #{@cfp.errors.to_a.join('. ')}." flash.now[:error] = "Updating call for papers failed. #{@cfp.errors.to_a.join('. ')}."
@ -44,9 +45,9 @@ module Admin
def destroy def destroy
if @cfp.destroy if @cfp.destroy
redirect_to admin_conference_program_cfp_path, notice: 'Call for Papers was successfully deleted.' redirect_to admin_conference_program_cfps_path, notice: 'Call for Papers was successfully deleted.'
else else
redirect_to admin_conference_program_cfp_path, error: 'An error prohibited this Call for Papers from being destroyed: '\ redirect_to admin_conference_program_cfps_path, error: 'An error prohibited this Call for Papers from being destroyed: '\
"#{@cfp.errors.full_messages.join('. ')}." "#{@cfp.errors.full_messages.join('. ')}."
end end
end end
@ -54,7 +55,7 @@ module Admin
private private
def cfp_params def cfp_params
params.require(:cfp).permit(:start_date, :end_date) params.require(:cfp).permit(:start_date, :end_date, :cfp_type)
end end
end end
end end

View file

@ -37,6 +37,9 @@ module Admin
@registrations = {} @registrations = {}
@registration_weeks = [0] @registration_weeks = [0]
@tickets = {}
@ticket_weeks = [0]
@conferences.each do |c| @conferences.each do |c|
# Event submissions over time chart # Event submissions over time chart
@submissions[c.short_title] = c.get_submissions_per_week @submissions[c.short_title] = c.get_submissions_per_week
@ -45,6 +48,10 @@ module Admin
# Conference registrations over time chart # Conference registrations over time chart
@registrations[c.short_title] = c.get_registrations_per_week @registrations[c.short_title] = c.get_registrations_per_week
@registration_weeks.push(@registrations[c.short_title].length) @registration_weeks.push(@registrations[c.short_title].length)
# Tickets sold over time chart
@tickets[c.short_title] = c.get_tickets_sold_per_week
@ticket_weeks.push(@tickets[c.short_title].length)
end end
@cfp_weeks = @cfp_weeks.max @cfp_weeks = @cfp_weeks.max
@ -55,12 +62,17 @@ module Admin
@registrations = normalize_array_length(@registrations, @registration_weeks) @registrations = normalize_array_length(@registrations, @registration_weeks)
@registration_weeks = @registration_weeks > 0 ? (1..@registration_weeks).to_a : 1 @registration_weeks = @registration_weeks > 0 ? (1..@registration_weeks).to_a : 1
@ticket_weeks = @ticket_weeks.max
@tickets = normalize_array_length(@tickets, @ticket_weeks)
@ticket_weeks = @ticket_weeks > 0 ? (1..@ticket_weeks).to_a : 1
@event_distribution = Conference.event_distribution @event_distribution = Conference.event_distribution
@user_distribution = Conference.user_distribution @user_distribution = Conference.user_distribution
end end
def new def new
@conference = Conference.new @conference = Conference.new
@organizations = Organization.accessible_by(current_ability, :update).pluck(:name, :id)
end end
def create def create
@ -133,6 +145,20 @@ module Admin
@submissions_data = @submissions_data.except('Weeks') @submissions_data = @submissions_data.except('Weeks')
end end
@tickets_data = {}
@tickets_data = @conference.get_tickets_data
@ticket_weeks = 0
if @tickets_data['Weeks']
@ticket_weeks = @tickets_data['Weeks']
@tickets_data = @tickets_data.except('Weeks')
end
# Set line color using a hash function
@tickets = []
@tickets_data.keys.each do |title|
@tickets.append(short_title: title, color: "\##{Digest::MD5.hexdigest(title)[0..5]}")
end
# Doughnut charts # Doughnut charts
@event_type_distribution = @conference.event_type_distribution @event_type_distribution = @conference.event_type_distribution
@event_type_distribution_confirmed = @conference.event_type_distribution(:confirmed) @event_type_distribution_confirmed = @conference.event_type_distribution(:confirmed)
@ -186,7 +212,7 @@ module Admin
:vpositions_attributes, :use_volunteers, :color, :vpositions_attributes, :use_volunteers, :color,
:sponsorship_levels_attributes, :sponsors_attributes, :sponsorship_levels_attributes, :sponsors_attributes,
:targets, :targets_attributes, :targets, :targets_attributes,
:campaigns, :campaigns_attributes, :registration_limit) :campaigns, :campaigns_attributes, :registration_limit, :organization_id, :ticket_layout, :booth_limit)
end end
end end
end end

View file

@ -30,7 +30,9 @@ module Admin
:send_on_conference_registration_dates_updated, :conference_registration_dates_updated_subject, :conference_registration_dates_updated_body, :send_on_conference_registration_dates_updated, :conference_registration_dates_updated_subject, :conference_registration_dates_updated_body,
:send_on_venue_updated, :venue_updated_subject, :venue_updated_body, :send_on_venue_updated, :venue_updated_subject, :venue_updated_body,
:send_on_cfp_dates_updated, :cfp_dates_updated_subject, :cfp_dates_updated_body, :send_on_cfp_dates_updated, :cfp_dates_updated_subject, :cfp_dates_updated_body,
:send_on_program_schedule_public, :program_schedule_public_subject, :program_schedule_public_body) :send_on_program_schedule_public, :program_schedule_public_subject, :program_schedule_public_body,
:send_on_booths_acceptance, :booths_acceptance_subject, :booths_acceptance_body,
:send_on_booths_rejection, :booths_rejection_subject, :booths_rejection_body)
end end
end end
end end

View file

@ -4,8 +4,10 @@ module Admin
load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :program, through: :conference, singleton: true
load_and_authorize_resource :event, through: :program load_and_authorize_resource :event, through: :program
load_and_authorize_resource :events_registration, only: :toggle_attendance load_and_authorize_resource :events_registration, only: :toggle_attendance
# 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 :get_event, except: [:index, :create, :new] before_action :get_tracks, only: [:index, :show, :edit]
# FIXME: The timezome should only be applied on output, otherwise # FIXME: The timezome should only be applied on output, otherwise
# you get lost in timezone conversions... # you get lost in timezone conversions...
@ -16,8 +18,6 @@ module Admin
end end
def index def index
@events = @program.events
@tracks = @program.tracks
@difficulty_levels = @program.difficulty_levels @difficulty_levels = @program.difficulty_levels
@event_types = @program.event_types @event_types = @program.event_types
@tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed) @tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed)
@ -43,7 +43,6 @@ module Admin
end end
def show def show
@tracks = @program.tracks
@event_types = @program.event_types @event_types = @program.event_types
@comments = @event.root_comments @comments = @event.root_comments
@comment_count = @event.comment_threads.count @comment_count = @event.comment_threads.count
@ -58,7 +57,6 @@ module Admin
def edit def edit
@event_types = @program.event_types @event_types = @program.event_types
@tracks = Track.all
@comments = @event.root_comments @comments = @event.root_comments
@comment_count = @event.comment_threads.count @comment_count = @event.comment_threads.count
@user = @event.submitter @user = @event.submitter
@ -104,7 +102,7 @@ module Admin
ahoy.track 'Event submission', title: 'New submission' ahoy.track 'Event submission', title: 'New submission'
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 else
flash[:error] = "Could not submit proposal: #{@event.errors.full_messages.join(', ')}" flash.now[:error] = "Could not submit proposal: #{@event.errors.full_messages.join(', ')}"
render action: 'new' render action: 'new'
end end
end end
@ -187,16 +185,6 @@ module Admin
params.require(:comment).permit(:commentable, :body, :user_id) params.require(:comment).permit(:commentable, :body, :user_id)
end end
def get_event
@event = @conference.program.events.find(params[:id])
unless @event
redirect_to admin_conference_program_events_path(conference_id: @conference.short_title),
error: 'Error! Could not find event!'
return
end
@event
end
def update_state(transition, notice, mail = false, subject = false, send_mail = false) def update_state(transition, notice, mail = false, subject = false, send_mail = false)
alert = @event.update_state(transition, mail, subject, send_mail, params[:send_mail].blank?) alert = @event.update_state(transition, mail, subject, send_mail, params[:send_mail].blank?)
@ -208,5 +196,9 @@ module Admin
return redirect_back_or_to(admin_conference_program_events_path(conference_id: @conference.short_title)) && return return redirect_back_or_to(admin_conference_program_events_path(conference_id: @conference.short_title)) && return
end end
end end
def get_tracks
@tracks = Track.accessible_by(current_ability).where(program: @program).confirmed
end
end end
end end

View file

@ -0,0 +1,94 @@
module Admin
class OrganizationsController < Admin::BaseController
load_and_authorize_resource :organization
before_action :verify_user, only: [:assign_org_admins, :unassign_org_admins]
def index
@organizations = Organization.all
end
def create
@organization = Organization.new(organization_params)
if @organization.save
redirect_to admin_organizations_path,
notice: 'Organization successfully created'
else
redirect_to new_admin_organization_path,
error: @organization.errors.full_messages.join(', ')
end
end
def new
@organization = Organization.new
end
def edit; end
def update
if @organization.update_attributes(organization_params)
redirect_to admin_organizations_path,
notice: 'Organization successfully updated'
else
redirect_to edit_admin_organization_path(@organization),
error: @organization.errors.full_messages.join(', ')
end
end
def destroy
if @organization.destroy
redirect_to admin_organizations_path,
notice: 'Organization successfully destroyed'
else
redirect_to admin_organizations_path,
error: 'Organization cannot be destroyed'
end
end
def assign_org_admins
if @user.has_role? 'organization_admin', @organization
flash[:error] = "User #{@user.email} already has the role organization admin"
elsif @user.add_role 'organization_admin', @organization
flash[:notice] = "Successfully added role organization admin to user #{@user.email}"
else
flash[:error] = "Coud not add role organization admin to #{@user.email}"
end
redirect_to admins_admin_organization_path(@organization)
end
def unassign_org_admins
if @user.remove_role 'organization_admin', @organization
flash[:notice] = "Successfully removed role organization admin from user #{@user.email}"
else
flash[:error] = "Could not remove role organization admin from user #{@user.email}"
end
redirect_to admins_admin_organization_path(@organization)
end
def admins
@role = @organization.roles.first
@users = @role.users
render 'show_org_admins'
end
private
def user_params
params.require(:user).permit(:email)
end
def verify_user
@user = User.find_by(email: user_params[:email])
unless @user
redirect_to admins_admin_organization_path(@organization),
error: 'Could not find user. Please provide a valid email!'
return
end
end
def organization_params
params.require(:organization).permit(:name, :description, :picture)
end
end
end

View file

@ -0,0 +1,14 @@
module Admin
class PhysicalTicketsController < Admin::BaseController
before_action :authenticate_user!
load_resource :conference, find_by: :short_title
load_and_authorize_resource
authorize_resource :conference_registrations, class: Registration
def index
@physical_tickets = @conference.physical_tickets
@tickets_sold_distribution = @conference.tickets_sold_distribution
@tickets_turnover_distribution = @conference.tickets_turnover_distribution
end
end
end

View file

@ -30,7 +30,7 @@ module Admin
flash.now[:error] = "Updating program failed. #{@program.errors.to_a.join('. ')}." flash.now[:error] = "Updating program failed. #{@program.errors.to_a.join('. ')}."
render :new render :new
end end
format.js { render json: { errors: "The selected schedule couldn't been updated #{@program.errors.to_a.join('. ')}" }, status: 422 } format.js { render json: { errors: "The selected schedule couldn't be updated #{@program.errors.to_a.join('. ')}" }, status: 422 }
end end
end end
end end

View file

@ -1,5 +1,5 @@
module Admin module Admin
class RegistrationPeriodsController < ApplicationController class RegistrationPeriodsController < Admin::BaseController
load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :conference, find_by: :short_title
load_and_authorize_resource through: :conference, singleton: true load_and_authorize_resource through: :conference, singleton: true

View file

@ -2,9 +2,11 @@ module Admin
class ReportsController < Admin::BaseController class ReportsController < Admin::BaseController
load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :conference, find_by: :short_title
load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :program, through: :conference, singleton: true
# For some reason this doesn't work, so a workaround is used
# load_and_authorize_resource :event, through: :program
def index def index
@events = @program.events @events = Event.accessible_by(current_ability).where(program: @program)
@events_commercials = Commercial.where(commercialable_type: 'Event', commercialable_id: @events.pluck(:id)) @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_missing_commercial = @events.where.not(id: @events_commercials.pluck(:commercialable_id))
@events_with_requirements = @events.where.not(description: ['', nil]) @events_with_requirements = @events.where.not(description: ['', nil])

View file

@ -8,14 +8,26 @@ module Admin
def index def index
@roles = Role.where(resource: @conference) @roles = Role.where(resource: @conference)
tracks = @conference.program.tracks.where.not(submitter: nil)
@roles += Role.where(resource: tracks)
authorize! :index, @role authorize! :index, @role
end end
def show def show
@url = if @track
toggle_user_admin_conference_program_track_role_path(@conference.short_title, @track, @role.name)
else
toggle_user_admin_conference_role_path(@conference.short_title, @role.name)
end
@users = @role.users @users = @role.users
end end
def edit def edit
@url = if @track
admin_conference_program_track_role_path(@conference.short_title, @track, @role.name)
else
admin_conference_role_path(@conference.short_title, @role.name)
end
@users = @role.users @users = @role.users
end end
@ -23,7 +35,13 @@ module Admin
role_name = @role.name role_name = @role.name
if @role.update_attributes(role_params) if @role.update_attributes(role_params)
redirect_to admin_conference_role_path(@conference.short_title, @role.name), url = if @track
admin_conference_program_track_role_path(@conference.short_title, @track, @role.name)
else
admin_conference_role_path(@conference.short_title, @role.name)
end
redirect_to url,
notice: 'Successfully updated role ' + @role.name notice: 'Successfully updated role ' + @role.name
else else
@role.name = role_name @role.name = role_name
@ -36,8 +54,14 @@ module Admin
user = User.find_by(email: user_params[:email]) user = User.find_by(email: user_params[:email])
state = user_params[:state] state = user_params[:state]
url = if @track
admin_conference_program_track_role_path(@conference.short_title, @track, @role.name)
else
admin_conference_role_path(@conference.short_title, @role.name)
end
unless user unless user
redirect_to admin_conference_role_path(@conference.short_title, @role.name), redirect_to url,
error: 'Could not find user. Please provide a valid email!' error: 'Could not find user. Please provide a valid email!'
return return
end end
@ -49,17 +73,23 @@ module Admin
return return
end end
if @role.resource_type == 'Conference'
role_resource = @conference
elsif @role.resource_type == 'Track'
role_resource = @track
end
# Remove user # Remove user
if state == 'false' if state == 'false'
if user.remove_role @role.name, @conference if user.remove_role @role.name, role_resource
flash[:notice] = "Successfully removed role #{@role.name} from user #{user.email}" flash[:notice] = "Successfully removed role #{@role.name} from user #{user.email}"
else else
flash[:error] = "Could not remove role #{@role.name} from user #{user.email}" flash[:error] = "Could not remove role #{@role.name} from user #{user.email}"
end end
elsif user.has_role? @role.name, @conference elsif user.has_role? @role.name, role_resource
flash[:error] = "User #{user.email} already has the role #{@role.name}" flash[:error] = "User #{user.email} already has the role #{@role.name}"
# Add user # Add user
elsif user.add_role @role.name, @conference elsif user.add_role @role.name, role_resource
flash[:notice] = "Successfully added role #{@role.name} to user #{user.email}" flash[:notice] = "Successfully added role #{@role.name} to user #{user.email}"
else else
flash[:error] = "Coud not add role #{@role.name} to #{user.email}" flash[:error] = "Coud not add role #{@role.name} to #{user.email}"
@ -67,7 +97,7 @@ module Admin
respond_to do |format| respond_to do |format|
format.js format.js
format.html { redirect_to admin_conference_role_path(@conference.short_title, @role.name) } format.html { redirect_to url }
end end
end end
@ -77,8 +107,13 @@ module Admin
# Set 'organizer' as default role, when there is no other selection # Set 'organizer' as default role, when there is no other selection
@selection = params[:id] ? params[:id].parameterize.underscore : 'organizer' @selection = params[:id] ? params[:id].parameterize.underscore : 'organizer'
if @selection == 'track_organizer'
@track = @conference.program.tracks.find_by(short_name: params[:track_id])
@role = Role.find_by(name: @selection, resource: @track)
else
@role = Role.find_by(name: @selection, resource: @conference) @role = Role.find_by(name: @selection, resource: @conference)
end end
end
def role_params def role_params
params.require(:role).permit(:name, :description, user_ids: []) params.require(:role).permit(:name, :description, user_ids: [])

View file

@ -4,14 +4,21 @@ module Admin
# the schedule of a conference, which should not be accessed in the first place # 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 :conference, find_by: :short_title
load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :program, through: :conference, singleton: true
load_and_authorize_resource :schedule, through: :program load_and_authorize_resource :schedule, through: :program, except: [:new, :create]
load_resource :event_schedules, through: :schedule load_resource :event_schedules, through: :schedule
load_resource :selected_schedule, through: :program, singleton: true load_resource :selected_schedule, through: :program, singleton: true
load_resource :venue, through: :conference, singleton: true load_resource :venue, through: :conference, singleton: true
def index; end def index; end
def new
@schedule = @program.schedules.build(track: @program.tracks.new)
authorize! :new, @schedule
end
def create def create
@schedule = @program.schedules.new(schedule_params)
authorize! :create, @schedule
if @schedule.save if @schedule.save
redirect_to admin_conference_schedule_path(@conference.short_title, @schedule.id), redirect_to admin_conference_schedule_path(@conference.short_title, @schedule.id),
notice: 'Schedule was successfully created.' notice: 'Schedule was successfully created.'
@ -23,10 +30,25 @@ module Admin
def show def show
@event_schedules = @schedule.event_schedules @event_schedules = @schedule.event_schedules
@unscheduled_events = @program.events.confirmed - @schedule.events
if @schedule.track
track = @schedule.track
@unscheduled_events = track.events.confirmed - @schedule.events
@dates = track.start_date..track.end_date
@rooms = [track.room]
else
@program.tracks.self_organized.confirmed.each do |t|
@event_schedules += t.selected_schedule.event_schedules if t.selected_schedule
end
self_organized_tracks_events = @program.tracks.self_organized.confirmed.map do |t|
t.events.confirmed
end
self_organized_tracks_events.flatten.compact!
@unscheduled_events = @program.events.confirmed - @schedule.events - self_organized_tracks_events
@dates = @conference.start_date..@conference.end_date @dates = @conference.start_date..@conference.end_date
@rooms = @conference.venue.rooms if @conference.venue @rooms = @conference.venue.rooms if @conference.venue
end end
end
def destroy def destroy
if @schedule.destroy if @schedule.destroy
@ -37,5 +59,11 @@ module Admin
error: "Schedule couldn't be deleted. #{@schedule.errors.full_messages.join('. ')}." error: "Schedule couldn't be deleted. #{@schedule.errors.full_messages.join('. ')}."
end end
end end
private
def schedule_params
params.require(:schedule).permit(:track_id) if params[:schedule]
end
end end
end end

View file

@ -47,7 +47,8 @@ module Admin
:include_tracks, :include_program, :include_cfp, :include_tracks, :include_program, :include_cfp,
:include_venue, :include_registrations, :include_venue, :include_registrations,
:include_tickets, :include_lodgings, :include_tickets, :include_lodgings,
:include_sponsors, :include_social_media) :include_sponsors, :include_social_media,
:include_booths)
end end
end end
end end

View file

@ -0,0 +1,16 @@
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
@ticket_scanning = TicketScanning.new(physical_ticket: @physical_ticket)
authorize! :create, @ticket_scanning
@ticket_scanning.save
redirect_to conferences_path,
notice: "Ticket with token #{@physical_ticket.token} successfully scanned."
end
end
end

View file

@ -5,6 +5,8 @@ module Admin
def index def index
authorize! :update, Ticket.new(conference_id: @conference.id) authorize! :update, Ticket.new(conference_id: @conference.id)
@tickets_sold_distribution = @conference.tickets_sold_distribution
@tickets_turnover_distribution = @conference.tickets_turnover_distribution
end end
def new def new
@ -48,7 +50,7 @@ module Admin
private private
def ticket_params def ticket_params
params.require(:ticket).permit(:conference, :title, :url, :description, :conference_id, :price_cents, :price_currency, :price) params.require(:ticket).permit(:conference, :title, :url, :description, :conference_id, :price_cents, :price_currency, :price, :registration_ticket)
end end
end end
end end

View file

@ -2,7 +2,10 @@ module Admin
class TracksController < Admin::BaseController class TracksController < Admin::BaseController
load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :conference, find_by: :short_title
load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :program, through: :conference, singleton: true
load_and_authorize_resource through: :program load_and_authorize_resource through: :program, find_by: :short_name
# Show flash message with ajax calls
after_action :prepare_unobtrusive_flash, only: :toggle_cfp_inclusion
def index; end def index; end
@ -19,6 +22,8 @@ module Admin
def create def create
@track = @program.tracks.new(track_params) @track = @program.tracks.new(track_params)
@track.state = 'confirmed'
@track.cfp_active = true
if @track.save if @track.save
redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title), redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title),
notice: 'Track successfully created.' notice: 'Track successfully created.'
@ -50,10 +55,80 @@ module Admin
end end
end end
def toggle_cfp_inclusion
@track.cfp_active = !@track.cfp_active
if @track.save
flash[:notice] = "Successfully changed cfp inclusion of #{@track.name} to #{@track.cfp_active}"
else
flash[:error] = "Failed to toggle cfp inclusion of #{@track.name} to #{@track.cfp_active}"
end
respond_to do |format|
format.js
end
end
def restart
update_state(:restart, "Review for #{@track.name} started!")
end
def to_accept
update_state(:to_accept, "Track #{@track.name} marked as a possible acceptance!")
end
def accept
if @track.room && @track.start_date && @track.end_date
update_state(:accept, "Track #{@track.name} accepted!")
else
flash[:alert] = 'Please make sure that the track has a room and start/end dates before accepting it'
redirect_to edit_admin_conference_program_track_path(@conference.short_title, @track)
end
end
def confirm
update_state(:confirm, "Track #{@track.name} confirmed!")
end
def to_reject
update_state(:to_reject, "Track #{@track.name} marked as a possible rejection!")
end
def reject
update_state(:reject, "Track #{@track.name} rejected!")
end
def cancel
update_state(:cancel, "Track #{@track.name} canceled!")
end
def update_selected_schedule
if @track.update_attributes(params.require(:track).permit(:selected_schedule_id))
respond_to do |format|
format.js { render json: {} }
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 }
end
end
end
private private
def track_params def track_params
params.require(:track).permit(:name, :description, :color) params.require(:track).permit(:name, :description, :color, :short_name, :cfp_active, :start_date, :end_date, :room_id)
end
def update_state(transition, notice)
errors = @track.update_state(transition)
if errors.blank?
flash[:notice] = notice
else
flash[:error] = errors
end
redirect_back_or_to(admin_conference_program_tracks_path(conference_id: @conference.short_title))
end end
end end
end end

View file

@ -1,17 +1,21 @@
module Admin module Admin
class VersionsController < Admin::BaseController class VersionsController < Admin::BaseController
skip_authorization_check load_resource :conference, find_by: :short_title, only: :index
load_and_authorize_resource class: PaperTrail::Version
def index def index
authorize! :index, PaperTrail::Version.new(item_type: 'User') @conferences_with_role = current_user.is_admin? ? Conference.pluck(:short_title) : Conference.with_role([:organizer, :cfp, :info_desk], current_user).pluck(:short_title)
conf_ids_for_organizer = current_user.is_admin? ? Conference.pluck(:id) : Conference.with_role(:organizer, current_user).pluck(:id)
@versions = PaperTrail::Version.where(["conference_id IN (?) OR item_type = 'User'", conf_ids_for_organizer]) if current_user.has_role? :organization_admin, :any
@conferences_with_role = Organization.with_role('organization_admin', current_user).map { |org| org.conferences.pluck :short_title }.flatten
end
@conferences_with_role.uniq!
return if @conference.blank?
@versions = PaperTrail::Version.where(conference_id: @conference.id).accessible_by(current_ability)
end end
def revert_attribute def revert_attribute
@version = PaperTrail::Version.find(params[:id])
authorize! :revert_attribute, @version
if params[:attribute] && @version.changeset.reject{ |_, values| values[0].blank? && values[1].blank? }.keys.include?(params[:attribute]) if params[:attribute] && @version.changeset.reject{ |_, values| values[0].blank? && values[1].blank? }.keys.include?(params[:attribute])
if @version.item[params[:attribute]] == @version.changeset[params[:attribute]][0] 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' flash[:error] = 'The item is already in the state that you are trying to revert it back to'
@ -33,9 +37,6 @@ module Admin
end end
def revert_object def revert_object
@version = PaperTrail::Version.find(params[:id])
authorize! :revert_object, @version
if @version.event != 'create' if @version.event != 'create'
if @version.reify.save if @version.reify.save
flash[:notice] = 'The selected change was successfully reverted' flash[:notice] = 'The selected change was successfully reverted'

View file

@ -13,10 +13,10 @@ module Admin
def show def show
if can_manage_volunteers?(@conference) if can_manage_volunteers?(@conference)
if @conference.use_vpositions @volunteers = if @conference.use_vpositions
@volunteers = @conference.registrations.joins(:vchoices).uniq @conference.registrations.joins(:vchoices).uniq
else else
@volunteers = @conference.registrations.where(volunteer: true) @conference.registrations.where(volunteer: true)
end end
else else
authorize! :index, :volunteer authorize! :index, :volunteer

View file

@ -0,0 +1,97 @@
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]
def index
@booths = current_user.booths.where(conference_id: @conference.id).uniq
end
def show; end
def new
@url = conference_booths_path(@conference.short_title)
end
def create
@url = conference_booths_path(@conference.short_title)
@booth.submitter = current_user
if @booth.save
redirect_to conference_booths_path,
notice: 'Booth successfully created.'
else
flash.now[:error] = "Creating booth failed. #{@booth.errors.full_messages.to_sentence}."
render :new
end
end
def edit
@url = conference_booth_path(@conference.short_title, @booth.id)
end
def update
@url = conference_booth_path(@conference.short_title, @booth.id)
@booth.update_attributes(booth_params)
if @booth.save
redirect_to conference_booths_path,
notice: 'Booth successfully updated!'
else
flash.now[:error] = "Booth could not be updated. #{@booth.errors.full_messages.to_sentence}."
end
end
def destroy; end
def withdraw
authorize! :update, @booth
@url = conference_booth_path(@conference.short_title, @booth.id)
@booth.withdraw!
if @booth.save
redirect_to conference_booths_path,
notice: 'Booth successfully withdrawn'
else
flash.now[:error] = "Booth could not be withdrawn. #{@booth.errors.full_messages.to_sentence}."
end
end
def confirm
authorize! :update, @booth
@url = conference_booth_path(@conference.short_title, @booth.id)
@booth.confirm!
if @booth.save
redirect_to conference_booths_path,
notice: 'Booth successfully confirmed'
else
flash.now[:error] = "Booth could not be confirmed. #{@booth.errors.full_messages.to_sentence}."
end
end
def restart
authorize! :update, @booth
@url = conference_booth_path(@conference.short_title, @booth.id)
@booth.restart!
if @booth.save
redirect_to conference_booths_path,
notice: 'Booth successfully re-submitted'
else
flash.now[:error] = "Booth could not be re-submitted. #{@booth.errors.full_messages.to_sentence}."
end
end
private
def booth_params
params.require(:booth).permit(:title, :description, :reasoning, :state, :picture, :conference_id,
:created_at, :updated_at, :submitter_relationship, :website_url, responsible_ids: [])
end
end

View file

@ -27,8 +27,9 @@ class ConferenceRegistrationsController < ApplicationController
end end
def show def show
@total_price = Ticket.total_price(@conference, current_user, paid: true) @total_price = Ticket.total_price_user(@conference, current_user, paid: true)
@tickets = current_user.ticket_purchases.by_conference(@conference).paid @tickets = current_user.ticket_purchases.by_conference(@conference).paid
@total_price_per_ticket = @tickets.group(:ticket_id).sum('amount_paid * quantity')
@ticket_payments = @tickets.group_by(&:ticket_id) @ticket_payments = @tickets.group_by(&:ticket_id)
@total_quantity = @tickets.group(:ticket_id).sum(:quantity) @total_quantity = @tickets.group(:ticket_id).sum(:quantity)
end end
@ -38,11 +39,11 @@ class ConferenceRegistrationsController < ApplicationController
def create def create
@registration = @conference.registrations.new(registration_params) @registration = @conference.registrations.new(registration_params)
if current_user.nil? @user = if current_user.nil?
# @user variable needs to be set so that _sign_up_form_embedded works properly # @user variable needs to be set so that _sign_up_form_embedded works properly
@user = @registration.build_user(user_params) @registration.build_user(user_params)
else else
@user = current_user current_user
end end
@registration.user = @user @registration.user = @user

View file

@ -1,18 +1,29 @@
class ConferencesController < ApplicationController class ConferencesController < ApplicationController
protect_from_forgery with: :null_session protect_from_forgery with: :null_session
before_action :respond_to_options before_action :respond_to_options
load_and_authorize_resource find_by: :short_title load_and_authorize_resource find_by: :short_title, except: :show
load_resource :program, through: :conference, singleton: true, except: :index
def index def index
@current = Conference.where('end_date >= ?', Date.current).reorder(start_date: :asc) @current = Conference.where('end_date >= ?', Date.current).reorder(start_date: :asc)
@antiquated = @conferences - @current @antiquated = @conferences - @current
end end
def show; end def show
@conference = if params[:id]
Conference.find_by_short_title(params[:id])
else
load_conference_by_domain
end
authorize! :show, @conference
@program = @conference.program
end
private private
def load_conference_by_domain
Conference.find_by(custom_domain: request.domain)
end
def respond_to_options def respond_to_options
respond_to do |format| respond_to do |format|
format.html { head :ok } format.html { head :ok }

View file

@ -0,0 +1,9 @@
class OpenidsController < ApplicationController
load_and_authorize_resource :user
load_and_authorize_resource through: :user
def destroy
@openid.destroy
redirect_to :back
end
end

View file

@ -0,0 +1,7 @@
class OrganizationsController < ApplicationController
load_and_authorize_resource :organization
def index
@organizations = Organization.all
end
end

View file

@ -10,6 +10,9 @@ class PaymentsController < ApplicationController
def new def new
@total_amount_to_pay = Ticket.total_price(@conference, current_user, paid: false) @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
@unpaid_ticket_purchases = current_user.ticket_purchases.unpaid.by_conference(@conference) @unpaid_ticket_purchases = current_user.ticket_purchases.unpaid.by_conference(@conference)
end end
@ -18,7 +21,7 @@ class PaymentsController < ApplicationController
if @payment.purchase && @payment.save if @payment.purchase && @payment.save
update_purchased_ticket_purchases update_purchased_ticket_purchases
redirect_to conference_conference_registration_path(@conference.short_title), redirect_to conference_physical_tickets_path,
notice: 'Thanks! Your ticket is booked successfully.' notice: 'Thanks! Your ticket is booked successfully.'
else else
@total_amount_to_pay = Ticket.total_price(@conference, current_user, paid: false) @total_amount_to_pay = Ticket.total_price(@conference, current_user, paid: false)
@ -38,6 +41,8 @@ class PaymentsController < ApplicationController
end end
def update_purchased_ticket_purchases def update_purchased_ticket_purchases
current_user.ticket_purchases.by_conference(@conference).unpaid.update_all(paid: true, payment_id: @payment.id) current_user.ticket_purchases.by_conference(@conference).unpaid.each do |ticket_purchase|
ticket_purchase.pay(@payment)
end
end end
end end

View file

@ -0,0 +1,28 @@
class PhysicalTicketsController < ApplicationController
before_action :authenticate_user!
load_resource :conference, find_by: :short_title
load_and_authorize_resource find_by: :token
authorize_resource :conference_registrations, class: Registration
def index
@physical_tickets = current_user.physical_tickets.by_conference(@conference)
@unpaid_ticket_purchases = current_user.ticket_purchases.by_conference(@conference).unpaid
end
def show
@file_name = "ticket_for_#{@conference.short_title}"
@user = @physical_ticket.user
@ticket_layout = @conference.ticket_layout.to_sym
@qrcode_image = RQRCode::QRCode.new(@physical_ticket.token).as_png(size: 180, border_modules: 0)
respond_to do |format|
format.html
format.pdf do
pdf = TicketPdf.new(@conference, @user, @physical_ticket, @ticket_layout, @file_name)
send_data pdf.render,
filename: @file_name,
type: 'application/pdf',
disposition: 'attachment'
end
end
end
end

View file

@ -49,6 +49,14 @@ class ProposalsController < ApplicationController
# by default. # by default.
@event.speakers = [current_user] @event.speakers = [current_user]
@event.submitter = current_user @event.submitter = current_user
track = Track.find_by(id: params[:event][:track_id])
if track && !track.cfp_active
flash.now[:error] = 'You have selected a track that doesn\'t accept proposals'
render action: 'new'
return
end
if @event.save if @event.save
ahoy.track 'Event submission', title: 'New submission' ahoy.track 'Event submission', title: 'New submission'
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.'
@ -61,6 +69,13 @@ class ProposalsController < ApplicationController
def update def update
@url = conference_program_proposal_path(@conference.short_title, params[:id]) @url = conference_program_proposal_path(@conference.short_title, params[:id])
track = Track.find_by(id: params[:event][:track_id])
if track && !track.cfp_active
flash.now[:error] = 'You have selected a track that doesn\'t accept proposals'
render action: 'edit'
return
end
if @event.update(event_params) if @event.update(event_params)
redirect_to conference_program_proposals_path(conference_id: @conference.short_title), redirect_to conference_program_proposals_path(conference_id: @conference.short_title),
notice: 'Proposal was successfully updated.' notice: 'Proposal was successfully updated.'

View file

@ -21,10 +21,17 @@ class SchedulesController < ApplicationController
# the schedule takes you to today if it is a date of the schedule # the schedule takes you to today if it is a date of the schedule
@current_day = @conference.current_conference_day @current_day = @conference.current_conference_day
@day = @current_day.present? ? @current_day : @dates.first @day = @current_day.present? ? @current_day : @dates.first
return unless @current_day unless @current_day
# the schedule takes you to the current time if it is beetween the start and the end time. # 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) @hour_column = @conference.hours_from_start_time(@conf_start, @conference.end_hour)
end end
# Ids of the 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
end
@selected_schedules_ids.compact!
end
def events def events
@dates = @conference.start_date..@conference.end_date @dates = @conference.start_date..@conference.end_date
@ -32,11 +39,7 @@ class SchedulesController < ApplicationController
@events_schedules = @program.selected_event_schedules @events_schedules = @program.selected_event_schedules
@events_schedules = [] unless @events_schedules @events_schedules = [] unless @events_schedules
@unscheduled_events = if @program.selected_schedule @unscheduled_events = @program.events.confirmed - @events_schedules.map(&:event)
@program.events.confirmed - @program.selected_schedule.events
else
@program.events.confirmed
end
day = @conference.current_conference_day day = @conference.current_conference_day
@tag = day.strftime('%Y-%m-%d') if day @tag = day.strftime('%Y-%m-%d') if day

View file

@ -5,17 +5,19 @@ class SubscriptionsController < ApplicationController
def create def create
@subscription = current_user.subscriptions.build(conference_id: @conference.id) @subscription = current_user.subscriptions.build(conference_id: @conference.id)
if @subscription.save! if @subscription.save
redirect_to root_path, notice: "You have been subscribed to receive email notifications for #{@conference.short_title}." redirect_to root_path, notice: "You have subscribed to receive email notifications for #{@conference.title}."
else else
redirect_to root_path, error: subscription.errors.full_messages.to_sentence redirect_to root_path, error: @subscription.errors.full_messages.to_sentence
end end
end end
def destroy def destroy
@subscription = current_user.subscriptions.find_by(conference_id: @conference.id) @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 if @subscription.destroy
redirect_to root_path, notice: "You have been unsubscribed and now you will not be receiving email notifications for #{@conference.short_title}." redirect_to root_path, notice: "You have unsubscribed and you will not be receiving email notifications for #{@conference.title}."
else else
redirect_to root_path, error: @subscription.errors.full_messages.to_sentence redirect_to root_path, error: @subscription.errors.full_messages.to_sentence
end end

View file

@ -2,6 +2,7 @@ class TicketPurchasesController < ApplicationController
before_filter :authenticate_user! before_filter :authenticate_user!
load_resource :conference, find_by: :short_title load_resource :conference, find_by: :short_title
authorize_resource :conference_registrations, class: Registration authorize_resource :conference_registrations, class: Registration
authorize_resource
def create def create
current_user.ticket_purchases.by_conference(@conference).unpaid.destroy_all current_user.ticket_purchases.by_conference(@conference).unpaid.destroy_all
@ -11,18 +12,22 @@ class TicketPurchasesController < ApplicationController
redirect_to new_conference_payment_path, redirect_to new_conference_payment_path,
notice: 'Please pay here to get tickets.' notice: 'Please pay here to get tickets.'
elsif current_user.ticket_purchases.by_conference(@conference).paid.any? elsif current_user.ticket_purchases.by_conference(@conference).paid.any?
redirect_to conference_conference_registration_path(@conference.short_title), redirect_to conference_physical_tickets_path,
notice: 'You have free tickets for the conference.' notice: 'You have free tickets for the conference.'
else else
redirect_to conference_tickets_path(@conference.short_title), redirect_to conference_tickets_path(@conference.short_title),
error: 'Please get at least one ticket to continue.' error: 'Please get at least one ticket to continue.'
end end
else else
redirect_to conference_conference_registration_path(@conference.short_title), redirect_to conference_tickets_path(@conference.short_title),
error: "Oops, something went wrong with your purchase! #{message}" error: "Oops, something went wrong with your purchase! #{message}"
end end
end end
def index
@unpaid_ticket_purchases = current_user.ticket_purchases.by_conference(@conference).unpaid
end
private private
def ticket_purchase_params def ticket_purchase_params

View file

@ -0,0 +1,70 @@
class TracksController < ApplicationController
load_resource :conference, find_by: :short_title
load_resource :program, through: :conference, singleton: true
load_and_authorize_resource through: :program, find_by: :short_name
def index
@tracks = @tracks.where(submitter: current_user)
end
def show; end
def new
@track = @program.tracks.new(color: @conference.next_color_for_collection(:tracks))
end
def edit; end
def create
@track = @program.tracks.new(track_params)
@track.submitter = current_user
@track.cfp_active = false
if @track.save
redirect_to conference_program_tracks_path(conference_id: @conference.short_title),
notice: 'Track request successfully created.'
else
flash.now[:error] = "Creating Track request failed: #{@track.errors.full_messages.join('. ')}."
render :new
end
end
def update
if @track.update_attributes(track_params)
redirect_to conference_program_tracks_path(conference_id: @conference.short_title),
notice: 'Track request successfully updated.'
else
flash.now[:error] = "Track request update failed: #{@track.errors.full_messages.join('. ')}."
render :edit
end
end
def restart
update_state(:restart, "Track #{@track.name} re-submitted.")
end
def confirm
update_state(:confirm, "Track #{@track.name} confirmed.")
end
def withdraw
update_state(:withdraw, "Track #{@track.name} withdrawn.")
end
private
def track_params
params.require(:track).permit(:name, :description, :color, :short_name, :start_date, :end_date, :relevance)
end
def update_state(transition, notice)
errors = @track.update_state(transition)
if errors.blank?
flash[:notice] = notice
else
flash[:error] = errors
end
redirect_back_or_to(conference_program_tracks_path(conference_id: @conference.short_title))
end
end

View file

@ -11,13 +11,13 @@ module Users
def handle(provider) def handle(provider)
auth_hash = request.env['omniauth.auth'] auth_hash = request.env['omniauth.auth']
uid = auth_hash[:uid] username = auth_hash.info.email.split('@')[0]
openid = Openid.find_for_oauth(auth_hash) # Get or create openid openid = Openid.find_for_oauth(auth_hash) # Get or create openid
# If openid exists and is associated with a user, sign in with associated user, # If openid exists and is associated with a user, sign in with associated user,
# even if the email of the associated user and the email of the provided openid are different # even if the email of the associated user and the email of the provided openid are different
unless (user = openid.user) unless (user = openid.user)
user = User.find_for_auth(auth_hash, current_user) # Get or create users user = User.find_for_auth(auth_hash, current_user) # Get or create users
user.username = "#{uid}@#{provider}" if user.username.blank? user.username = "#{username}@#{provider}" if user.username.blank?
end end
begin begin

View file

@ -33,6 +33,11 @@ module ApplicationHelper
result result
end end
# Returns time with conference timezone
def time_with_timezone(time)
time.strftime('%F %R') + ' ' + @conference.timezone.to_s
end
# Set resource_name for devise so that we can call the devise help links (views/devise/shared/_links) from anywhere (eg sign_up form in proposals#new) # Set resource_name for devise so that we can call the devise help links (views/devise/shared/_links) from anywhere (eg sign_up form in proposals#new)
def resource_name def resource_name
:user :user
@ -51,7 +56,7 @@ module ApplicationHelper
end end
def tracks(conference) def tracks(conference)
all = conference.program.tracks.map {|t| t.name} all = conference.program.tracks.confirmed.cfp_active.pluck(:name)
first = all[0...-1] first = all[0...-1]
last = all[-1] last = all[-1]
ts = '' ts = ''
@ -93,25 +98,6 @@ module ApplicationHelper
.reverse.sub(',', ' dna ').reverse .reverse.sub(',', ' dna ').reverse
end end
# Recieves a model_name and id
# Returns nil if model_name is invalid
# Returns object in its current state if its alive
# Otherwise Returns object state just before deletion
def current_or_last_object_state(model_name, id)
return nil unless id.present? && model_name.present?
begin
object = model_name.constantize.find_by(id: id)
rescue NameError
return nil
end
if object.nil?
object_last_version = PaperTrail::Version.where(item_type: model_name, item_id: id).last
object = object_last_version.reify if object_last_version
end
object
end
def normalize_array_length(hashmap, length) def normalize_array_length(hashmap, length)
hashmap.each do |_, value| hashmap.each do |_, value|
if value.length < length if value.length < length
@ -131,7 +117,7 @@ module ApplicationHelper
def concurrent_events(event) def concurrent_events(event)
return nil unless event.scheduled? && event.program.selected_event_schedules return nil unless event.scheduled? && event.program.selected_event_schedules
event_schedule = event.program.selected_event_schedules.find_by(event: event) 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 { |other_event_schedule| other_event_schedule == event_schedule }
concurrent_events = [] concurrent_events = []
@ -147,7 +133,7 @@ module ApplicationHelper
end end
def speaker_links(event) def speaker_links(event)
event.speakers.map{ |speaker| link_to speaker.name, admin_user_path(speaker) }.join(', ').html_safe safe_join(event.speakers.map{ |speaker| link_to speaker.name, admin_user_path(speaker) }, ',')
end end
def speaker_selector_input(form) def speaker_selector_input(form)
@ -157,6 +143,14 @@ module ApplicationHelper
include_blank: false, label: 'Speakers', input_html: { class: 'select-help-toggle', multiple: 'true' } include_blank: false, label: 'Speakers', input_html: { class: 'select-help-toggle', multiple: 'true' }
end end
def responsibles_selector_input(form)
users = User.active.pluck(:id, :name, :username, :email).map { |user| [user[0], user[1].blank? ? user[2] : user[1], user[2], user[3]] }.sort_by { |user| user[1].downcase }
form.input :responsibles, as: :select,
collection: options_for_select(users.map {|user| ["#{user[1]} (#{user[2]}) #{user[3]}", user[0]]}, @booth.responsibles.map(&:id)),
include_blank: false, label: 'Responsibles', input_html: { class: 'select-help-toggle', multiple: 'true' },
hint: 'The people responsible for the booth. You can only select existing users.'
end
def event_types(conference) def event_types(conference)
conference.program.event_types.map { |et| et.title.pluralize }.to_sentence conference.program.event_types.map { |et| et.title.pluralize }.to_sentence
end end
@ -168,4 +162,13 @@ module ApplicationHelper
new_user_session_path new_user_session_path
end end
end end
##
# ====Gets
# a conference object
# ==== Returns
# class hidden if conference is over
def hidden_if_conference_over(conference)
'hidden' if Date.today > conference.end_date
end
end end

View file

@ -39,4 +39,12 @@ module EventsHelper
content_tag :span, 'REPLACEMENT', class: (['label', 'label-info'] + label_classes) content_tag :span, 'REPLACEMENT', class: (['label', 'label-info'] + label_classes)
end end
end end
def track_selector_input(form)
if @program.tracks.confirmed.cfp_active.any?
form.input :track_id, as: :select,
collection: @program.tracks.confirmed.cfp_active.pluck(:name, :id),
include_blank: '(Please select)'
end
end
end end

View file

@ -15,6 +15,19 @@ module FormatHelper
end end
end end
def booth_status_icon(booth)
case booth.state
when 'new', 'to_reject', 'to_accept'
'fa-eye'
when 'accepted'
'fa-check text-muted'
when 'confirmed'
'fa-check text-success'
when 'rejected', 'withdrawn', 'canceled'
'fa-ban'
end
end
def event_progress_color(progress) def event_progress_color(progress)
progress = progress.to_i progress = progress.to_i
if progress == 100 if progress == 100
@ -102,7 +115,6 @@ module FormatHelper
end end
end end
# rubocop:disable Lint/EndAlignment
def word_pluralize(count, singular, plural = nil) def word_pluralize(count, singular, plural = nil)
word = if (count == 1 || count =~ /^1(\.0+)?$/) word = if (count == 1 || count =~ /^1(\.0+)?$/)
singular singular
@ -176,7 +188,7 @@ module FormatHelper
(schedule == @selected_schedule) ? 'Yes' : 'No' (schedule == @selected_schedule) ? 'Yes' : 'No'
end end
def markdown(text) def markdown(text, escape_html=true)
return '' if text.nil? return '' if text.nil?
options = { options = {
@ -184,12 +196,12 @@ module FormatHelper
space_after_headers: true, space_after_headers: true,
no_intra_emphasis: true no_intra_emphasis: true
} }
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(escape_html: true), options) markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(escape_html: escape_html), options)
markdown.render(text).html_safe markdown.render(text).html_safe
end end
def markdown_hint(text='') 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") markdown("#{text} Please look at #{link_to '**Markdown Syntax**', 'https://daringfireball.net/projects/markdown/syntax', target: '_blank'} to format your text", false)
end end
def quantity_left_of(resource) def quantity_left_of(resource)

View file

@ -2,14 +2,6 @@ module PathsHelper
## ##
# Includes functions related to links or redirects # Includes functions related to links or redirects
## ##
def link_to_user(user_id)
user = User.find_by(id: user_id)
if user
link_to user.name, admin_user_path(id: user_id)
else
'Someone (probably via the console)'
end
end
def active_nav_li(link) def active_nav_li(link)
if current_page?(link) if current_page?(link)

View file

@ -2,25 +2,78 @@ module VersionsHelper
## ##
# Groups functions related to change description # Groups functions related to change description
## ##
def link_if_alive(version, link_text, link_url) def link_if_alive(version, link_text, link_url, conference)
version.item ? link_to(link_text, link_url) : link_text version.item && conference ? link_to(link_text, link_url) : "#{link_text} with ID #{version.item_id}"
end
def link_to_organization(organization_id)
return 'deleted organization' unless organization_id
org = Organization.find_by(id: organization_id)
return current_or_last_object_state('Organization', organization_id).try(:name) unless org
org.name.to_s
end
def link_to_conference(conference_id)
return 'deleted conference' if conference_id.nil?
conference = Conference.find_by(id: conference_id)
if conference
link_to conference.short_title,
edit_admin_conference_path(conference.short_title)
else
short_title = current_or_last_object_state('Conference', conference_id).try(:short_title) || ''
" #{short_title} with ID #{conference_id}"
end
end
def link_to_user(user_id)
return 'Someone (probably via the console)' unless user_id
user = User.find_by(id: user_id)
if user
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}"
end
end
# Receives a model_name and id
# Returns nil if model_name is invalid
# Returns object in its current state if its alive
# Otherwise Returns object state just before deletion
def current_or_last_object_state(model_name, id)
return nil unless id.present? && model_name.present?
begin
object = model_name.constantize.find_by(id: id)
rescue NameError
return nil
end
if object.nil?
object_last_version = PaperTrail::Version.where(item_type: model_name, item_id: id).last
object = object_last_version.reify if object_last_version
end
object
end end
def subscription_change_description(version) def subscription_change_description(version)
user = current_or_last_object_state(version.item_type, version.item_id).user user_id = current_or_last_object_state(version.item_type, version.item_id).user_id
user_name = user.name unless user.id.to_s == version.whodunnit 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" version.event == 'create' ? "subscribed #{user_name} to" : "unsubscribed #{user_name} from"
end end
def registration_change_description(version) def registration_change_description(version)
if version.item_type == 'Registration' if version.item_type == 'Registration'
user = current_or_last_object_state(version.item_type, version.item_id).user user_id = current_or_last_object_state(version.item_type, version.item_id).user_id
elsif version.item_type == 'EventsRegistration' elsif version.item_type == 'EventsRegistration'
registration_id = current_or_last_object_state(version.item_type, version.item_id).registration_id registration_id = current_or_last_object_state(version.item_type, version.item_id).registration_id
user = current_or_last_object_state('Registration', registration_id).user user_id = current_or_last_object_state('Registration', registration_id).user_id
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
if user.id.to_s == version.whodunnit if user_id.to_s == version.whodunnit
case version.event case version.event
when 'create' then 'registered to' when 'create' then 'registered to'
when 'update' then "updated #{updated_attributes(version)} of the registration for" when 'update' then "updated #{updated_attributes(version)} of the registration for"
@ -28,9 +81,9 @@ module VersionsHelper
end end
else else
case version.event case version.event
when 'create' then "registered #{user.name} to" when 'create' then "registered #{user_name} to"
when 'update' then "updated #{updated_attributes(version)} of #{user.name}'s registration for" when 'update' then "updated #{updated_attributes(version)} of #{user_name}'s registration for"
when 'destroy' then "unregistered #{user.name} from" when 'destroy' then "unregistered #{user_name} from"
end end
end end
end end

View file

@ -8,6 +8,22 @@ class Mailbot < ActionMailer::Base
conference.email_settings.registration_body)) conference.email_settings.registration_body))
end end
def ticket_confirmation_mail(ticket_purchase)
@ticket_purchase = ticket_purchase
@conference = ticket_purchase.conference
@user = ticket_purchase.user
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}")
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!")
end
def acceptance_mail(event) def acceptance_mail(event)
conference = event.program.conference conference = event.program.conference
@ -81,6 +97,24 @@ class Mailbot < ActionMailer::Base
conference.email_settings.cfp_dates_updated_body)) conference.email_settings.cfp_dates_updated_body))
end end
def conference_booths_acceptance_mail(booth)
conference = booth.conference
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))
end
def conference_booths_rejection_mail(booth)
conference = booth.conference
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))
end
def event_comment_mail(comment, user) def event_comment_mail(comment, user)
@comment = comment @comment = comment
@event = @comment.commentable @event = @comment.commentable

View file

@ -3,32 +3,19 @@ class Ability
# Initializes the ability class # Initializes the ability class
def initialize(user) def initialize(user)
# Order Abilities
# (Check https://github.com/CanCanCommunity/cancancan/wiki/Ability-Precedence)
# Check roles of user, using rolify. Role name is *case sensitive*
# user.is_organizer? or user.has_role? :organizer
# user.is_cfp_of? Conference or user.has_role? :cfp, Conference
# user.is_info_desk_of? Conference
# user.is_volunteers_coordinator_of? Conference
# user.is_attendee_of? Conference
# The following is wrong because a user will only have 'cfp' role for a specific conference
# user.is_cfp? # This is always false
user ||= User.new user ||= User.new
# This is what sets up the different abilities
if user.new_record? if user.new_record?
not_signed_in not_signed_in
# Checks if the user does not have any role and is not an admin
elsif user.roles.any? || user.is_admin
signed_in_with_roles(user)
else else
signed_in(user) signed_in(user)
common_abilities_for_admins(user) if user.roles.any? || user.is_admin?
end end
end end
# Abilities for not signed in users (guests) # Abilities for not signed in users (guests)
def not_signed_in def not_signed_in
can [:index], Organization
can [:index], Conference can [:index], Conference
can [:show], Conference do |conference| can [:show], Conference do |conference|
conference.splashpage && conference.splashpage.public == true conference.splashpage && conference.splashpage.public == true
@ -73,7 +60,6 @@ class Ability
def signed_in(user) def signed_in(user)
# Abilities from not_signed_in user are also inherited # Abilities from not_signed_in user are also inherited
not_signed_in not_signed_in
can :manage, User, id: user.id can :manage, User, id: user.id
can :manage, Registration, user_id: user.id can :manage, Registration, user_id: user.id
@ -83,9 +69,19 @@ class Ability
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 end
can :index, Organization
can :index, Ticket can :index, Ticket
can :manage, TicketPurchase, user_id: user.id can :manage, TicketPurchase, user_id: user.id
can [:new, :create], Payment, user_id: user.id can [:new, :create], Payment, user_id: user.id
can [:index, :show], PhysicalTicket, user: user
can [:new, :create], Booth do |booth|
booth.new_record? && booth.conference.program.cfps.for_booths.try(:open?)
end
can [:edit, :update, :index, :show], Booth do |booth|
booth.users.include?(user)
end
can [:create, :destroy], Subscription, user_id: user.id can [:create, :destroy], Subscription, user_id: user.id
@ -93,181 +89,58 @@ class Ability
event.program.cfp_open? && event.new_record? event.program.cfp_open? && event.new_record?
end end
can [:update, :show, :delete, :index], Event do |event| can [:update, :show, :index], Event do |event|
event.users.include?(user) event.users.include?(user)
end end
# can manage the commercials of their own events # can manage the commercials of their own events
can :manage, Commercial, commercialable_type: 'Event', commercialable_id: user.events.pluck(:id) can :manage, Commercial, commercialable_type: 'Event', commercialable_id: user.events.pluck(:id)
can [:destroy], Openid
can [:new, :create], Track do |track|
track.new_record? && track.program.cfps.for_tracks.try(:open?)
end end
# Abilities for signed in users with roles can [:index, :show, :restart, :confirm, :withdraw], Track, submitter_id: user.id
def signed_in_with_roles(user)
# Abilities from not_signed_in and signed_in are also inherited
signed_in(user)
signed_in_with_organizer_role(user) if user.has_role? :organizer, :any can [:edit, :update], Track do |track|
signed_in_with_cfp_role(user) if user.has_role? :cfp, :any user == track.submitter && !(track.accepted? || track.confirmed?)
signed_in_with_info_desk_role(user) if user.has_role? :info_desk, :any end
signed_in_with_volunteers_coordinator_role(user) if user.has_role? :volunteers_coordinator, :any end
# for users with any role # Abilities for users with roles wandering around in non-admin views.
def common_abilities_for_admins(user)
can :access, Admin can :access, Admin
can [:show], Conference can :manage, :all if user.is_admin?
can :index, Commercial, commercialable_type: 'Conference'
cannot [: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['OSEM_ICHAIN_ENABLED'] == 'true'
cannot :revert_object, PaperTrail::Version do |version|
(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?
end
cannot :destroy, Program
# Do not delete venue, when there are rooms being used
cannot :destroy, Venue do |venue|
venue.conference.program.events.where.not(room_id: nil).any?
end
end
def signed_in_with_organizer_role(user)
# ids of all the conferences for which the user has the 'organizer' role
conf_ids_for_organizer = Conference.with_role(:organizer, user).pluck(:id) conf_ids_for_organizer = Conference.with_role(:organizer, user).pluck(:id)
can :manage, Resource, conference_id: conf_ids_for_organizer
can [:new, :create], Conference if user.has_role?(:organizer, :any)
can :manage, Conference, id: conf_ids_for_organizer
can :manage, Splashpage, conference_id: conf_ids_for_organizer
can :manage, Contact, conference_id: conf_ids_for_organizer
can :manage, EmailSettings, conference_id: conf_ids_for_organizer
can :manage, Campaign, conference_id: conf_ids_for_organizer
can :manage, Target, conference_id: conf_ids_for_organizer
can :manage, Commercial, commercialable_type: 'Conference',
commercialable_id: conf_ids_for_organizer
can :manage, Registration, conference_id: conf_ids_for_organizer
can :manage, RegistrationPeriod, conference_id: conf_ids_for_organizer
can :manage, Question, conference_id: conf_ids_for_organizer
can :manage, Question do |question|
!(question.conferences.pluck(:id) & conf_ids_for_organizer).empty?
end
can :manage, Vposition, conference_id: conf_ids_for_organizer
can :manage, Vday, conference_id: conf_ids_for_organizer
can :manage, Program, conference_id: conf_ids_for_organizer
can :manage, Schedule, program: { conference_id: conf_ids_for_organizer }
can :manage, EventSchedule, schedule: { program: { conference_id: conf_ids_for_organizer } }
can :manage, Cfp, program: { conference_id: conf_ids_for_organizer}
can :manage, Event, program: { conference_id: conf_ids_for_organizer}
can :manage, EventType, program: { conference_id: conf_ids_for_organizer}
can :manage, Track, program: { conference_id: conf_ids_for_organizer}
can :manage, DifficultyLevel, program: { conference_id: conf_ids_for_organizer}
can :manage, Commercial, commercialable_type: 'Event',
commercialable_id: Event.where(program_id: Program.where(conference_id: conf_ids_for_organizer).pluck(:id)).pluck(:id)
can :manage, Venue, conference_id: conf_ids_for_organizer
can :manage, Commercial, commercialable_type: 'Venue',
commercialable_id: Venue.where(conference_id: conf_ids_for_organizer).pluck(:id)
can :manage, Lodging, conference_id: conf_ids_for_organizer
can :manage, Room, venue: { conference_id: conf_ids_for_organizer}
can :manage, Sponsor, conference_id: conf_ids_for_organizer
can :manage, SponsorshipLevel, conference_id: conf_ids_for_organizer
can :manage, Ticket, conference_id: conf_ids_for_organizer
can :index, Comment, commentable_type: 'Event',
commentable_id: Event.where(program_id: Program.where(conference_id: conf_ids_for_organizer).pluck(:id)).pluck(:id)
# Abilities for Role (Conference resource)
can [:index, :show], Role
can [:edit, :update, :toggle_user], Role do |role|
role.resource_type == 'Conference' && (conf_ids_for_organizer.include? role.resource_id)
end
can [:index, :revert_object, :revert_attribute], PaperTrail::Version do |version|
version.item_type == 'User' || (conf_ids_for_organizer.include? version.conference_id)
end
end
def signed_in_with_cfp_role(user)
# ids of all the conferences for which the user has the 'cfp' role
conf_ids_for_cfp = Conference.with_role(:cfp, user).pluck(:id) conf_ids_for_cfp = Conference.with_role(:cfp, user).pluck(:id)
can [:index, :show, :update], Resource, 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 }
can :manage, Track, program: { conference_id: conf_ids_for_cfp }
can :manage, DifficultyLevel, program: { conference_id: conf_ids_for_cfp }
can :manage, EmailSettings, conference_id: conf_ids_for_cfp
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 :manage, Cfp, program: { conference_id: conf_ids_for_cfp }
can :manage, Program, conference_id: conf_ids_for_cfp
can :manage, Commercial, commercialable_type: 'Event',
commercialable_id: Event.where(program_id: Program.where(conference_id: conf_ids_for_cfp).pluck(:id)).pluck(:id)
can :index, Comment, commentable_type: 'Event',
commentable_id: Event.where(program_id: Program.where(conference_id: conf_ids_for_cfp).pluck(:id)).pluck(:id)
# Abilities for Role (Conference resource)
can [:index, :show], Role
# Can add or remove users from role, when user has that same role for the conference
# Eg. If you are member of the CfP team, you can add more CfP team members (add users to the role 'CfP')
can :toggle_user, Role do |role|
role.resource_type == 'Conference' && role.name == 'cfp' &&
(Conference.with_role(:cfp, user).pluck(:id).include? role.resource_id)
end
can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'Event', conference_id: conf_ids_for_cfp
can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'Vote', conference_id: conf_ids_for_cfp
can [:index, :revert_object, :revert_attribute], PaperTrail::Version do |version|
version.item_type == 'Commercial' && conf_ids_for_cfp.include?(version.conference_id) &&
(version.object.to_s.include?('Event') || version.object_changes.to_s.include?('Event'))
end
end
def signed_in_with_info_desk_role(user)
# ids of all the conferences for which the user has the 'info_desk' role
conf_ids_for_info_desk = Conference.with_role(:info_desk, user).pluck(:id) conf_ids_for_info_desk = Conference.with_role(:info_desk, user).pluck(:id)
can [:index, :show, :update], Resource, conference_id: conf_ids_for_info_desk if conf_ids_for_organizer
# To access splashpage of their conference if it is not public
can :show, Conference, id: conf_ids_for_organizer
# To access conference/proposals/registrations
can :manage, Registration, conference_id: conf_ids_for_organizer
# To access conference/proposals
can :manage, Event, program: { conference_id: conf_ids_for_organizer }
# To access comment link in menu bar
can :index, Comment, commentable_type: 'Event',
commentable_id: Event.where(program_id: Program.where(conference_id: conf_ids_for_organizer).pluck(:id)).pluck(:id)
end
if conf_ids_for_cfp
# To access comment link in menu bar
can :index, Comment, commentable_type: 'Event',
commentable_id: Event.where(program_id: Program.where(conference_id: conf_ids_for_cfp).pluck(:id)).pluck(:id)
# To access conference/proposals
can :manage, Event, program: { conference_id: conf_ids_for_cfp }
end
if conf_ids_for_info_desk
# To access conference/proposals/registrations
can :manage, Registration, 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|
!(question.conferences.pluck(:id) & conf_ids_for_info_desk).empty?
end
# Abilities for Role (Conference resource)
can [:index, :show], Role
# Can add or remove users from role, when user has that same role for the conference
# Eg. If you are member of the CfP team, you can add more CfP team members (add users to the role 'CfP')
can :toggle_user, Role do |role|
role.resource_type == 'Conference' && role.name == 'info_desk' &&
(Conference.with_role(:info_desk, user).pluck(:id).include? role.resource_id)
end
end
def signed_in_with_volunteers_coordinator_role(user)
# ids of all the conferences for which the user has the 'volunteers_coordinator' role
conf_ids_for_volunteers_coordinator = Conference.with_role(:volunteers_coordinator, user).pluck(:id)
can [: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
# Abilities for Role (Conference resource)
can [:index, :show], Role
# Can add or remove users from role, when user has that same role for the conference
# Eg. If you are member of the CfP team, you can add more CfP team members (add users to the role 'CfP')
can :toggle_user, Role do |role|
role.resource_type == 'Conference' && role.name == 'volunteers_coordinator' &&
(Conference.with_role(:volunteers_coordinator, user).pluck(:id).include? role.resource_id)
end end
end end
end end

324
app/models/admin_ability.rb Normal file
View file

@ -0,0 +1,324 @@
class AdminAbility
include CanCan::Ability
def initialize(user)
# Order Abilities
# (Check https://github.com/CanCanCommunity/cancancan/wiki/Ability-Precedence)
# Check roles of user, using rolify. Role name is *case sensitive*
# user.is_organizer? or user.has_role? :organizer
# user.is_cfp_of? Conference or user.has_role? :cfp, Conference
# user.is_info_desk_of? Conference
# user.is_volunteers_coordinator_of? Conference
# user.is_attendee_of? Conference
# The following is wrong because a user will only have 'cfp' role for a specific conference
# user.is_cfp? # This is always false
user ||= User.new
signed_in_with_roles(user)
end
def common_abilities_for_roles(user)
can :manage, Registration, user_id: user.id
can :index, Conference
can :show, Registration, &:new_record?
can [:new, :create], Registration do |registration|
conference = registration.conference
conference.registration_open? && !conference.registration_limit_exceeded? || conference.program.speakers.confirmed.include?(user)
end
can [:index, :admins], Organization
can :index, Ticket
can :manage, TicketPurchase, user_id: user.id
can [:new, :create], Payment, user_id: user.id
can [:create, :destroy], Subscription, user_id: user.id
can [:new, :create], Event do |event|
event.program.cfp_open? && event.new_record?
end
# can manage the commercials of their own events
can :manage, Commercial, commercialable_type: 'Event', commercialable_id: user.events.pluck(:id)
can [:destroy], Openid
can :access, Admin
can :index, Commercial, commercialable_type: 'Conference'
cannot [: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['OSEM_ICHAIN_ENABLED'] == 'true'
cannot :revert_object, PaperTrail::Version do |version|
(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?
end
# Can't create cfp if there are no available cfp types
cannot [:new, :create], Cfp do |cfp|
cfp.program.remaining_cfp_types.empty?
end
cannot :destroy, Program
# Do not delete venue, when there are rooms being used
cannot :destroy, Venue do |venue|
venue.conference.program.events.where.not(room_id: nil).any?
end
# Prevent requests for tracks from being destroyed
cannot :destroy, Track do |track|
track.self_organized?
end
# Can't accept a booth when booth_limit is reached
cannot :accept, Booth do |booth|
conference = booth.conference
conference.maximum_accepted_booths?
end
end
# Abilities for signed in users with roles
def signed_in_with_roles(user)
signed_in_with_organization_admin_role(user) if user.has_role? :organization_admin, :any
signed_in_with_organizer_role(user) if user.has_role? :organizer, :any
signed_in_with_cfp_role(user) if user.has_role? :cfp, :any
signed_in_with_info_desk_role(user) if user.has_role? :info_desk, :any
signed_in_with_volunteers_coordinator_role(user) if user.has_role? :volunteers_coordinator, :any
signed_in_with_track_organizer_role(user) if user.has_role? :track_organizer, :any
common_abilities_for_roles(user)
end
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 :new, Conference
can :manage, Conference, organization_id: org_ids_for_organization_admin
can [:index, :show], Role
signed_in_with_organizer_role(user, conf_ids_for_organization_admin)
end
def signed_in_with_organizer_role(user, conf_ids_for_organization_admin = [])
# ids of all the conferences for which the user has the 'organizer' role and
# conferences that belong to organizations for which user is 'organization_admin'
conf_ids = conf_ids_for_organization_admin.concat(Conference.with_role(:organizer, user).pluck(:id)).uniq
# ids of all the tracks that belong to the programs of the above conferences
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 :manage, Splashpage, conference_id: conf_ids
can :manage, Contact, conference_id: conf_ids
can :manage, EmailSettings, conference_id: conf_ids
can :manage, Campaign, conference_id: conf_ids
can :manage, Target, conference_id: conf_ids
can :manage, Commercial, commercialable_type: 'Conference',
commercialable_id: conf_ids
can :manage, Registration, conference_id: conf_ids
can :manage, RegistrationPeriod do |registration_period|
conference = registration_period.conference
conf_ids.include?(conference.id) && conference.tickets.for_registration.any?
end
can :manage, Booth, conference_id: conf_ids
can :manage, Question, conference_id: conf_ids
can :manage, Question do |question|
!(question.conferences.pluck(:id) & conf_ids).empty?
end
can :manage, Vposition, conference_id: conf_ids
can :manage, Vday, conference_id: conf_ids
can :manage, Program, conference_id: conf_ids
can :manage, Schedule, program: { conference_id: conf_ids }
can :manage, EventSchedule, schedule: { program: { conference_id: conf_ids } }
can :manage, Cfp, program: { conference_id: conf_ids }
can :manage, Event, program: { conference_id: conf_ids }
can :manage, EventType, program: { conference_id: conf_ids }
can :manage, Track, program: { conference_id: conf_ids }
can :manage, DifficultyLevel, program: { conference_id: conf_ids }
can :manage, Commercial, commercialable_type: 'Event',
commercialable_id: Event.where(program_id: Program.where(conference_id: conf_ids).pluck(:id)).pluck(:id)
can :manage, Venue, conference_id: conf_ids
can :manage, Commercial, commercialable_type: 'Venue',
commercialable_id: Venue.where(conference_id: conf_ids).pluck(:id)
can :manage, Lodging, conference_id: conf_ids
can :manage, Room, venue: { conference_id: conf_ids }
can :manage, Sponsor, conference_id: conf_ids
can :manage, SponsorshipLevel, conference_id: conf_ids
can :manage, Ticket, conference_id: conf_ids
can :create, TicketScanning do |ticket_scanning|
conf_id = ticket_scanning.physical_ticket.ticket_purchase.conference_id
conf_ids.include? conf_id
end
can :index, Comment, commentable_type: 'Event',
commentable_id: Event.where(program_id: Program.where(conference_id: conf_ids).pluck(:id)).pluck(:id)
# Abilities for Role (Conference resource)
can [:index, :show], Role do |role|
role.resource_type == 'Conference' || role.resource_type == 'Track'
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)
end
can [:index, :revert_object, :revert_attribute], PaperTrail::Version, item_type: 'User'
can [:index, :revert_object, :revert_attribute], PaperTrail::Version, conference_id: conf_ids
end
def signed_in_with_cfp_role(user)
# ids of all the conferences for which the user has the 'cfp' role
conf_ids_for_cfp = Conference.with_role(:cfp, user).pluck(:id)
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 :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 }
can :manage, Track, program: { conference_id: conf_ids_for_cfp }
can :manage, DifficultyLevel, program: { conference_id: conf_ids_for_cfp }
can :manage, EmailSettings, conference_id: conf_ids_for_cfp
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 :manage, Cfp, program: { conference_id: conf_ids_for_cfp }
can :manage, Program, conference_id: conf_ids_for_cfp
can :manage, Commercial, commercialable_type: 'Event',
commercialable_id: Event.where(program_id: Program.where(conference_id: conf_ids_for_cfp).pluck(:id)).pluck(:id)
can :index, Comment, commentable_type: 'Event',
commentable_id: Event.where(program_id: Program.where(conference_id: conf_ids_for_cfp).pluck(:id)).pluck(:id)
# Abilities for Role (Conference resource)
can [:index, :show], Role do |role|
role.resource_type == 'Conference' || role.resource_type == 'Track'
end
# Can add or remove users from role, when user has that same role for the conference
# Eg. If you are member of the CfP team, you can add more CfP team members (add users to the role 'CfP')
can :toggle_user, Role do |role|
role.resource_type == 'Conference' && role.name == 'cfp' &&
(Conference.with_role(:cfp, user).pluck(:id).include? role.resource_id)
end
can [: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|
version.item_type == 'Commercial' && conf_ids_for_cfp.include?(version.conference_id) &&
(version.object.to_s.include?('Event') || version.object_changes.to_s.include?('Event'))
end
end
def signed_in_with_info_desk_role(user)
# ids of all the conferences for which the user has the 'info_desk' role
conf_ids_for_info_desk = Conference.with_role(:info_desk, user).pluck(:id)
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 :manage, Registration, conference_id: conf_ids_for_info_desk
can :manage, Question, conference_id: conf_ids_for_info_desk
can :manage, Question do |question|
!(question.conferences.pluck(:id) & conf_ids_for_info_desk).empty?
end
can :create, TicketScanning do |ticket_scanning|
conf_id = ticket_scanning.physical_ticket.ticket_purchase.conference_id
conf_ids_for_info_desk.include? conf_id
end
# Abilities for Role (Conference resource)
can [:index, :show], Role do |role|
role.resource_type == 'Conference' || role.resource_type == 'Track'
end
# Can add or remove users from role, when user has that same role for the conference
# Eg. If you are member of the CfP team, you can add more CfP team members (add users to the role 'CfP')
can :toggle_user, Role do |role|
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
end
def signed_in_with_volunteers_coordinator_role(user)
# ids of all the conferences for which the user has the 'volunteers_coordinator' role
conf_ids_for_volunteers_coordinator = Conference.with_role(:volunteers_coordinator, user).pluck(:id)
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 :manage, Vposition, conference_id: conf_ids_for_volunteers_coordinator
can :manage, Vday, conference_id: conf_ids_for_volunteers_coordinator
# Abilities for Role (Conference resource)
can [:index, :show], Role do |role|
role.resource_type == 'Conference' || role.resource_type == 'Track'
end
# Can add or remove users from role, when user has that same role for the conference
# Eg. If you are member of the CfP team, you can add more CfP team members (add users to the role 'CfP')
can :toggle_user, Role do |role|
role.resource_type == 'Conference' && role.name == 'volunteers_coordinator' &&
(Conference.with_role(:volunteers_coordinator, user).pluck(:id).include? role.resource_id)
end
end
def signed_in_with_track_organizer_role(user)
# ids of all the conferences for which the user has the 'track organizer' role
conf_ids_for_track_organizer = Track.with_role(:track_organizer, user).joins(:program).pluck(:conference_id)
# ids of all the tracks for which the user has the 'track_organizer' role
track_ids_for_track_organizer = Track.with_role(:track_organizer, user).pluck(:id)
can :show, Conference do |conf|
conf_ids_for_track_organizer.include?(conf.id)
end
# Show Program in the admin sidebar
can :show, Program, conference_id: conf_ids_for_track_organizer
# Show Tracks in the admin sidebar
can :update, Track do |track|
track.new_record? && conf_ids_for_track_organizer.include?(track.program.conference_id)
end
can :manage, Track, id: track_ids_for_track_organizer
cannot [:edit, :update], Track do |track|
track.self_organized_and_accepted_or_confirmed?
end
# Show Roles in the admin sidebar and allow authorization of the index action
can [:index, :show], Role do |role|
role.resource_type == 'Conference' || role.resource_type == 'Track'
end
can :toggle_user, Role do |role|
role.resource_type == 'Track' && track_ids_for_track_organizer.include?(role.resource_id)
end
# Show Events in the admin sidebar
can :update, Event do |event|
event.new_record? && conf_ids_for_track_organizer.include?(event.program.conference_id)
end
can :manage, Event, track_id: track_ids_for_track_organizer
can :manage, Commercial, commercialable_type: 'Event',
commercialable_id: Event.where(track_id: track_ids_for_track_organizer).pluck(:id)
# Show Scheduless in the admin sidebar
can :update, Schedule do |schedule|
schedule.new_record? && conf_ids_for_track_organizer.include?(schedule.program.conference_id)
end
# Show new track schedule button
can :new, Schedule do |schedule|
schedule.new_record? && conf_ids_for_track_organizer.include?(schedule.program.conference_id) && schedule.track.try(:new_record?)
end
can :manage, Schedule, track_id: track_ids_for_track_organizer
can :manage, EventSchedule, schedule: { track_id: track_ids_for_track_organizer }
end
end

83
app/models/booth.rb Normal file
View file

@ -0,0 +1,83 @@
class Booth < ActiveRecord::Base
include ActiveRecord::Transitions
has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id }
belongs_to :conference
has_many :booth_requests, dependent: :destroy
has_many :users, through: :booth_requests
has_one :submitter_booth_user, -> { where(role: 'submitter') }, class_name: 'BoothRequest'
has_one :submitter, through: :submitter_booth_user, source: :user
has_many :responsibles_booth_user, -> { where(role: 'responsible') }, class_name: 'BoothRequest'
has_many :responsibles, through: :responsibles_booth_user, source: :user
validates :title,
uniqueness: { case_sensitive: false },
presence: true
validates :description,
:reasoning,
:state,
:responsibles,
:conference_id,
:website_url,
:submitter_relationship,
presence: true
scope :accepted, -> { where(state: 'accepted') }
scope :confirmed, -> { where(state: 'confirmed') }
mount_uploader :picture, PictureUploader, mount_on: :logo_link
state_machine initial: :new do
state :new
state :withdrawn
state :to_accept
state :accepted
state :to_reject
state :rejected
state :canceled
state :confirmed
event :restart do
transitions to: :new, from: [:withdrawn, :rejected, :canceled]
end
event :withdraw do
transitions to: :withdrawn, from: [: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]
end
event :to_reject do
transitions to: :to_reject, from: [:new, :to_accept]
end
event :accept do
transitions to: :accepted, from: [:new, :to_accept]
end
event :reject do
transitions to: :rejected, from: [:new, :to_reject]
end
event :cancel do
transitions to: :canceled, from: [:accepted, :rejected, :to_accept, :to_reject, :confirmed]
end
end
def transition_possible?(transition)
self.class.state_machine.events_for(current_state).include?(transition)
end
def update_state(transition, _notice)
alert = ''
begin
send(transition)
save
rescue Transitions::InvalidTransition => e
alert = "Update state failed. #{e.message}"
end
alert
end
end

View file

@ -0,0 +1,9 @@
class BoothRequest < ActiveRecord::Base
belongs_to :booth
belongs_to :user
validates :role,
presence: true
ROLES = %w[submitter responsible].freeze
end

View file

@ -1,6 +1,8 @@
# cannot delete program if there are events submitted # cannot delete program if there are events submitted
class Cfp < ActiveRecord::Base class Cfp < ActiveRecord::Base
TYPES = %w(events booths tracks).freeze
has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id }
belongs_to :program belongs_to :program
@ -8,6 +10,15 @@ class Cfp < ActiveRecord::Base
validates :start_date, :end_date, presence: true validates :start_date, :end_date, presence: true
validate :before_end_of_conference validate :before_end_of_conference
validate :start_after_end_date validate :start_after_end_date
validates :cfp_type,
presence: true,
inclusion: {
in: TYPES
},
uniqueness: {
scope: :program,
case_sensitive: false
}
## ##
# Checks whether cfp date is updated # Checks whether cfp date is updated
@ -55,15 +66,52 @@ class Cfp < ActiveRecord::Base
result > 0 ? result : 0 result > 0 ? result : 0
end end
##
# Checks if the call for papers is currently open
#
# ====Returns
# * +false+ -> If the CFP is not set or today isn't in the CFP period.
# * +true+ -> If today is in the CFP period.
def open?
(start_date..end_date).cover?(Date.current)
end
##
# Finds the cfp for events if it exists
#
# ====Returns
# * +Cfp+ -> The cfp with type 'events'
def self.for_events
find_by(cfp_type: 'events')
end
##
# Finds the cfp for tracks if it exists
#
# ====Returns
# * +Cfp+ -> The cfp with type 'tracks'
def self.for_tracks
find_by(cfp_type: 'tracks')
end
##
# Finds the cfp for booths if it exists
#
# ====Returns
# * +Cfp+ -> The cfp with type 'booths'
def self.for_booths
find_by(cfp_type: 'booths')
end
private private
def before_end_of_conference def before_end_of_conference
if program.conference && program.conference.end_date && end_date && (end_date > program.conference.end_date) if program && program.conference && program.conference.end_date && end_date && (end_date > program.conference.end_date)
errors errors
.add(:end_date, "can't be after the conference end date (#{program.conference.end_date})") .add(:end_date, "can't be after the conference end date (#{program.conference.end_date})")
end end
if program.conference && program.conference.end_date && start_date && (start_date > program.conference.end_date) if program && program.conference && program.conference.end_date && start_date && (start_date > program.conference.end_date)
errors errors
.add(:start_date, "can't be after the conference end date (#{program.conference.end_date})") .add(:start_date, "can't be after the conference end date (#{program.conference.end_date})")
end end

View file

@ -8,7 +8,7 @@ class Comment < ActiveRecord::Base
# want user to vote on the quality of comments. # want user to vote on the quality of comments.
#acts_as_votable #acts_as_votable
belongs_to :commentable, polymorphic: true belongs_to :commentable, counter_cache: true, polymorphic: true
# NOTE: Comments belong to a user # NOTE: Comments belong to a user
belongs_to :user belongs_to :user

View file

@ -0,0 +1,11 @@
module RevisionCount
extend ActiveSupport::Concern
included do
after_update :increment_revision
end
def increment_revision
conference.update_column(:revision, conference.revision + 1)
end
end

View file

@ -1,4 +1,5 @@
class Conference < ActiveRecord::Base class Conference < ActiveRecord::Base
include RevisionCount
require 'uri' require 'uri'
serialize :events_per_week, Hash serialize :events_per_week, Hash
# Needed to call 'Conference.with_role' in /models/ability.rb # Needed to call 'Conference.with_role' in /models/ability.rb
@ -6,6 +7,10 @@ class Conference < ActiveRecord::Base
resourcify :roles, dependent: :delete_all resourcify :roles, dependent: :delete_all
default_scope { order('start_date DESC') } default_scope { order('start_date DESC') }
scope :upcoming, (-> { where('end_date >= ?', Date.current) })
scope :past, (-> { where('end_date < ?', Date.current) })
belongs_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 }
@ -17,11 +22,17 @@ class Conference < ActiveRecord::Base
has_one :email_settings, dependent: :destroy has_one :email_settings, dependent: :destroy
has_one :program, dependent: :destroy has_one :program, dependent: :destroy
has_one :venue, dependent: :destroy has_one :venue, dependent: :destroy
has_many :physical_tickets, through: :ticket_purchases
has_many :ticket_purchases, dependent: :destroy has_many :ticket_purchases, dependent: :destroy
has_many :payments, dependent: :destroy has_many :payments, dependent: :destroy
has_many :supporters, through: :ticket_purchases, source: :user has_many :supporters, through: :ticket_purchases, source: :user
has_many :tickets, dependent: :destroy has_many :tickets, dependent: :destroy do
def for_registration
where(registration_ticket: true)
end
end
has_many :resources, dependent: :destroy has_many :resources, dependent: :destroy
has_many :booths, dependent: :destroy
has_many :lodgings, dependent: :destroy has_many :lodgings, dependent: :destroy
has_many :registrations, dependent: :destroy has_many :registrations, dependent: :destroy
@ -53,7 +64,9 @@ class Conference < ActiveRecord::Base
:start_date, :start_date,
:end_date, :end_date,
:start_hour, :start_hour,
:end_hour, presence: true :end_hour,
:ticket_layout,
:organization, presence: true
validates :short_title, uniqueness: true validates :short_title, uniqueness: true
validates :short_title, format: { with: /\A[a-zA-Z0-9_-]*\z/ } validates :short_title, format: { with: /\A[a-zA-Z0-9_-]*\z/ }
@ -69,6 +82,8 @@ class Conference < ActiveRecord::Base
after_create :create_free_ticket after_create :create_free_ticket
after_update :delete_event_schedules after_update :delete_event_schedules
enum ticket_layout: [:portrait, :landscape]
## ##
# Checks if the user is registered to the conference # Checks if the user is registered to the conference
# #
@ -128,7 +143,7 @@ class Conference < ActiveRecord::Base
result = [] result = []
if program && program.cfp && program.events if program && program.cfp && program.events
submissions = program.events.group(:week).count submissions = program.events.select(:week).group(:week).order(:week).count
start_week = program.cfp.start_week start_week = program.cfp.start_week
weeks = program.cfp.weeks weeks = program.cfp.weeks
result = calculate_items_per_week(start_week, weeks, submissions) result = calculate_items_per_week(start_week, weeks, submissions)
@ -176,7 +191,7 @@ class Conference < ActiveRecord::Base
registration_period.start_date && registration_period.start_date &&
registration_period.end_date registration_period.end_date
reg = registrations.group(:week).count reg = registrations.group(:week).order(:week).count
start_week = get_registration_start_week start_week = get_registration_start_week
weeks = registration_weeks weeks = registration_weeks
result = calculate_items_per_week(start_week, weeks, reg) result = calculate_items_per_week(start_week, weeks, reg)
@ -184,6 +199,59 @@ class Conference < ActiveRecord::Base
result result
end end
##
# Returns an array with the summarized ticket sales per week.
#
# ====Returns
# * +Array+ -> e.g. [0, 3, 3, 5] -> first week 0, second week 3 tickets sold
def get_tickets_sold_per_week
result = []
if tickets && ticket_purchases && registration_period
tickets_sold = ticket_purchases.paid.group(:week).sum(:quantity)
start_week = get_registration_start_week
weeks = registration_weeks
result = calculate_items_per_week(start_week, weeks, tickets_sold)
end
result
end
##
# Returns an hash with ticket sales by ticket title
# per week.
#
# ====Returns
# * +Array+ -> e.g. 'Free Access' => [0, 3, 3, 5] -> first week 0 tickets sold, second week 3 tickets sold.
def get_tickets_data
result = {}
if tickets && ticket_purchases && registration_period
tickets_per_ticket_id_and_week = ticket_purchases.paid.group(:ticket_id, :week).sum(:quantity)
start_week = get_registration_start_week
weeks = registration_weeks
tickets_by_id_per_week = {}
tickets.each do |ticket|
tickets_by_id_per_week[ticket.id] = {}
(start_week...(start_week + weeks)).each do |week|
tickets_by_id_per_week[ticket.id][week] = 0
end
end
tickets_per_ticket_id_and_week.each do |ticket_week, value|
tickets_by_id_per_week[ticket_week[0]][ticket_week[1]] = value
end
tickets_by_id_per_week.each do |ticket, values|
result[Ticket.find(ticket).title] = pad_array_left_not_kumulative(start_week, values)
end
result['Weeks'] = weeks > 0 ? (1..weeks).to_a : 0
end
result
end
## ##
# Calculates how many weeks the registration is. # Calculates how many weeks the registration is.
# #
@ -281,8 +349,8 @@ class Conference < ActiveRecord::Base
# ====Returns # ====Returns
# * +hash+ -> user: submissions # * +hash+ -> user: submissions
def self.get_top_submitter(limit = 5) def self.get_top_submitter(limit = 5)
submitter = EventUser.where('event_role = ?', 'submitter').limit(limit).group(:user_id) submitter = EventUser.select(:user_id).where('event_role = ?', 'submitter').limit(limit).group(:user_id)
counter = submitter.order('count_all desc').count counter = submitter.order('count_all desc').count(:all)
calculate_user_submission_hash(submitter, counter) calculate_user_submission_hash(submitter, counter)
end end
@ -292,10 +360,10 @@ class Conference < ActiveRecord::Base
# ====Returns # ====Returns
# * +hash+ -> user: submissions # * +hash+ -> user: submissions
def get_top_submitter(limit = 5) def get_top_submitter(limit = 5)
submitter = EventUser.joins(:event) submitter = EventUser.joins(:event).select(:user_id)
.where('event_role = ? and program_id = ?', 'submitter', Conference.find(id).program.id) .where('event_role = ? and program_id = ?', 'submitter', Conference.find(id).program.id)
.limit(limit).group(:user_id) .limit(limit).group(:user_id)
counter = submitter.order('count_all desc').count counter = submitter.order('count_all desc').count(:all)
Conference.calculate_user_submission_hash(submitter, counter) Conference.calculate_user_submission_hash(submitter, counter)
end end
@ -396,6 +464,46 @@ class Conference < ActiveRecord::Base
calculate_user_distribution_hash(active_user, unconfirmed_user, dead_user) calculate_user_distribution_hash(active_user, unconfirmed_user, dead_user)
end end
##
# Returns a hash with per ticket sales => { "Title" => { value: number of tickets sold,
# color: generated from the title using a hash function }, ...}
#
# ====Returns
# * +hash+ -> hash
def tickets_sold_distribution
result = {}
if tickets && ticket_purchases
tickets.each do |ticket|
result[ticket.title] = {
'value' => ApplicationController.helpers.humanized_money(ticket.tickets_sold).delete(',').to_i,
'color' => "\##{Digest::MD5.hexdigest(ticket.title)[0..5]}"
}
end
end
result
end
##
# Returns a hash with per ticket turnover => { "Title" => { value: ticket turnover,
# color: generated from the title using a hash function }, ...}
#
# ====Returns
# * +hash+ -> hash
def tickets_turnover_distribution
result = {}
if tickets && ticket_purchases
tickets.each do |ticket|
result[ticket.title] = {
'value' => ApplicationController.helpers.humanized_money(ticket.tickets_turnover_total(ticket.id)).delete(',').to_i,
'color' => "\##{Digest::MD5.hexdigest(ticket.title)[0..5]}"
}
end
end
result
end
## ##
# Calculates the overall program minutes # Calculates the overall program minutes
# #
@ -460,10 +568,10 @@ class Conference < ActiveRecord::Base
# ====Returns # ====Returns
# * +hash+ -> track => {color, value} # * +hash+ -> track => {color, value}
def tracks_distribution(state = nil) def tracks_distribution(state = nil)
if state tracks_grouped = if state
tracks_grouped = program.events.select(:track_id).where('state = ?', state).group(:track_id) program.events.select(:track_id).where('state = ?', state).group(:track_id)
else else
tracks_grouped = program.events.select(:track_id).group(:track_id) program.events.select(:track_id).group(:track_id)
end end
tracks_counted = tracks_grouped.count tracks_counted = tracks_grouped.count
@ -547,7 +655,7 @@ class Conference < ActiveRecord::Base
def self.write_event_distribution_to_db def self.write_event_distribution_to_db
week = DateTime.now.end_of_week week = DateTime.now.end_of_week
Conference.where('end_date > ?', Date.today).each do |conference| Conference.where('end_date > ?', Date.today).find_each do |conference|
result = {} result = {}
Event.state_machine.states.each do |state| Event.state_machine.states.each do |state|
count = conference.program.events.where('state = ?', state.name).count count = conference.program.events.where('state = ?', state.name).count
@ -634,6 +742,28 @@ class Conference < ActiveRecord::Base
(start_hour..(end_hour - 1)).cover?(current_hour) ? current_hour - start_hour : 0 (start_hour..(end_hour - 1)).cover?(current_hour) ? current_hour - start_hour : 0
end end
##
#
# ====Returns
# * +True+ -> if accepted booths are equal to the booth limit
# * +False+ -> Accepted booths have not reached the booth limit
def maximum_accepted_booths?
booth_limit > 0 && booths.accepted.count + booths.confirmed.count >= booth_limit
end
##
# Return the current conference object to be used in RevisionCount
#
# ====Returns
# * +ActiveRecord+
def conference
self
end
def to_param
short_title
end
private private
# Returns a different html colour for every i and consecutive colors are # Returns a different html colour for every i and consecutive colors are
@ -803,7 +933,7 @@ class Conference < ActiveRecord::Base
hash[key] = 0 hash[key] = 0
end end
end end
Hash[ hash.sort ] Hash[hash.sort]
end end
## ##
@ -889,10 +1019,10 @@ class Conference < ActiveRecord::Base
# ====Returns # ====Returns
# * +hash+ -> object_type => {color, value} # * +hash+ -> object_type => {color, value}
def calculate_event_distribution(group_by_id, association_symbol, state = nil) def calculate_event_distribution(group_by_id, association_symbol, state = nil)
if state grouped = if state
grouped = program.events.select(group_by_id).where('state = ?', 'confirmed').group(group_by_id) program.events.select(group_by_id).where('state = ?', 'confirmed').group(group_by_id)
else else
grouped = program.events.select(group_by_id).group(group_by_id) program.events.select(group_by_id).group(group_by_id)
end end
counted = grouped.count counted = grouped.count
@ -1004,7 +1134,8 @@ class Conference < ActiveRecord::Base
def self.calculate_user_submission_hash(submitters, counter) def self.calculate_user_submission_hash(submitters, counter)
result = ActiveSupport::OrderedHash.new result = ActiveSupport::OrderedHash.new
counter.each do |key, value| counter.each do |key, value|
submitter = submitters.where(user_id: key).first # make PG happy by including the user_id in ORDER
submitter = submitters.where(user_id: key).order(:user_id).first
if submitter if submitter
result[submitter.user] = value result[submitter.user] = value
end end

View file

@ -3,7 +3,7 @@ class EmailSettings < ActiveRecord::Base
has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id } has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id }
def get_values(conference, user, event = nil) def get_values(conference, user, event = nil, booth = nil)
h = { h = {
'email' => user.email, 'email' => user.email,
'name' => user.name, 'name' => user.name,
@ -45,6 +45,10 @@ class EmailSettings < ActiveRecord::Base
h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url( h['proposalslink'] = Rails.application.routes.url_helpers.conference_program_proposals_url(
conference.short_title, host: (ENV['OSEM_HOSTNAME'] || 'localhost:3000')) conference.short_title, host: (ENV['OSEM_HOSTNAME'] || 'localhost:3000'))
end end
if booth
h['booth_title'] = booth.title
end
h h
end end
@ -58,6 +62,11 @@ class EmailSettings < ActiveRecord::Base
parse_template(conf_update_template, values) parse_template(conf_update_template, values)
end end
def generate_booth_mail(booth, booth_template)
values = get_values(booth.conference, booth.submitter, nil, booth)
parse_template(booth_template, values)
end
private private
def parse_template(text, values) def parse_template(text, values)

View file

@ -1,5 +1,6 @@
class Event < ActiveRecord::Base class Event < ActiveRecord::Base
include ActiveRecord::Transitions include ActiveRecord::Transitions
include RevisionCount
has_paper_trail on: [:create, :update], ignore: [:updated_at, :guid, :week], meta: { conference_id: :conference_id } has_paper_trail on: [:create, :update], ignore: [:updated_at, :guid, :week], meta: { conference_id: :conference_id }
acts_as_commentable acts_as_commentable
@ -44,6 +45,7 @@ class Event < ActiveRecord::Base
validates :max_attendees, numericality: { only_integer: true, greater_than_or_equal_to: 1, allow_nil: true } validates :max_attendees, numericality: { only_integer: true, greater_than_or_equal_to: 1, allow_nil: true }
validate :max_attendees_no_more_than_room_size validate :max_attendees_no_more_than_room_size
validate :valid_track
scope :confirmed, -> { where(state: 'confirmed') } scope :confirmed, -> { where(state: 'confirmed') }
scope :canceled, -> { where(state: 'canceled') } scope :canceled, -> { where(state: 'canceled') }
@ -83,7 +85,7 @@ class Event < ActiveRecord::Base
# ====Returns # ====Returns
# * +true+ or +false+ # * +true+ or +false+
def scheduled? def scheduled?
event_schedules.find_by(schedule_id: program.selected_schedule_id).present? event_schedules.find_by(schedule_id: selected_schedule_id).present?
end end
def registration_possible? def registration_possible?
@ -115,7 +117,7 @@ class Event < ActiveRecord::Base
def average_rating def average_rating
@total_rating = 0 @total_rating = 0
votes.each do |vote| votes.each do |vote|
@total_rating = @total_rating + vote.rating @total_rating += vote.rating
end end
@total = votes.size @total = votes.size
@total_rating > 0 ? number_with_precision(@total_rating / @total.to_f, precision: 2, strip_insignificant_zeros: true) : 0 @total_rating > 0 ? number_with_precision(@total_rating / @total.to_f, precision: 2, strip_insignificant_zeros: true) : 0
@ -241,14 +243,22 @@ class Event < ActiveRecord::Base
def room def room
# We use try(:selected_schedule_id) because this function is used for # We use try(:selected_schedule_id) because this function is used for
# validations so program could not be present there # validations so program could not be present there
if track.try(:self_organized?)
track.room
else
event_schedules.find_by(schedule_id: program.try(:selected_schedule_id)).try(:room) event_schedules.find_by(schedule_id: program.try(:selected_schedule_id)).try(:room)
end end
end
## ##
# Returns the start time at which this event is scheduled # Returns the start time at which this event is scheduled
# #
def time def time
event_schedules.find_by(schedule_id: program.selected_schedule_id).try(:start_time) event_schedules.find_by(schedule_id: selected_schedule_id).try(:start_time)
end
def conference
program.conference
end end
private private
@ -298,4 +308,25 @@ class Event < ActiveRecord::Base
def conference_id def conference_id
program.conference_id program.conference_id
end end
##
# Allow only confirmed tracks that belong to the same program as the event
#
def valid_track
return unless track && track.program && program
errors.add(:track, 'is invalid') unless track.confirmed? && track.program == program
end
##
# Return the id of the selected schedule
#
# ====Returns
# * +Integer+ -> selected_schedule_id of self-organized track or program
def selected_schedule_id
if track.try(:self_organized?)
track.selected_schedule_id
else
program.selected_schedule_id
end
end
end end

View file

@ -12,6 +12,9 @@ class EventSchedule < ActiveRecord::Base
validates :event, uniqueness: { scope: :schedule } validates :event, uniqueness: { scope: :schedule }
validate :start_after_end_hour validate :start_after_end_hour
validate :start_before_start_hour validate :start_before_start_hour
validate :same_room_as_track
validate :during_track
validate :valid_schedule
scope :confirmed, -> { joins(:event).where('state = ?', 'confirmed') } scope :confirmed, -> { joins(:event).where('state = ?', 'confirmed') }
scope :canceled, -> { joins(:event).where('state = ?', 'canceled') } scope :canceled, -> { joins(:event).where('state = ?', 'canceled') }
@ -52,4 +55,35 @@ class EventSchedule < ActiveRecord::Base
def conference_id def conference_id
schedule.program.conference_id schedule.program.conference_id
end end
##
# Validates that the event is scheduled in the same room as its track
#
def same_room_as_track
return unless event.try(:track).try(:room)
errors.add(:room, "must be the same as the track's room (#{event.track.room.name})") unless event.track.room == room
end
##
# Validates that the event is scheduled within its track's time slot
#
def during_track
return unless event.try(:track) && start_time
if event.track.try(:start_date) && event.track.start_date > start_time
errors.add(:start_time, "can't be before the track's start date (#{event.track.start_date})")
end
if event.track.try(:end_date) && event.track.end_date + 1.day < end_time
errors.add(:end_time, "can't be after the track's end date (#{event.track.end_date})")
end
end
##
# Validates that the event is scheduled in its self-organized tracks's schedules
#
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)
end
end end

View file

@ -1,6 +1,6 @@
class EventUser < ActiveRecord::Base class EventUser < ActiveRecord::Base
# TODO Do we need these roles? # TODO: Do we need these roles?
ROLES = [['Speaker', 'speaker'], ['Submitter', 'submitter'], ['Moderator', 'moderator']] ROLES = [%w[Speaker speaker], %w[Submitter submitter], %w[Moderator moderator]]
belongs_to :event belongs_to :event
belongs_to :user belongs_to :user

View file

@ -0,0 +1,19 @@
class Organization < ActiveRecord::Base
resourcify :roles, dependent: :delete_all
has_paper_trail
has_many :conferences, dependent: :destroy
after_create :create_roles
validates :name, presence: true
mount_uploader :picture, PictureUploader, mount_on: :picture
private
def create_roles
roles.where(name: 'organization_admin').first_or_create(description: 'For the administrators of an organization and its conferences')
end
end

View file

@ -0,0 +1,22 @@
class PhysicalTicket < ActiveRecord::Base
belongs_to :ticket_purchase
has_one :ticket, through: :ticket_purchase
has_one :conference, through: :ticket_purchase
has_one :user, through: :ticket_purchase
has_many :ticket_scannings
before_create :set_token
private
def set_token
self.token = generate_token
end
def generate_token
loop do
token = SecureRandom.hex(10)
break token unless PhysicalTicket.exists?(token: token)
end
end
end

View file

@ -5,7 +5,7 @@ class Program < ActiveRecord::Base
belongs_to :conference belongs_to :conference
has_one :cfp, dependent: :destroy has_many :cfps, dependent: :destroy
has_many :event_types, dependent: :destroy has_many :event_types, dependent: :destroy
has_many :tracks, dependent: :destroy has_many :tracks, dependent: :destroy
has_many :difficulty_levels, dependent: :destroy has_many :difficulty_levels, dependent: :destroy
@ -71,7 +71,11 @@ class Program < ActiveRecord::Base
# Returns all event_schedules for the selected schedule ordered by start_time # Returns all event_schedules for the selected schedule ordered by start_time
def selected_event_schedules def selected_event_schedules
selected_schedule.event_schedules.order(start_time: :asc) if selected_schedule event_schedules = selected_schedule.event_schedules.order(start_time: :asc) if selected_schedule
tracks.self_organized.confirmed.order(start_date: :asc).each do |track|
event_schedules += track.selected_schedule.event_schedules.order(start_time: :asc) if track.selected_schedule
end
event_schedules.sort_by(&:start_time) if event_schedules
end end
## ##
@ -163,6 +167,23 @@ class Program < ActiveRecord::Base
EventSchedule.where(schedule: selected_schedule).where(start_time: parsed_date..(parsed_date + 1)).any? EventSchedule.where(schedule: selected_schedule).where(start_time: parsed_date..(parsed_date + 1)).any?
end end
##
# Provides backwards compatibility for when the program had one cfp
#
# ====Returns
# * +ActiveRecord+ -> The program's cfp with cfp_type == 'events'
def cfp
return nil if cfps.for_events.blank?
cfps.for_events
end
##
# ====Returns
# * +Array+ -> The types of cfps for which a cfp doesn't exist yet
def remaining_cfp_types
Cfp::TYPES - cfps.pluck(:cfp_type)
end
private private
## ##

View file

@ -2,7 +2,6 @@ class Registration < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :conference belongs_to :conference
has_and_belongs_to_many :events
has_and_belongs_to_many :qanswers has_and_belongs_to_many :qanswers
has_and_belongs_to_many :vchoices has_and_belongs_to_many :vchoices

View file

@ -1,26 +0,0 @@
#
# suseconferenceclient relies on a 'revision' attribute for caching and
# doing some calculations.
#
# It should be incremented after any change in the conference or in any
# associated models
#
# This observer updates the revision column in a non-intrusive way,
# preventing validations, callbacks or exceptions to be triggered
#
# Relying on paper_trail could also be an option, but a 'revision' column
# in table 'conferences' looks like a more simple and straightforward solution
#
class RevisionObserver < ActiveRecord::Observer
observe :conference, :event, :room, :track
def after_save(model)
begin
conference = model.kind_of?(Conference) ? model : model.conference
conference.reload.increment(:revision)
conference.update_column(:revision, conference.revision)
rescue
nil
end
end
end

View file

@ -1,6 +1,8 @@
class Room < ActiveRecord::Base class Room < ActiveRecord::Base
include RevisionCount
belongs_to :venue belongs_to :venue
has_many :event_schedules, dependent: :destroy has_many :event_schedules, dependent: :destroy
has_many :tracks
has_paper_trail ignore: [:guid], meta: { conference_id: :conference_id } has_paper_trail ignore: [:guid], meta: { conference_id: :conference_id }
@ -10,6 +12,10 @@ class Room < ActiveRecord::Base
validates :size, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true validates :size, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
def conference
venue.conference
end
private private
def generate_guid def generate_guid

View file

@ -1,5 +1,6 @@
class Schedule < ActiveRecord::Base class Schedule < ActiveRecord::Base
belongs_to :program belongs_to :program
belongs_to :track
has_many :event_schedules, dependent: :destroy has_many :event_schedules, dependent: :destroy
has_many :events, through: :event_schedules has_many :events, through: :event_schedules

View file

@ -1,5 +1,4 @@
class Subscription < ActiveRecord::Base class Subscription < ActiveRecord::Base
validates :user_id, uniqueness: { scope: [:conference_id] }
belongs_to :conference belongs_to :conference
belongs_to :user belongs_to :user

View file

@ -55,12 +55,18 @@ class Ticket < ActiveRecord::Base
result ? result : Money.new(0, 'USD') result ? result : Money.new(0, 'USD')
end end
def tickets_sold def self.total_price_user(conference, user, paid: false)
ticket_purchases.sum(:quantity) tickets = TicketPurchase.where(conference: conference, user: user, paid: paid)
tickets.inject(0){ |sum, ticket| sum + (ticket.amount_paid * ticket.quantity) }
end end
def tickets_turnover def tickets_turnover_total(id)
tickets_sold * price tickets = TicketPurchase.where(ticket_id: id)
tickets.inject(0){ |sum, ticket| sum + (ticket.amount_paid * ticket.quantity) }
end
def tickets_sold
ticket_purchases.paid.sum(:quantity)
end end
private private

View file

@ -2,9 +2,11 @@ class TicketPurchase < ActiveRecord::Base
belongs_to :ticket belongs_to :ticket
belongs_to :user belongs_to :user
belongs_to :conference belongs_to :conference
belongs_to :payment
validates :ticket_id, :user_id, :conference_id, :quantity, presence: true validates :ticket_id, :user_id, :conference_id, :quantity, presence: true
validate :one_registration_ticket_per_user
validate :registration_ticket_already_purchased, on: :create
validates :quantity, numericality: { greater_than: 0 } validates :quantity, numericality: { greater_than: 0 }
delegate :title, to: :ticket delegate :title, to: :ticket
@ -13,28 +15,35 @@ class TicketPurchase < ActiveRecord::Base
delegate :price_cents, to: :ticket delegate :price_cents, to: :ticket
delegate :price_currency, to: :ticket delegate :price_currency, to: :ticket
has_many :physical_tickets
scope :paid, -> { where(paid: true) } scope :paid, -> { where(paid: true) }
scope :unpaid, -> { where(paid: false) } scope :unpaid, -> { where(paid: false) }
scope :by_conference, ->(conference) { where(conference_id: conference.id) } scope :by_conference, ->(conference) { where(conference_id: conference.id) }
scope :by_user, ->(user) { where(user_id: user.id) } scope :by_user, ->(user) { where(user_id: user.id) }
after_create :set_week
def self.purchase(conference, user, purchases) def self.purchase(conference, user, purchases)
errors = [] errors = []
if count_purchased_registration_tickets(conference, purchases) > 1
errors.push('You cannot buy more than one registration tickets.')
else
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
conference.tickets.each do |ticket| conference.tickets.each do |ticket|
quantity = purchases[ticket.id.to_s].to_i quantity = purchases[ticket.id.to_s].to_i
# if the user bought the ticket and is still unpaid, just update the quantity # if the user bought the ticket and is still unpaid, just update the quantity
if ticket.bought?(user) && ticket.unpaid?(user) purchase = if ticket.bought?(user) && ticket.unpaid?(user)
purchase = update_quantity(conference, quantity, ticket, user) update_quantity(conference, quantity, ticket, user)
else else
purchase = purchase_ticket(conference, quantity, ticket, user) purchase_ticket(conference, quantity, ticket, user)
end end
if purchase && !purchase.save if purchase && !purchase.save
errors.push(purchase.errors.full_messages) errors.push(purchase.errors.full_messages)
end end
end end
end end
end
errors.join('. ') errors.join('. ')
end end
@ -44,7 +53,8 @@ class TicketPurchase < ActiveRecord::Base
conference_id: conference.id, conference_id: conference.id,
user_id: user.id, user_id: user.id,
quantity: quantity, quantity: quantity,
paid: ticket.price_cents.zero?) amount_paid: ticket.price)
purchase.pay(nil) if ticket.price_cents.zero?
end end
purchase purchase
end end
@ -58,4 +68,37 @@ class TicketPurchase < ActiveRecord::Base
purchase.quantity = quantity if quantity > 0 purchase.quantity = quantity if quantity > 0
purchase purchase
end end
def pay(payment)
update_attributes(paid: true, payment: payment)
PhysicalTicket.transaction do
quantity.times { physical_tickets.create }
end
Mailbot.ticket_confirmation_mail(self).deliver_later
end
def one_registration_ticket_per_user
if ticket.try(:registration_ticket?) && quantity != 1
errors.add(:quantity, 'cannot be greater than one for registration tickets.')
end
end
def registration_ticket_already_purchased
if ticket.try(:registration_ticket?) && user.tickets.for_registration(conference).present?
errors.add(:quantity, 'cannot be greater than one for registration tickets.')
end
end
end
private
def set_week
self.week = created_at.strftime('%W')
save!
end
def count_purchased_registration_tickets(conference, purchases)
conference.tickets.for_registration.inject(0) do |sum, registration_ticket|
sum + purchases[registration_ticket.id.to_s].to_i
end
end end

View file

@ -0,0 +1,13 @@
class TicketScanning < ActiveRecord::Base
belongs_to :physical_ticket
before_create :mark_user_present
private
def mark_user_present
if physical_ticket.ticket.registration_ticket?
physical_ticket.user.mark_attendance_for_conference(physical_ticket.conference)
end
end
end

View file

@ -1,15 +1,176 @@
class Track < ActiveRecord::Base class Track < ActiveRecord::Base
belongs_to :program include ActiveRecord::Transitions
has_many :events, dependent: :nullify include RevisionCount
has_paper_trail only: [:name, :description, :color], meta: { conference_id: :conference_id } resourcify :roles, dependent: :delete_all
belongs_to :program
belongs_to :submitter, class_name: 'User'
belongs_to :room
belongs_to :selected_schedule, class_name: 'Schedule'
has_many :events, dependent: :nullify
has_many :schedules
has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id }
before_create :generate_guid before_create :generate_guid
validates :name, presence: true validates :name, presence: true
validates :color, format: /\A#[0-9A-F]{6}\z/ validates :color, format: /\A#[0-9A-F]{6}\z/
validates :short_name,
presence: true,
format: /\A[a-zA-Z0-9_-]*\z/,
uniqueness: {
scope: :program
}
validates :state,
presence: true,
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?
validates :room, presence: true, if: :self_organized_and_accepted_or_confirmed?
validates :relevance, presence: true, if: :self_organized?
validates :description, presence: true, if: :self_organized?
validate :dates_within_conference_dates
validate :start_date_before_end_date
validate :valid_room
validate :overlapping
before_validation :capitalize_color before_validation :capitalize_color
scope :accepted, -> { where(state: 'accepted') }
scope :confirmed, -> { where(state: 'confirmed') }
scope :cfp_active, -> { where(cfp_active: true) }
scope :self_organized, -> { where.not(submitter: nil) }
state_machine initial: :pending do
state :new
state :to_accept
state :accepted
state :confirmed
state :to_reject
state :rejected
state :canceled
state :withdrawn
event :restart do
transitions to: :new, from: [:rejected, :withdrawn, :canceled]
end
event :to_accept do
transitions to: :to_accept, from: [:new, :to_reject]
end
event :accept do
transitions to: :accepted, from: [: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]
end
event :reject do
transitions to: :rejected, from: [:new, :to_reject]
end
event :cancel do
transitions to: :canceled, from: [: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
end
end
def conference
program.conference
end
##
# Checks if the track is self-organized
# ====Returns
# * +true+ -> If the track has a submitter
# * +false+ -> if the track doesn't have a submitter
def self_organized?
return true if submitter
false
end
def to_param
short_name
end
def transition_possible?(transition)
self.class.state_machine.events_for(current_state).include?(transition)
end
# Gives the role of the track_organizer to the submitter
def assign_role_to_submitter
submitter.add_role 'track_organizer', self
end
##
# Revokes the track organizer role, destroys the track's schedule, removes the
# track from events that have it set and reverts their state to new
#
def revoke_role_and_cleanup
role = Role.find_by(name: 'track_organizer', resource: self)
if role
role.users.each do |user|
user.remove_role 'track_organizer', self
end
end
self.selected_schedule_id = nil
save!
schedules.each(&:destroy!)
events.each do |event|
event.track = nil
event.state = 'new'
event.save!
end
end
##
# Checks if the track is accepted
# ====Returns
# * +true+ -> If the track's state is 'accepted'
# * +false+ -> If the track's state isn't 'accepted'
def accepted?
state == 'accepted'
end
##
# Checks if the track is confirmed
# ====Returns
# * +true+ -> If the track's state is 'confirmed'
# * +false+ -> If the track's state isn't 'confirmed'
def confirmed?
state == 'confirmed'
end
##
# Checks if a self-organized track is accepted or confirmed
# ====Returns
# * +true+ -> If the track's state is 'accepted' or 'confirmed'
# * +false+ -> If the track's state is neither 'accepted' nor 'confirmed'
def self_organized_and_accepted_or_confirmed?
self_organized? && (accepted? || confirmed?)
end
def update_state(transition)
error = ''
begin
send(transition)
rescue Transitions::InvalidTransition => e
error += "State update failed. #{e.message} "
end
error += errors.full_messages.join(', ') unless save
error
end
private private
def generate_guid def generate_guid
@ -27,4 +188,51 @@ class Track < ActiveRecord::Base
def conference_id def conference_id
program.conference_id program.conference_id
end end
##
# Creates the role of the track organizer
def create_organizer_role
Role.where(name: 'track_organizer', resource: self).first_or_create(description: 'For the organizers of the Track')
end
##
# 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)
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)
end
##
# Verify that the start date isn't after the end date
#
def start_date_before_end_date
return unless start_date && end_date
errors.add(:start_date, 'can\'t be after the end date') if start_date > end_date
end
##
# Verify that the room is a room of the conference
#
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
end
##
# Check that there is no other track in the same room with overlapping dates
#
def overlapping
return unless start_date && end_date && room && program.try(:tracks)
(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
end
end
end end

View file

@ -6,6 +6,11 @@ end
class User < ActiveRecord::Base class User < ActiveRecord::Base
rolify rolify
has_many :physical_tickets, through: :ticket_purchases do
def by_conference(conference)
where('ticket_purchases.conference_id = ?', conference)
end
end
has_many :users_roles has_many :users_roles
has_many :roles, through: :users_roles, dependent: :destroy has_many :roles, through: :users_roles, dependent: :destroy
@ -25,12 +30,12 @@ class User < ActiveRecord::Base
# :lockable, :timeoutable and :omniauthable # :lockable, :timeoutable and :omniauthable
devise_modules = [] devise_modules = []
if ENV['OSEM_ICHAIN_ENABLED'] == 'true' devise_modules += if ENV['OSEM_ICHAIN_ENABLED'] == 'true'
devise_modules += [ :ichain_authenticatable, :ichain_registerable, :omniauthable, omniauth_providers: [] ] [:ichain_authenticatable, :ichain_registerable, :omniauthable, omniauth_providers: []]
else else
devise_modules += [:database_authenticatable, :registerable, [:database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable, :recoverable, :rememberable, :trackable, :validatable, :confirmable,
:omniauthable, omniauth_providers: [:suse, :google, :facebook, :github] ] :omniauthable, omniauth_providers: [:suse, :google, :facebook, :github]]
end end
devise(*devise_modules) devise(*devise_modules)
@ -42,14 +47,26 @@ class User < ActiveRecord::Base
has_many :event_users, dependent: :destroy has_many :event_users, dependent: :destroy
has_many :events, -> { uniq }, through: :event_users has_many :events, -> { uniq }, through: :event_users
has_many :presented_events, -> { joins(:event_users).where(event_users: {event_role: 'speaker'}).uniq }, through: :event_users, source: :event has_many :presented_events, -> { joins(:event_users).where(event_users: {event_role: 'speaker'}).uniq }, through: :event_users, source: :event
has_many :registrations, dependent: :destroy has_many :registrations, dependent: :destroy do
def for_conference conference
where(conference: conference).first
end
end
has_many :events_registrations, through: :registrations has_many :events_registrations, through: :registrations
has_many :ticket_purchases, dependent: :destroy has_many :ticket_purchases, dependent: :destroy
has_many :payments, dependent: :destroy has_many :payments, dependent: :destroy
has_many :tickets, through: :ticket_purchases, source: :ticket has_many :tickets, through: :ticket_purchases, source: :ticket do
def for_registration conference
where(conference: conference, registration_ticket: true).first
end
end
has_many :votes, dependent: :destroy has_many :votes, dependent: :destroy
has_many :voted_events, through: :votes, source: :events has_many :voted_events, through: :votes, source: :events
has_many :subscriptions, dependent: :destroy 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
accepts_nested_attributes_for :roles accepts_nested_attributes_for :roles
scope :admin, -> { where(is_admin: true) } scope :admin, -> { where(is_admin: true) }
@ -80,6 +97,12 @@ class User < ActiveRecord::Base
event_registration.attended event_registration.attended
end end
def mark_attendance_for_conference conference
registration = registrations.for_conference(conference)
registration.attended = true
registration.save
end
def name def name
self[:name].blank? ? username : self[:name] self[:name].blank? ? username : self[:name]
end end
@ -156,7 +179,11 @@ class User < ActiveRecord::Base
def get_roles def get_roles
result = {} result = {}
roles.each do |role| roles.each do |role|
resource = Conference.find(role.resource_id).short_title resource = if role.resource_type == 'Conference'
Conference.find(role.resource_id).short_title
elsif role.resource_type == 'Track'
Track.find(role.resource_id).name
end
if result[role.name].nil? if result[role.name].nil?
result[role.name] = [resource] result[role.name] = [resource]
else else

92
app/pdfs/ticket_pdf.rb Normal file
View file

@ -0,0 +1,92 @@
class TicketPdf < Prawn::Document
def initialize(conference, user, physical_ticket, ticket_layout, file_name)
super(page_layout: ticket_layout, page_size: 'A4', filename: file_name)
@user = user
@physical_ticket = physical_ticket
@conference = conference
@left = bounds.left
@right = bounds.right
@mid_vertical = (bounds.top - bounds.bottom) / 2
@mid_horizontal = (bounds.right - bounds.left) / 2
@x = 0
draw_first_square
draw_second_square
draw_third_square
draw_fourth_square
end
def draw_first_square
move_down @mid_vertical
dash(2, space: 1)
stroke_horizontal_rule
stroke_vertical_line bounds.top, bounds.bottom, at: @mid_horizontal
move_up @mid_vertical
draw_text 'TICKET HOLDER', at: [@x, cursor - 30], size: 17
dash(2, space: 0)
bounding_box [@x, cursor - 50], width: 230, height: 150 do
pad(15) do
text_box 'NAME', at: [@x + 10, cursor], size: 13
fill_color '808080'
text_box @user.name.to_s, at: [@x + 10, cursor - 20], size: 18
fill_color '000000'
text_box 'EMAIL', at: [@x + 10, cursor - 60], size: 13
fill_color '808080'
text_box @user.email.to_s, at: [@x + 10, cursor - 80], size: 18, overflow: :shrink_to_fit
fill_color '000000'
end
stroke_bounds
end
end
def draw_second_square
move_up 150
if @conference.picture?
if 7 * @conference.picture.image[:width] > 12 * @conference.picture.image[:height]
image "#{Rails.root}/public#{@conference.picture_url}", at: [@mid_horizontal + 30, cursor], width: 120
else
image "#{Rails.root}/public#{@conference.picture_url}", at: [@mid_horizontal + 30, cursor], height: 70
end
else
image "#{Rails.root}/public/img/osem-logo.png", at: [@mid_horizontal + 30, cursor], height: 70
end
move_down 70
draw_text @conference.title.to_s, at: [@mid_horizontal + 30, cursor - 30], size: 12
draw_text @conference.organization.name.to_s, at: [@mid_horizontal + 30, cursor - 50], size: 12
if @conference.venue
draw_text @conference.venue.name, at: [@mid_horizontal + 30, cursor - 70]
draw_text @conference.venue.street, at: [@mid_horizontal + 30, cursor - 90]
draw_text @conference.venue.city, at: [@mid_horizontal + 30, cursor - 110]
end
move_up 130
move_down @mid_vertical
end
def draw_third_square
draw_text 'EVENT', at: [@x, cursor - 40], size: 15
fill_color '808080'
draw_text @conference.title.to_s, at: [@x, cursor - 60], size: 12
draw_text @conference.start_date.strftime('%B %d, %Y').to_s, at: [@x, cursor - 80], size: 12
move_down 80
fill_color '000000'
draw_text 'TICKET', at: [@x, cursor - 30], size: 15
fill_color '808080'
draw_text @physical_ticket.ticket.title.to_s, at: [@x, cursor - 50], size: 12
move_down 50
fill_color '000000'
draw_text 'TICKET REF.', at: [@x, cursor - 30], size: 15
fill_color '808080'
draw_text @physical_ticket.ticket_purchase.id.to_s, at: [@x, cursor - 50], size: 12
move_down 50
fill_color '000000'
draw_text 'Powered By OSEM', at: [(@mid_horizontal - @left - 100) / 2, cursor - 100], size: 11
move_up 180
end
def draw_fourth_square
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

View file

@ -52,6 +52,10 @@ class PictureUploader < CarrierWave::Uploader::Base
"system/#{object_class_name}/#{mounted_as}/#{model.id}" "system/#{object_class_name}/#{mounted_as}/#{model.id}"
end end
def image
@image ||= MiniMagick::Image.open(file.file)
end
# Create different versions of your uploaded files: # Create different versions of your uploaded files:
version :large do version :large do
process resize_to_fit: [300, 300] process resize_to_fit: [300, 300]

View file

@ -0,0 +1,49 @@
- if booth.transition_possible? :accept
- if can? :accept, booth
- if @conference.booth_limit > 0
- confirm_message = ' You are able to accept '+ pluralize(@conference.booth_limit - (@conference.booths.accepted.count + @conference.booths.confirmed.count), 'more booth') + " (booth limit set to #{@conference.booth_limit}). Are you sure you want to accept this one?"
- if @conference.email_settings.send_on_booths_acceptance
- link = 'Accept with email'
- confirm_message = "By accepting this booth, an email will be sent informing the submitter for the acceptance. You may change the state to \'To accept\' until you are completely sure." + confirm_message
- else
- link = 'Accept booth'
%li= link_to link,
accept_admin_conference_booth_path(@conference.short_title, booth),
method: :patch ,id: "accept_booth_#{booth.id}",
data: (confirm_message ? { confirm: confirm_message } : nil)
- if booth.transition_possible? :reject
- if @conference.email_settings.send_on_booths_rejection
- link = 'Reject with email'
- else
- link = 'Reject'
%li= link_to link,
reject_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "reject_booth_#{booth.id}",
data: (@conference.email_settings.send_on_booths_rejection ? { confirm: 'By rejecting this booth, an email will be sent informing the submitter about the rejection. You may change the state to \'To reject\' until you are completely sure.'} : nil)
- if booth.transition_possible? :to_reject
%li= link_to 'To reject booth',
to_reject_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "to_reject_booth_#{booth.id}"
- if booth.transition_possible? :restart
%li= link_to 'Start review',
restart_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "restart_booth_#{booth.id}"
- if booth.transition_possible? :to_accept
%li= link_to 'To accept booth',
to_accept_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "to_accept_booth_#{booth.id}"
- if booth.transition_possible? :cancel
%li= link_to 'Cancel booth',
cancel_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "cancel_booth_#{booth.id}"
- if booth.transition_possible? :confirm
%li= link_to 'Confirm booth',
confirm_admin_conference_booth_path(@conference.short_title, booth),
method: :patch, id: "confirm_booth_#{booth.id}"

View file

@ -0,0 +1,5 @@
%h1
Editing
= @booth.title
= render 'booths/form'

View file

@ -0,0 +1,78 @@
.row
.col-md-12
.page-header
%h1
Booths
= "(#{@booths.length})" if @booths.any?
%p.text-muted
All the booth requests
.row
.col-md-12
%h4
- if @conference.booth_limit == 0
%p
Set the
= link_to 'Booth limit', edit_admin_conference_path(@conference.short_title)
to make sure you are not accepting more booths than you can accommodate.
- elsif !@conference.maximum_accepted_booths?
%p
You cannot accept more than
%b
= pluralize(@conference.booth_limit, 'booth')
(
= pluralize(@conference.booths.accepted.count + @conference.booths.confirmed.count, 'accepted booth')
so far)
- else
%p
You have reached the maximum number of accepted booths.
(
= link_to "#{@conference.booth_limit} booths", edit_admin_conference_path(@conference.short_title)
)
- if @booths.any?
%table.table.table-striped.table-bordered.table-hover.datatable
%thead
%th
%b ID
%th
%b Logo
%th
%b Title
%th
%b Submitter
%th
%b Responsibles
%th
%b State
%th
%b Actions
- @booths.each do |booth|
%tr
%td
= booth.id
%td
- if booth.logo_link
= image_tag(booth.picture.thumb.url, width: '20%')
%td
= link_to booth.title, admin_conference_booth_path(@conference.short_title, booth)
%td
= link_to booth.submitter.name, admin_user_path(booth.submitter) if booth.submitter
%td
.responsibles
- booth.responsibles.each_with_index do |responsible, i|
= link_to responsible.name, admin_user_path(responsible)
= ", " unless i == booth.responsibles.length - 1
%td
.btn-group
%button{ type: 'button', class: 'btn btn-link dropdown-toggle', 'data-toggle' => 'dropdown' }
= booth.state.humanize
%span.caret
%ul.dropdown-menu{ role: 'menu' }
= render 'change_state_dropdown', booth: booth
%td
= link_to 'Edit', edit_admin_conference_booth_path(@conference.short_title, booth.id),
class: 'btn btn-primary'
.row
.col-md-12.text-right
- if can? :create, Booth
= link_to 'New Booth', new_admin_conference_booth_path(@conference.short_title), class: 'button btn btn-primary'

View file

@ -0,0 +1,3 @@
%h1 New booth
= render 'booths/form'

View file

@ -0,0 +1,58 @@
.row
.col-md-12
%h3
= image_tag(@booth.picture.thumb.url, size: '20%', alt: '')
= @booth.title
.btn-group.pull-right
= link_to 'Edit', edit_admin_conference_booth_path(@conference.short_title, @booth), class: 'btn btn-mini btn-primary'
.row
.col-md-12
%table.table
%tr
%td.col-md-2
%b Description
%td
= markdown(@booth.description)
%tr
%td.col-md-2
%b Reasoning
%td
= markdown(@booth.reasoning)
%tr
%td.col-md-2
%b Website
%td
- if @booth.website_url.present?
= link_to @booth.website_url, @booth.website_url
%tr
%td.col-md-2
%b Submitter
%td
= link_to @booth.submitter.name, admin_user_path(@booth.submitter)
%tr
%td.col-md-2
%b Submitter's relationship
%td
= @booth.submitter_relationship
%tr
%td.col-md-2
%b Responsibles
%td
- @booth.responsibles.each_with_index do |responsibles, i|
.responsibles
= link_to responsibles.name, admin_user_path(responsibles)
(
= responsibles.email
)
= " , " unless i == @booth.responsibles.length - 1
%tr
%td.col-md-2
%b Submitted on
%td
= @booth.created_at
%tr
%td.col-md-2
%b Last updated on
%td
= @booth.updated_at

View file

@ -0,0 +1,16 @@
%dt
Type:
%dd
= @cfp.cfp_type.capitalize
%dt
Start Date:
%dd
= @cfp.start_date.strftime('%A, %B %e. %Y')
%dt
End Date:
%dd
= @cfp.end_date.strftime('%A, %B %e. %Y')
%dt
Days Left:
%dd
= pluralize(@cfp.remaining_days, 'day')

View file

@ -0,0 +1,42 @@
%dt
Type:
%dd
= @cfp.cfp_type.capitalize
%dt
Start Date:
%dd#start_date
= @cfp.start_date.strftime('%A, %B %-d. %Y')
%dt
End Date:
%dd#end_date
= @cfp.end_date.strftime('%A, %B %-d. %Y')
%dt
Days Left:
%dd
= pluralize(@cfp.remaining_days, 'day')
%dt
Event types:
%dd
= event_types(@conference)
%dt
Tracks:
%dd
= tracks(@conference)
%dt
Public Schedule:
%dd#schedule_public
- if @program.schedule_public
Yes
- else
No
%dt
Schedule changeable?
%dd#schedule_changes
- if @program.schedule_fluid
Yes
- else
No
%dt
Rating Levels:
%dd#rating
= @program.rating

View file

@ -4,8 +4,9 @@
%h1 Call for Papers %h1 Call for Papers
.row .row
.col-md-8 .col-md-8
= semantic_form_for(@cfp, url: admin_conference_program_cfp_path(@conference.short_title), html: {multipart: true}) do |f| = semantic_form_for(@cfp, url: (@cfp.new_record? ? admin_conference_program_cfps_path : admin_conference_program_cfp_path(@conference.short_title, @cfp)), html: {multipart: true}) do |f|
= f.input :start_date, as: :string, input_html: { id: 'registration-period-start-datepicker', start_date: @conference.start_date, end_date: @conference.end_date, readonly: 'readonly' } = f.input :start_date, as: :string, input_html: { id: 'registration-period-start-datepicker', start_date: @conference.start_date, end_date: @conference.end_date, readonly: 'readonly' }
= f.input :end_date, as: :string, input_html: { id: 'registration-period-end-datepicker', readonly: 'readonly' } = f.input :end_date, as: :string, input_html: { id: 'registration-period-end-datepicker', readonly: 'readonly' }
= f.input :cfp_type, as: :select, collection: (@cfp.new_record? ? @program.remaining_cfp_types : [@cfp.cfp_type] + @program.remaining_cfp_types).map {|type| ["#{type.capitalize}", type]}, include_blank: false, label: 'Type', input_html: { class: 'select-help-toggle' }
%p.text-right %p.text-right
= f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' }

View file

@ -0,0 +1,16 @@
%dt
Type:
%dd
= @cfp.cfp_type.capitalize
%dt
Start Date:
%dd#start_date
= @cfp.start_date.strftime('%A, %B %-d. %Y')
%dt
End Date:
%dd#end_date
= @cfp.end_date.strftime('%A, %B %-d. %Y')
%dt
Days Left:
%dd
= pluralize(@cfp.remaining_days, 'day')

View file

@ -0,0 +1,36 @@
.row
.col-md-12
.page-header
%h1 Call for Papers
%p.text-muted
Call for people to submit events to your conference
- if @program.cfps
.row
.col-md-12
%table.table.table-hover.datatable#tickets
%thead
%th Type
%th Start Date
%th End Date
%th Days Left
%th Actions
%tbody
- @program.cfps.each do |cfp|
%tr
%td
= link_to(admin_conference_program_cfp_path(@conference.short_title, cfp.id)) do
= cfp.cfp_type.capitalize
%td
= cfp.start_date.strftime('%A, %B %-d. %Y')
%td
= cfp.end_date.strftime('%A, %B %-d. %Y')
%td
= pluralize(cfp.remaining_days, 'day')
%td
.btn-group
= link_to 'Edit', edit_admin_conference_program_cfp_path(@conference.short_title, cfp.id), method: :get, class: 'btn btn-primary'
= link_to 'Delete', admin_conference_program_cfp_path(@conference.short_title, cfp.id), method: 'delete', class: 'btn btn-danger', data: { confirm: 'Are you sure you want to delete the CfP?' }
- if can? :new, @program.cfps.new
.row
.col-md-12.text-right
= link_to 'Create Call for Papers', new_admin_conference_program_cfp_path(@conference.short_title), class: 'btn btn-primary'

View file

@ -4,55 +4,13 @@
%h1 Call for Papers %h1 Call for Papers
%p.text-muted %p.text-muted
Call for people to submit events to your conference Call for people to submit events to your conference
- if @cfp .row
.row
.col-md-8 .col-md-8
%dl.dl-horizontal %dl.dl-horizontal
%dt = render "#{@cfp.cfp_type}_cfp"
Start Date: .row
%dd#start_date
= @cfp.start_date.strftime('%A, %B %-d. %Y')
%dt
End Date:
%dd#end_date
= @cfp.end_date.strftime('%A, %B %-d. %Y')
%dt
Days Left:
%dd
= pluralize(@cfp.remaining_days, 'day')
%dt
Event types:
%dd
= event_types(@conference)
%dt
Tracks:
%dd
= tracks(@conference)
%dt
Public Schedule
%dd#schedule_public
- if @program.schedule_public
Yes
- else
No
%dt
Schedule changeable?
%dd#schedule_changes
- if @program.schedule_fluid
Yes
- else
No
%dt
Rating Levels
%dd#rating
= @program.rating
.row
.col-md-12.text-right .col-md-12.text-right
= link_to(edit_admin_conference_program_cfp_path(@conference.short_title), class: 'btn btn-primary') do = link_to(edit_admin_conference_program_cfp_path(@conference.short_title, @cfp.id), class: 'btn btn-primary') do
Edit Edit
= link_to(admin_conference_program_cfp_path(@conference.short_title), method: 'delete', class: 'btn btn-danger', data: { confirm: 'Are you sure you want to delete the CfP?' }) do = link_to(admin_conference_program_cfp_path(@conference.short_title, @cfp.id), method: 'delete', class: 'btn btn-danger', data: { confirm: 'Are you sure you want to delete the CfP?' }) do
Delete Delete
- else
.row
.col-md-12.text-right
= link_to 'Create Call for Papers', new_admin_conference_program_cfp_path(@conference.short_title), class: 'btn btn-primary'

View file

@ -1,6 +1,23 @@
.text-center .text-center
%h4 #{title} %h4 #{title}
%canvas.doughnut_chart{ 'data-chart' => data.to_json } %canvas.doughnut_chart{ id: "dough_#{title}", 'data-chart' => data.to_json }
- if data - if data
- data.each do |key, value| - data.each do |key, value|
%span{ 'style' => "border-bottom: 3px solid #{value['color']}" } #{key}: #{value['value']} %span{ 'style' => "border-bottom: 3px solid #{value['color']}" } #{key}: #{value['value']}
:javascript
$(document).ready( function(){
var d = $("#dough_#{title}");
var dt = d.get(0).getContext('2d');
$(window).resize( respondCanvas );
function respondCanvas(){
dt.canvas.width = 150;
dt.canvas.height = 150;
}
respondCanvas();
});

View file

@ -27,3 +27,21 @@
%span{ 'style' => "border-bottom: 3px solid #{conference[:color]};", 'data-chart' => "#{name}" } %span{ 'style' => "border-bottom: 3px solid #{conference[:color]};", 'data-chart' => "#{name}" }
%input{ 'type' => 'checkbox', 'name' => "#{conference[:short_title]}" } %input{ 'type' => 'checkbox', 'name' => "#{conference[:short_title]}" }
#{conference[:short_title]} #{conference[:short_title]}
:javascript
$(document).ready( function(){
var c = $("#line_chart_#{name}");
var ct = c.get(0).getContext('2d');
var container = $(c).parent();
$(window).resize( respondCanvas );
function respondCanvas(){
c.attr('width', $(container).width() );
c.attr('height', $(container).height() );
}
respondCanvas();
});

View file

@ -1,12 +1,12 @@
.list-group .list-group
%li.list-group-item %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)}" }
%h4 %h4
Conference progress Conference progress
.progress .progress
.progress-bar{ 'role' => 'progressbar', 'aria-valuenow' => "#{conference_progress['process']}", 'aria-valuemin' => '0', .progress-bar{ 'role' => 'progressbar', 'aria-valuenow' => "#{conference_progress['process']}", 'aria-valuemin' => '0',
'aria-valuemax' => '100', 'style' => "width: #{conference_progress['process']}%;" } 'aria-valuemax' => '100', 'style' => "width: #{conference_progress['process']}%;" }
= conference_progress['process'] + '%' = conference_progress['process'] + '%'
%li{ 'class' => "list-group-item #{class_for_todo(conference_progress['registration'])}" } %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['registration'])}" }
%span{ 'class' => icon_for_todo(conference_progress['registration']) } %span{ 'class' => icon_for_todo(conference_progress['registration']) }
- if can? :update, @conference - if can? :update, @conference
- if conference.registration_period - if conference.registration_period
@ -15,13 +15,13 @@
= link_to 'Set up registration period', new_admin_conference_registration_period_path(conference_progress['short_title']) = link_to 'Set up registration period', new_admin_conference_registration_period_path(conference_progress['short_title'])
- else - else
Set up registration period Set up registration period
%li{ 'class' => "list-group-item #{class_for_todo(conference_progress['cfp'])}" } %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['cfp'])}" }
%span{ 'class' => icon_for_todo(conference_progress['cfp']) } %span{ 'class' => icon_for_todo(conference_progress['cfp']) }
- if can? :update, Cfp.new(program_id: @program.id) - if can? :update, Cfp.new(program_id: @program.id)
= link_to 'Set up call for papers', admin_conference_program_cfp_path(conference_progress['short_title']) = link_to 'Set up call for papers', admin_conference_program_cfps_path(conference_progress['short_title'])
- else - else
Set up call for papers Set up call for papers
%li{'class'=>"list-group-item #{class_for_todo(conference_progress['venue'])}"} %li{'class'=>"list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['venue'])}"}
%span{'class'=>icon_for_todo(conference_progress['venue'])} %span{'class'=>icon_for_todo(conference_progress['venue'])}
- if can? :update, Venue.new(conference: @conference) - if can? :update, Venue.new(conference: @conference)
- @conference.reload - @conference.reload
@ -32,31 +32,31 @@
- else - else
- @conference.reload - @conference.reload
Add venue Add venue
%li{ 'class' => "list-group-item #{class_for_todo(conference_progress['rooms'])}" } %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['rooms'])}" }
%span{ 'class' => icon_for_todo(conference_progress['rooms']) } %span{ 'class' => icon_for_todo(conference_progress['rooms']) }
- if @conference.venue && (can? :update, @conference.venue.rooms.build) - if @conference.venue && (can? :update, @conference.venue.rooms.build)
= link_to 'Add rooms', admin_conference_venue_rooms_path(conference_progress['short_title']) = link_to 'Add rooms', admin_conference_venue_rooms_path(conference_progress['short_title'])
- else - else
Add rooms Add rooms
%li{ 'class' => "list-group-item #{class_for_todo(conference_progress['tracks'])}" } %li{ 'class' => "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['tracks'])}" }
%span{ 'class' => icon_for_todo(conference_progress['tracks']) } %span{ 'class' => icon_for_todo(conference_progress['tracks']) }
- if can? :update, @conference.program.tracks.build - if can? :update, @conference.program.tracks.build
= link_to 'Add tracks', admin_conference_program_tracks_path(conference_progress['short_title']) = link_to 'Add tracks', admin_conference_program_tracks_path(conference_progress['short_title'])
- else - else
Add tracks Add tracks
%li{ 'class' => "list-group-item #{class_for_todo(conference_progress['event_types'])}" } %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']) } %span{ 'class' => icon_for_todo(conference_progress['event_types']) }
- if can? :update, @conference.program.event_types.build - if can? :update, @conference.program.event_types.build
= link_to 'Add event types', admin_conference_program_event_types_path(conference_progress['short_title']) = link_to 'Add event types', admin_conference_program_event_types_path(conference_progress['short_title'])
- else - else
Add event types Add event types
%li{ 'class' => "list-group-item #{class_for_todo(conference_progress['difficulty_levels'])}" } %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']) } %span{ 'class' => icon_for_todo(conference_progress['difficulty_levels']) }
- if can? :update, @conference.program.difficulty_levels.build - if can? :update, @conference.program.difficulty_levels.build
= link_to 'Add difficulty levels', admin_conference_program_difficulty_levels_path(conference_progress['short_title']) = link_to 'Add difficulty levels', admin_conference_program_difficulty_levels_path(conference_progress['short_title'])
- else - else
Add difficulty levels Add difficulty levels
%li{ class: "list-group-item #{class_for_todo(conference_progress['splashpage'])}" } %li{ class: "list-group-item #{hidden_if_conference_over(conference)} #{class_for_todo(conference_progress['splashpage'])}" }
%span{ 'class' => icon_for_todo(conference_progress['splashpage']) } %span{ 'class' => icon_for_todo(conference_progress['splashpage']) }
- if can? :update, @conference - if can? :update, @conference
= link_to 'Set up a Splashpage', admin_conference_splashpage_path(conference_progress['short_title']) = link_to 'Set up a Splashpage', admin_conference_splashpage_path(conference_progress['short_title'])

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