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.test
.env.local
docker-compose.env
docker-compose.yml
.DS_Store
.byebug_history

View file

@ -1,6 +1,6 @@
# This configuration was generated by
# `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
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
@ -8,9 +8,13 @@
linters:
# Offense count: 952
# Offense count: 945
LineLength:
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/index.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/index.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/lodgings/_form.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/show.html.haml"
- "app/views/admin/questions/_form.html.haml"
@ -73,6 +78,7 @@ linters:
- "app/views/admin/resources/show.html.haml"
- "app/views/admin/roles/_form.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/show.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/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/new.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/_volunteer.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/_conference_details.html.haml"
- "app/views/conferences/_gallery.html.haml"
@ -143,7 +153,9 @@ linters:
- "app/views/layouts/_admin_sidebar_index.html.haml"
- "app/views/layouts/_messages.html.haml"
- "app/views/layouts/_navigation.html.haml"
- "app/views/layouts/_user_menu.html.haml"
- "app/views/layouts/application.html.haml"
- "app/views/organizations/index.html.haml"
- "app/views/payments/_payment.html.haml"
- "app/views/proposals/_form.html.haml"
- "app/views/proposals/_proposal_form.html.haml"
@ -169,11 +181,21 @@ linters:
- "app/views/tickets/index.html.haml"
- "app/views/users/edit.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:
exclude:
- "app/views/admin/booths/_change_state_dropdown.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/conferences/_todo_list.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_index.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/registration_periods/_form.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_item.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
IdNames:
@ -244,15 +273,14 @@ linters:
- "app/views/admin/users/_event_registrations.html.haml"
- "app/views/admin/users/show.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:
exclude:
- "app/views/admin/conferences/_doughnut_chart.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/new.html.haml"
@ -282,6 +310,7 @@ linters:
SpaceInsideHashAttributes:
exclude:
- "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/index.html.haml"
- "app/views/admin/registrations/index.html.haml"
@ -327,12 +356,13 @@ linters:
- "app/views/shared/_object_changes.html.haml"
- "app/views/tickets/_ticket.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:
exclude:
- "app/views/admin/emails/index.html.haml"
- "app/views/admin/events/reports.html.haml"
- "app/views/admin/events/show.html.haml"
- "app/views/admin/reports/index.html.haml"
- "app/views/admin/users/show.html.haml"
@ -356,6 +386,7 @@ linters:
- "app/views/admin/tracks/show.html.haml"
- "app/views/admin/venues/show.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/_ticket.html.haml"
- "app/views/conference_registrations/show.html.haml"
@ -380,12 +411,27 @@ linters:
# Offense count: 14
ConsecutiveSilentScripts:
exclude:
- "app/views/admin/booths/_change_state_dropdown.html.haml"
- "app/views/admin/events/index.html.haml"
- "app/views/admin/schedules/_day_tab.html.haml"
- "app/views/admin/schedules/_event.html.haml"
- "app/views/admin/versions/_object_desc_and_link.html.haml"
- "app/views/booths/index.html.haml"
- "app/views/schedules/_carousel.html.haml"
# Offense count: 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
FinalNewline:
exclude:
@ -406,19 +452,6 @@ linters:
- "app/views/schedules/_event.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
ClassAttributeWithStaticValue:
exclude:

View file

@ -13,100 +13,6 @@ AllCops:
- 'config/**/*'
- '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 ###############################
@ -123,77 +29,6 @@ Metrics/ClassLength:
Metrics/BlockLength:
Exclude:
- 'spec/models/conference_spec.rb'
#################### Lint ###############################
# 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
- 'spec/features/ability_spec.rb'
- 'spec/models/ability_spec.rb'
- 'spec/models/admin_ability_spec.rb'

File diff suppressed because it is too large Load diff

View file

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

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
# 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
# boxes will only be checked for updates when the user runs

View file

@ -55,7 +55,6 @@ $(function() {
}
function draw_line_chart(animation, $canvas){
var options = get_animation({}, animation);
var chart_data = create_dataset($canvas);
var weeks = $canvas.parent().data('weeks');
var data = {
@ -63,10 +62,27 @@ $(function() {
datasets : chart_data
}
var options = get_animation(wholeNumberAxisFix(data), animation);
var ctx = $canvas.get(0).getContext("2d");
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){
var selected = getSelectedConferences($canvas);
var chart_data = $canvas.parent().data('chart');

View file

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

View file

@ -78,11 +78,12 @@ var Schedule = {
};
$(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();
$('.non_schedulable .schedule-event-delete-button').hide();
// set events as draggable
$('.schedule-event').draggable({
$('.schedule-event').not('.non_schedulable').draggable({
snap: '.schedule-room-slot',
revertDuration: 200,
revert: function (event, ui) {
@ -99,7 +100,7 @@ $(document).ready( function() {
});
// set room cells as droppable
$('.schedule-room-slot').droppable({
$('.schedule-room-slot').not('.non_schedulable .schedule-room-slot').droppable({
accept: '.schedule-event',
tolerance: "pointer",
drop: function(event, ui) {

View file

@ -4,14 +4,14 @@ function update_price($this){
// Calculate price for row
var value = $this.val();
var price = $('#price_' + id).text();
$('#total_row_' + id).text(value * price);
$('#total_row_' + id).text((value * price).toFixed(2));
// Calculate total price
var total = 0;
$('.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() {

View file

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

View file

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

View file

@ -90,3 +90,17 @@ p.comment-body {
.changeset{
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
before_filter :verify_user_admin
private
def current_ability
@current_ability ||= AdminAbility.new(current_user)
end
def verify_user_admin
if (current_user.nil?)
redirect_to sign_in_path
return false
end
unless (current_user.has_role? :organizer, :any) || (current_user.has_role? :cfp, :any) ||
(current_user.has_role? :info_desk, :any) ||
(current_user.has_role? :volunteers_coordinator, :any) || current_user.is_admin
raise CanCan::AccessDenied.new('You are not authorized to access this area!')
(current_user.has_role? :info_desk, :any) || (current_user.has_role? :organization_admin, :any) ||
(current_user.has_role? :volunteers_coordinator, :any) ||
(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

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
load_and_authorize_resource :conference, find_by: :short_title
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 new
@cfp = @program.build_cfp
@cfp = @program.cfps.new
end
def edit; end
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?
if @cfp.save
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.'
else
flash.now[:error] = "Creating the call for papers failed. #{@cfp.errors.full_messages.join('. ')}."
@ -27,14 +29,13 @@ module Admin
end
def update
@cfp = @program.cfp
@cfp.assign_attributes(cfp_params)
send_mail_on_cfp_dates_updates = @cfp.notify_on_cfp_date_update?
if @cfp.update_attributes(cfp_params)
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.'
else
flash.now[:error] = "Updating call for papers failed. #{@cfp.errors.to_a.join('. ')}."
@ -44,9 +45,9 @@ module Admin
def 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
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('. ')}."
end
end
@ -54,7 +55,7 @@ module Admin
private
def cfp_params
params.require(:cfp).permit(:start_date, :end_date)
params.require(:cfp).permit(:start_date, :end_date, :cfp_type)
end
end
end

View file

@ -37,6 +37,9 @@ module Admin
@registrations = {}
@registration_weeks = [0]
@tickets = {}
@ticket_weeks = [0]
@conferences.each do |c|
# Event submissions over time chart
@submissions[c.short_title] = c.get_submissions_per_week
@ -45,6 +48,10 @@ module Admin
# Conference registrations over time chart
@registrations[c.short_title] = c.get_registrations_per_week
@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
@cfp_weeks = @cfp_weeks.max
@ -55,12 +62,17 @@ module Admin
@registrations = normalize_array_length(@registrations, @registration_weeks)
@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
@user_distribution = Conference.user_distribution
end
def new
@conference = Conference.new
@organizations = Organization.accessible_by(current_ability, :update).pluck(:name, :id)
end
def create
@ -133,6 +145,20 @@ module Admin
@submissions_data = @submissions_data.except('Weeks')
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
@event_type_distribution = @conference.event_type_distribution
@event_type_distribution_confirmed = @conference.event_type_distribution(:confirmed)
@ -186,7 +212,7 @@ module Admin
:vpositions_attributes, :use_volunteers, :color,
:sponsorship_levels_attributes, :sponsors_attributes,
:targets, :targets_attributes,
:campaigns, :campaigns_attributes, :registration_limit)
:campaigns, :campaigns_attributes, :registration_limit, :organization_id, :ticket_layout, :booth_limit)
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_venue_updated, :venue_updated_subject, :venue_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

View file

@ -4,8 +4,10 @@ module Admin
load_and_authorize_resource :program, through: :conference, singleton: true
load_and_authorize_resource :event, through: :program
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
# you get lost in timezone conversions...
@ -16,8 +18,6 @@ module Admin
end
def index
@events = @program.events
@tracks = @program.tracks
@difficulty_levels = @program.difficulty_levels
@event_types = @program.event_types
@tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed)
@ -43,7 +43,6 @@ module Admin
end
def show
@tracks = @program.tracks
@event_types = @program.event_types
@comments = @event.root_comments
@comment_count = @event.comment_threads.count
@ -58,7 +57,6 @@ module Admin
def edit
@event_types = @program.event_types
@tracks = Track.all
@comments = @event.root_comments
@comment_count = @event.comment_threads.count
@user = @event.submitter
@ -104,7 +102,7 @@ module Admin
ahoy.track 'Event submission', title: 'New submission'
redirect_to admin_conference_program_events_path(@conference.short_title), notice: 'Event was successfully submitted.'
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'
end
end
@ -187,16 +185,6 @@ module Admin
params.require(:comment).permit(:commentable, :body, :user_id)
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)
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
end
end
def get_tracks
@tracks = Track.accessible_by(current_ability).where(program: @program).confirmed
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('. ')}."
render :new
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

View file

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

View file

@ -2,9 +2,11 @@ module Admin
class ReportsController < Admin::BaseController
load_and_authorize_resource :conference, find_by: :short_title
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
@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_missing_commercial = @events.where.not(id: @events_commercials.pluck(:commercialable_id))
@events_with_requirements = @events.where.not(description: ['', nil])

View file

@ -8,14 +8,26 @@ module Admin
def index
@roles = Role.where(resource: @conference)
tracks = @conference.program.tracks.where.not(submitter: nil)
@roles += Role.where(resource: tracks)
authorize! :index, @role
end
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
end
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
end
@ -23,7 +35,13 @@ module Admin
role_name = @role.name
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
else
@role.name = role_name
@ -36,8 +54,14 @@ module Admin
user = User.find_by(email: user_params[:email])
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
redirect_to admin_conference_role_path(@conference.short_title, @role.name),
redirect_to url,
error: 'Could not find user. Please provide a valid email!'
return
end
@ -49,17 +73,23 @@ module Admin
return
end
if @role.resource_type == 'Conference'
role_resource = @conference
elsif @role.resource_type == 'Track'
role_resource = @track
end
# Remove user
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}"
else
flash[:error] = "Could not remove role #{@role.name} from user #{user.email}"
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}"
# 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}"
else
flash[:error] = "Coud not add role #{@role.name} to #{user.email}"
@ -67,7 +97,7 @@ module Admin
respond_to do |format|
format.js
format.html { redirect_to admin_conference_role_path(@conference.short_title, @role.name) }
format.html { redirect_to url }
end
end
@ -77,7 +107,12 @@ module Admin
# Set 'organizer' as default role, when there is no other selection
@selection = params[:id] ? params[:id].parameterize.underscore : 'organizer'
@role = Role.find_by(name: @selection, resource: @conference)
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)
end
end
def role_params

View file

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

View file

@ -47,7 +47,8 @@ module Admin
:include_tracks, :include_program, :include_cfp,
:include_venue, :include_registrations,
:include_tickets, :include_lodgings,
:include_sponsors, :include_social_media)
:include_sponsors, :include_social_media,
:include_booths)
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
authorize! :update, Ticket.new(conference_id: @conference.id)
@tickets_sold_distribution = @conference.tickets_sold_distribution
@tickets_turnover_distribution = @conference.tickets_turnover_distribution
end
def new
@ -48,7 +50,7 @@ module Admin
private
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

View file

@ -2,7 +2,10 @@ module Admin
class TracksController < Admin::BaseController
load_and_authorize_resource :conference, find_by: :short_title
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
@ -19,6 +22,8 @@ module Admin
def create
@track = @program.tracks.new(track_params)
@track.state = 'confirmed'
@track.cfp_active = true
if @track.save
redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title),
notice: 'Track successfully created.'
@ -50,10 +55,80 @@ module Admin
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
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

View file

@ -1,17 +1,21 @@
module Admin
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
authorize! :index, PaperTrail::Version.new(item_type: 'User')
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])
@conferences_with_role = current_user.is_admin? ? Conference.pluck(:short_title) : Conference.with_role([:organizer, :cfp, :info_desk], current_user).pluck(:short_title)
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
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 @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'
@ -33,9 +37,6 @@ module Admin
end
def revert_object
@version = PaperTrail::Version.find(params[:id])
authorize! :revert_object, @version
if @version.event != 'create'
if @version.reify.save
flash[:notice] = 'The selected change was successfully reverted'

View file

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

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
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
@total_price_per_ticket = @tickets.group(:ticket_id).sum('amount_paid * quantity')
@ticket_payments = @tickets.group_by(&:ticket_id)
@total_quantity = @tickets.group(:ticket_id).sum(:quantity)
end
@ -38,12 +39,12 @@ class ConferenceRegistrationsController < ApplicationController
def create
@registration = @conference.registrations.new(registration_params)
if current_user.nil?
# @user variable needs to be set so that _sign_up_form_embedded works properly
@user = @registration.build_user(user_params)
else
@user = current_user
end
@user = if current_user.nil?
# @user variable needs to be set so that _sign_up_form_embedded works properly
@registration.build_user(user_params)
else
current_user
end
@registration.user = @user
authorize! :create, @registration

View file

@ -1,18 +1,29 @@
class ConferencesController < ApplicationController
protect_from_forgery with: :null_session
before_action :respond_to_options
load_and_authorize_resource find_by: :short_title
load_resource :program, through: :conference, singleton: true, except: :index
load_and_authorize_resource find_by: :short_title, except: :show
def index
@current = Conference.where('end_date >= ?', Date.current).reorder(start_date: :asc)
@antiquated = @conferences - @current
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
def load_conference_by_domain
Conference.find_by(custom_domain: request.domain)
end
def respond_to_options
respond_to do |format|
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
@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)
end
@ -18,7 +21,7 @@ class PaymentsController < ApplicationController
if @payment.purchase && @payment.save
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.'
else
@total_amount_to_pay = Ticket.total_price(@conference, current_user, paid: false)
@ -38,6 +41,8 @@ class PaymentsController < ApplicationController
end
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

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.
@event.speakers = [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
ahoy.track 'Event submission', title: 'New submission'
redirect_to conference_program_proposals_path(@conference.short_title), notice: 'Proposal was successfully submitted.'
@ -61,6 +69,13 @@ class ProposalsController < ApplicationController
def update
@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)
redirect_to conference_program_proposals_path(conference_id: @conference.short_title),
notice: 'Proposal was successfully updated.'

View file

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

View file

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

View file

@ -2,6 +2,7 @@ class TicketPurchasesController < ApplicationController
before_filter :authenticate_user!
load_resource :conference, find_by: :short_title
authorize_resource :conference_registrations, class: Registration
authorize_resource
def create
current_user.ticket_purchases.by_conference(@conference).unpaid.destroy_all
@ -11,18 +12,22 @@ class TicketPurchasesController < ApplicationController
redirect_to new_conference_payment_path,
notice: 'Please pay here to get tickets.'
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.'
else
redirect_to conference_tickets_path(@conference.short_title),
error: 'Please get at least one ticket to continue.'
end
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}"
end
end
def index
@unpaid_ticket_purchases = current_user.ticket_purchases.by_conference(@conference).unpaid
end
private
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)
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
# 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
unless (user = openid.user)
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
begin

View file

@ -33,6 +33,11 @@ module ApplicationHelper
result
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)
def resource_name
:user
@ -51,7 +56,7 @@ module ApplicationHelper
end
def tracks(conference)
all = conference.program.tracks.map {|t| t.name}
all = conference.program.tracks.confirmed.cfp_active.pluck(:name)
first = all[0...-1]
last = all[-1]
ts = ''
@ -93,25 +98,6 @@ module ApplicationHelper
.reverse.sub(',', ' dna ').reverse
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)
hashmap.each do |_, value|
if value.length < length
@ -131,7 +117,7 @@ module ApplicationHelper
def concurrent_events(event)
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 }
concurrent_events = []
@ -147,7 +133,7 @@ module ApplicationHelper
end
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
def speaker_selector_input(form)
@ -157,6 +143,14 @@ module ApplicationHelper
include_blank: false, label: 'Speakers', input_html: { class: 'select-help-toggle', multiple: 'true' }
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)
conference.program.event_types.map { |et| et.title.pluralize }.to_sentence
end
@ -168,4 +162,13 @@ module ApplicationHelper
new_user_session_path
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

View file

@ -39,4 +39,12 @@ module EventsHelper
content_tag :span, 'REPLACEMENT', class: (['label', 'label-info'] + label_classes)
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

View file

@ -15,6 +15,19 @@ module FormatHelper
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)
progress = progress.to_i
if progress == 100
@ -102,13 +115,12 @@ module FormatHelper
end
end
# rubocop:disable Lint/EndAlignment
def word_pluralize(count, singular, plural = nil)
word = if (count == 1 || count =~ /^1(\.0+)?$/)
singular
else
plural || singular.pluralize
end
singular
else
plural || singular.pluralize
end
"#{word}"
end
@ -176,7 +188,7 @@ module FormatHelper
(schedule == @selected_schedule) ? 'Yes' : 'No'
end
def markdown(text)
def markdown(text, escape_html=true)
return '' if text.nil?
options = {
@ -184,12 +196,12 @@ module FormatHelper
space_after_headers: 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
end
def markdown_hint(text='')
markdown("#{text} Please look at #{link_to '**Markdown Syntax**', 'https://daringfireball.net/projects/markdown/syntax', target: '_blank'} to format your text")
markdown("#{text} Please look at #{link_to '**Markdown Syntax**', 'https://daringfireball.net/projects/markdown/syntax', target: '_blank'} to format your text", false)
end
def quantity_left_of(resource)

View file

@ -2,14 +2,6 @@ module PathsHelper
##
# 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)
if current_page?(link)

View file

@ -2,35 +2,88 @@ module VersionsHelper
##
# Groups functions related to change description
##
def link_if_alive(version, link_text, link_url)
version.item ? link_to(link_text, link_url) : link_text
def link_if_alive(version, link_text, link_url, conference)
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
def subscription_change_description(version)
user = current_or_last_object_state(version.item_type, version.item_id).user
user_name = user.name unless user.id.to_s == version.whodunnit
user_id = current_or_last_object_state(version.item_type, version.item_id).user_id
user_name = User.find_by(id: user_id).try(:name) || current_or_last_object_state('User', user_id).try(:name) || PaperTrail::Version.where(item_type: 'User', item_id: user_id).last.changeset[:name].second unless user_id.to_s == version.whodunnit
version.event == 'create' ? "subscribed #{user_name} to" : "unsubscribed #{user_name} from"
end
def registration_change_description(version)
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'
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
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
when 'create' then 'registered to'
when 'update' then "updated #{updated_attributes(version)} of the registration for"
when 'destroy' then 'unregistered from'
when 'destroy' then 'unregistered from'
end
else
case version.event
when 'create' then "registered #{user.name} to"
when 'update' then "updated #{updated_attributes(version)} of #{user.name}'s registration for"
when 'destroy' then "unregistered #{user.name} from"
when 'create' then "registered #{user_name} to"
when 'update' then "updated #{updated_attributes(version)} of #{user_name}'s registration for"
when 'destroy' then "unregistered #{user_name} from"
end
end
end

View file

@ -8,6 +8,22 @@ class Mailbot < ActionMailer::Base
conference.email_settings.registration_body))
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)
conference = event.program.conference
@ -81,6 +97,24 @@ class Mailbot < ActionMailer::Base
conference.email_settings.cfp_dates_updated_body))
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)
@comment = comment
@event = @comment.commentable

View file

@ -3,32 +3,19 @@ class Ability
# Initializes the ability class
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
# This is what sets up the different abilities
if user.new_record?
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
signed_in(user)
common_abilities_for_admins(user) if user.roles.any? || user.is_admin?
end
end
# Abilities for not signed in users (guests)
def not_signed_in
can [:index], Organization
can [:index], Conference
can [:show], Conference do |conference|
conference.splashpage && conference.splashpage.public == true
@ -73,7 +60,6 @@ class Ability
def signed_in(user)
# Abilities from not_signed_in user are also inherited
not_signed_in
can :manage, 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)
end
can :index, Organization
can :index, Ticket
can :manage, TicketPurchase, 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
@ -93,181 +89,58 @@ class Ability
event.program.cfp_open? && event.new_record?
end
can [:update, :show, :delete, :index], Event do |event|
can [:update, :show, :index], Event do |event|
event.users.include?(user)
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 [:new, :create], Track do |track|
track.new_record? && track.program.cfps.for_tracks.try(:open?)
end
can [:index, :show, :restart, :confirm, :withdraw], Track, submitter_id: user.id
can [:edit, :update], Track do |track|
user == track.submitter && !(track.accepted? || track.confirmed?)
end
end
# Abilities for signed in users with roles
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
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
# 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 [:show], Conference
can :index, Commercial, commercialable_type: 'Conference'
cannot [:edit, :update, :destroy], Question, global: true
# for admins
can :manage, :all if user.is_admin
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)
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)
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)
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?
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
# 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)
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
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)
if conf_ids_for_info_desk
# To access conference/proposals/registrations
can :manage, Registration, conference_id: conf_ids_for_info_desk
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
class Cfp < ActiveRecord::Base
TYPES = %w(events booths tracks).freeze
has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id }
belongs_to :program
@ -8,6 +10,15 @@ class Cfp < ActiveRecord::Base
validates :start_date, :end_date, presence: true
validate :before_end_of_conference
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
@ -55,15 +66,52 @@ class Cfp < ActiveRecord::Base
result > 0 ? result : 0
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
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
.add(:end_date, "can't be after the conference end date (#{program.conference.end_date})")
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
.add(:start_date, "can't be after the conference end date (#{program.conference.end_date})")
end

View file

@ -8,7 +8,7 @@ class Comment < ActiveRecord::Base
# want user to vote on the quality of comments.
#acts_as_votable
belongs_to :commentable, polymorphic: true
belongs_to :commentable, counter_cache: true, polymorphic: true
# NOTE: Comments belong to a 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
include RevisionCount
require 'uri'
serialize :events_per_week, Hash
# Needed to call 'Conference.with_role' in /models/ability.rb
@ -6,6 +7,10 @@ class Conference < ActiveRecord::Base
resourcify :roles, dependent: :delete_all
default_scope { order('start_date DESC') }
scope :upcoming, (-> { where('end_date >= ?', Date.current) })
scope :past, (-> { where('end_date < ?', Date.current) })
belongs_to :organization
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 :program, dependent: :destroy
has_one :venue, dependent: :destroy
has_many :physical_tickets, through: :ticket_purchases
has_many :ticket_purchases, dependent: :destroy
has_many :payments, dependent: :destroy
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 :booths, dependent: :destroy
has_many :lodgings, dependent: :destroy
has_many :registrations, dependent: :destroy
@ -53,7 +64,9 @@ class Conference < ActiveRecord::Base
:start_date,
:end_date,
:start_hour,
:end_hour, presence: true
:end_hour,
:ticket_layout,
:organization, presence: true
validates :short_title, uniqueness: true
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_update :delete_event_schedules
enum ticket_layout: [:portrait, :landscape]
##
# Checks if the user is registered to the conference
#
@ -128,7 +143,7 @@ class Conference < ActiveRecord::Base
result = []
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
weeks = program.cfp.weeks
result = calculate_items_per_week(start_week, weeks, submissions)
@ -176,7 +191,7 @@ class Conference < ActiveRecord::Base
registration_period.start_date &&
registration_period.end_date
reg = registrations.group(:week).count
reg = registrations.group(:week).order(:week).count
start_week = get_registration_start_week
weeks = registration_weeks
result = calculate_items_per_week(start_week, weeks, reg)
@ -184,6 +199,59 @@ class Conference < ActiveRecord::Base
result
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.
#
@ -281,8 +349,8 @@ class Conference < ActiveRecord::Base
# ====Returns
# * +hash+ -> user: submissions
def self.get_top_submitter(limit = 5)
submitter = EventUser.where('event_role = ?', 'submitter').limit(limit).group(:user_id)
counter = submitter.order('count_all desc').count
submitter = EventUser.select(:user_id).where('event_role = ?', 'submitter').limit(limit).group(:user_id)
counter = submitter.order('count_all desc').count(:all)
calculate_user_submission_hash(submitter, counter)
end
@ -292,10 +360,10 @@ class Conference < ActiveRecord::Base
# ====Returns
# * +hash+ -> user: submissions
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)
.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)
end
@ -396,6 +464,46 @@ class Conference < ActiveRecord::Base
calculate_user_distribution_hash(active_user, unconfirmed_user, dead_user)
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
#
@ -460,11 +568,11 @@ class Conference < ActiveRecord::Base
# ====Returns
# * +hash+ -> track => {color, value}
def tracks_distribution(state = nil)
if state
tracks_grouped = program.events.select(:track_id).where('state = ?', state).group(:track_id)
else
tracks_grouped = program.events.select(:track_id).group(:track_id)
end
tracks_grouped = if state
program.events.select(:track_id).where('state = ?', state).group(:track_id)
else
program.events.select(:track_id).group(:track_id)
end
tracks_counted = tracks_grouped.count
calculate_track_distribution_hash(tracks_grouped, tracks_counted)
@ -547,7 +655,7 @@ class Conference < ActiveRecord::Base
def self.write_event_distribution_to_db
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 = {}
Event.state_machine.states.each do |state|
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
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
# Returns a different html colour for every i and consecutive colors are
@ -803,7 +933,7 @@ class Conference < ActiveRecord::Base
hash[key] = 0
end
end
Hash[ hash.sort ]
Hash[hash.sort]
end
##
@ -889,11 +1019,11 @@ class Conference < ActiveRecord::Base
# ====Returns
# * +hash+ -> object_type => {color, value}
def calculate_event_distribution(group_by_id, association_symbol, state = nil)
if state
grouped = program.events.select(group_by_id).where('state = ?', 'confirmed').group(group_by_id)
else
grouped = program.events.select(group_by_id).group(group_by_id)
end
grouped = if state
program.events.select(group_by_id).where('state = ?', 'confirmed').group(group_by_id)
else
program.events.select(group_by_id).group(group_by_id)
end
counted = grouped.count
calculate_distribution_hash(grouped, counted, association_symbol)
@ -1004,7 +1134,8 @@ class Conference < ActiveRecord::Base
def self.calculate_user_submission_hash(submitters, counter)
result = ActiveSupport::OrderedHash.new
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
result[submitter.user] = value
end

View file

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

View file

@ -1,5 +1,6 @@
class Event < ActiveRecord::Base
include ActiveRecord::Transitions
include RevisionCount
has_paper_trail on: [:create, :update], ignore: [:updated_at, :guid, :week], meta: { conference_id: :conference_id }
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 }
validate :max_attendees_no_more_than_room_size
validate :valid_track
scope :confirmed, -> { where(state: 'confirmed') }
scope :canceled, -> { where(state: 'canceled') }
@ -83,7 +85,7 @@ class Event < ActiveRecord::Base
# ====Returns
# * +true+ or +false+
def scheduled?
event_schedules.find_by(schedule_id: program.selected_schedule_id).present?
event_schedules.find_by(schedule_id: selected_schedule_id).present?
end
def registration_possible?
@ -115,7 +117,7 @@ class Event < ActiveRecord::Base
def average_rating
@total_rating = 0
votes.each do |vote|
@total_rating = @total_rating + vote.rating
@total_rating += vote.rating
end
@total = votes.size
@total_rating > 0 ? number_with_precision(@total_rating / @total.to_f, precision: 2, strip_insignificant_zeros: true) : 0
@ -241,14 +243,22 @@ class Event < ActiveRecord::Base
def room
# We use try(:selected_schedule_id) because this function is used for
# validations so program could not be present there
event_schedules.find_by(schedule_id: program.try(:selected_schedule_id)).try(:room)
if track.try(:self_organized?)
track.room
else
event_schedules.find_by(schedule_id: program.try(:selected_schedule_id)).try(:room)
end
end
##
# Returns the start time at which this event is scheduled
#
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
private
@ -298,4 +308,25 @@ class Event < ActiveRecord::Base
def conference_id
program.conference_id
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

View file

@ -12,6 +12,9 @@ class EventSchedule < ActiveRecord::Base
validates :event, uniqueness: { scope: :schedule }
validate :start_after_end_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 :canceled, -> { joins(:event).where('state = ?', 'canceled') }
@ -52,4 +55,35 @@ class EventSchedule < ActiveRecord::Base
def conference_id
schedule.program.conference_id
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

View file

@ -1,6 +1,6 @@
class EventUser < ActiveRecord::Base
# TODO Do we need these roles?
ROLES = [['Speaker', 'speaker'], ['Submitter', 'submitter'], ['Moderator', 'moderator']]
# TODO: Do we need these roles?
ROLES = [%w[Speaker speaker], %w[Submitter submitter], %w[Moderator moderator]]
belongs_to :event
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
has_one :cfp, dependent: :destroy
has_many :cfps, dependent: :destroy
has_many :event_types, dependent: :destroy
has_many :tracks, 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
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
##
@ -163,6 +167,23 @@ class Program < ActiveRecord::Base
EventSchedule.where(schedule: selected_schedule).where(start_time: parsed_date..(parsed_date + 1)).any?
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
##

View file

@ -2,7 +2,6 @@ class Registration < ActiveRecord::Base
belongs_to :user
belongs_to :conference
has_and_belongs_to_many :events
has_and_belongs_to_many :qanswers
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
include RevisionCount
belongs_to :venue
has_many :event_schedules, dependent: :destroy
has_many :tracks
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
def conference
venue.conference
end
private
def generate_guid

View file

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

View file

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

View file

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

View file

@ -2,9 +2,11 @@ class TicketPurchase < ActiveRecord::Base
belongs_to :ticket
belongs_to :user
belongs_to :conference
belongs_to :payment
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 }
delegate :title, to: :ticket
@ -13,25 +15,32 @@ class TicketPurchase < ActiveRecord::Base
delegate :price_cents, to: :ticket
delegate :price_currency, to: :ticket
has_many :physical_tickets
scope :paid, -> { where(paid: true) }
scope :unpaid, -> { where(paid: false) }
scope :by_conference, ->(conference) { where(conference_id: conference.id) }
scope :by_user, ->(user) { where(user_id: user.id) }
after_create :set_week
def self.purchase(conference, user, purchases)
errors = []
ActiveRecord::Base.transaction do
conference.tickets.each do |ticket|
quantity = purchases[ticket.id.to_s].to_i
# if the user bought the ticket and is still unpaid, just update the quantity
if ticket.bought?(user) && ticket.unpaid?(user)
purchase = update_quantity(conference, quantity, ticket, user)
else
purchase = purchase_ticket(conference, quantity, ticket, user)
end
if purchase && !purchase.save
errors.push(purchase.errors.full_messages)
if count_purchased_registration_tickets(conference, purchases) > 1
errors.push('You cannot buy more than one registration tickets.')
else
ActiveRecord::Base.transaction do
conference.tickets.each do |ticket|
quantity = purchases[ticket.id.to_s].to_i
# if the user bought the ticket and is still unpaid, just update the quantity
purchase = if ticket.bought?(user) && ticket.unpaid?(user)
update_quantity(conference, quantity, ticket, user)
else
purchase_ticket(conference, quantity, ticket, user)
end
if purchase && !purchase.save
errors.push(purchase.errors.full_messages)
end
end
end
end
@ -44,7 +53,8 @@ class TicketPurchase < ActiveRecord::Base
conference_id: conference.id,
user_id: user.id,
quantity: quantity,
paid: ticket.price_cents.zero?)
amount_paid: ticket.price)
purchase.pay(nil) if ticket.price_cents.zero?
end
purchase
end
@ -58,4 +68,37 @@ class TicketPurchase < ActiveRecord::Base
purchase.quantity = quantity if quantity > 0
purchase
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

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
belongs_to :program
has_many :events, dependent: :nullify
include ActiveRecord::Transitions
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
validates :name, presence: true
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
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
def generate_guid
@ -27,4 +188,51 @@ class Track < ActiveRecord::Base
def conference_id
program.conference_id
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

View file

@ -6,6 +6,11 @@ end
class User < ActiveRecord::Base
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 :roles, through: :users_roles, dependent: :destroy
@ -25,13 +30,13 @@ class User < ActiveRecord::Base
# :lockable, :timeoutable and :omniauthable
devise_modules = []
if ENV['OSEM_ICHAIN_ENABLED'] == 'true'
devise_modules += [ :ichain_authenticatable, :ichain_registerable, :omniauthable, omniauth_providers: [] ]
else
devise_modules += [:database_authenticatable, :registerable,
devise_modules += if ENV['OSEM_ICHAIN_ENABLED'] == 'true'
[:ichain_authenticatable, :ichain_registerable, :omniauthable, omniauth_providers: []]
else
[:database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable,
:omniauthable, omniauth_providers: [:suse, :google, :facebook, :github] ]
end
:omniauthable, omniauth_providers: [:suse, :google, :facebook, :github]]
end
devise(*devise_modules)
@ -42,14 +47,26 @@ class User < ActiveRecord::Base
has_many :event_users, dependent: :destroy
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 :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 :ticket_purchases, 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 :voted_events, through: :votes, source: :events
has_many :subscriptions, dependent: :destroy
has_many :tracks, foreign_key: 'submitter_id'
has_many :booth_requests
has_many :booth_requests, dependent: :destroy
has_many :booths, through: :booth_requests
accepts_nested_attributes_for :roles
scope :admin, -> { where(is_admin: true) }
@ -80,6 +97,12 @@ class User < ActiveRecord::Base
event_registration.attended
end
def mark_attendance_for_conference conference
registration = registrations.for_conference(conference)
registration.attended = true
registration.save
end
def name
self[:name].blank? ? username : self[:name]
end
@ -156,7 +179,11 @@ class User < ActiveRecord::Base
def get_roles
result = {}
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?
result[role.name] = [resource]
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

@ -60,7 +60,7 @@ class ConferenceSerializer < ActiveModel::Serializer
def date_range
if defined? date_string(object.start_date, object.end_date)
date_string(object.start_date, object.end_date).try(:split, ',').try(:first)
date_string(object.start_date, object.end_date).try(:split, ',').try(:first)
end
end
end

View file

@ -52,6 +52,10 @@ class PictureUploader < CarrierWave::Uploader::Base
"system/#{object_class_name}/#{mounted_as}/#{model.id}"
end
def image
@image ||= MiniMagick::Image.open(file.file)
end
# Create different versions of your uploaded files:
version :large do
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
.row
.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 :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
= 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
%p.text-muted
Call for people to submit events to your conference
- if @cfp
.row
.col-md-8
%dl.dl-horizontal
%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
.row
.col-md-12.text-right
= link_to(edit_admin_conference_program_cfp_path(@conference.short_title), class: 'btn btn-primary') do
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
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'
.row
.col-md-8
%dl.dl-horizontal
= render "#{@cfp.cfp_type}_cfp"
.row
.col-md-12.text-right
= link_to(edit_admin_conference_program_cfp_path(@conference.short_title, @cfp.id), class: 'btn btn-primary') do
Edit
= 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

View file

@ -1,6 +1,23 @@
.text-center
%h4 #{title}
%canvas.doughnut_chart{ 'data-chart' => data.to_json }
%canvas.doughnut_chart{ id: "dough_#{title}", 'data-chart' => data.to_json }
- if data
- data.each do |key, 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}" }
%input{ 'type' => 'checkbox', 'name' => "#{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();
});

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