open_conference_ware 1.0.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.tar.gz.sig +0 -0
- data/.autotest +14 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +21 -0
- data/.rspec +1 -0
- data/.travis.yml +18 -0
- data/CHANGES.md +280 -0
- data/CONTRIBUTORS.markdown +17 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +296 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +23 -0
- data/README.markdown +173 -0
- data/Rakefile +27 -0
- data/TODO.txt +22 -0
- data/app/assets/images/open_conference_ware/accept.png +0 -0
- data/app/assets/images/open_conference_ware/arrow-left.png +0 -0
- data/app/assets/images/open_conference_ware/arrow-out.png +0 -0
- data/app/assets/images/open_conference_ware/arrow-right.png +0 -0
- data/app/assets/images/open_conference_ware/arrow-up.png +0 -0
- data/app/assets/images/open_conference_ware/arrow_refresh.png +0 -0
- data/app/assets/images/open_conference_ware/comment.png +0 -0
- data/app/assets/images/open_conference_ware/comment_edit.png +0 -0
- data/app/assets/images/open_conference_ware/delete.png +0 -0
- data/app/assets/images/open_conference_ware/disk.png +0 -0
- data/app/assets/images/open_conference_ware/document-blank.png +0 -0
- data/app/assets/images/open_conference_ware/document.png +0 -0
- data/app/assets/images/open_conference_ware/edit.png +0 -0
- data/app/assets/images/open_conference_ware/extras/001_01.png +0 -0
- data/app/assets/images/open_conference_ware/extras/001_05.png +0 -0
- data/app/assets/images/open_conference_ware/extras/001_05x16.png +0 -0
- data/app/assets/images/open_conference_ware/extras/001_21.png +0 -0
- data/app/assets/images/open_conference_ware/extras/001_23.png +0 -0
- data/app/assets/images/open_conference_ware/extras/001_24.png +0 -0
- data/app/assets/images/open_conference_ware/extras/001_29.png +0 -0
- data/app/assets/images/open_conference_ware/extras/01.png +0 -0
- data/app/assets/images/open_conference_ware/extras/05.png +0 -0
- data/app/assets/images/open_conference_ware/extras/06.png +0 -0
- data/app/assets/images/open_conference_ware/extras/1.png +0 -0
- data/app/assets/images/open_conference_ware/extras/14.png +0 -0
- data/app/assets/images/open_conference_ware/extras/16.png +0 -0
- data/app/assets/images/open_conference_ware/extras/2.png +0 -0
- data/app/assets/images/open_conference_ware/extras/21.png +0 -0
- data/app/assets/images/open_conference_ware/extras/28.png +0 -0
- data/app/assets/images/open_conference_ware/extras/3.png +0 -0
- data/app/assets/images/open_conference_ware/extras/31.png +0 -0
- data/app/assets/images/open_conference_ware/extras/32.png +0 -0
- data/app/assets/images/open_conference_ware/extras/35.png +0 -0
- data/app/assets/images/open_conference_ware/extras/36.png +0 -0
- data/app/assets/images/open_conference_ware/extras/4.png +0 -0
- data/app/assets/images/open_conference_ware/extras/44.png +0 -0
- data/app/assets/images/open_conference_ware/extras/45.png +0 -0
- data/app/assets/images/open_conference_ware/extras/46.png +0 -0
- data/app/assets/images/open_conference_ware/extras/cancel.png +0 -0
- data/app/assets/images/open_conference_ware/extras/cancelx16.png +0 -0
- data/app/assets/images/open_conference_ware/extras/icon.trashcan.gif +0 -0
- data/app/assets/images/open_conference_ware/favorite.png +0 -0
- data/app/assets/images/open_conference_ware/feed.png +0 -0
- data/app/assets/images/open_conference_ware/heart.png +0 -0
- data/app/assets/images/open_conference_ware/house.png +0 -0
- data/app/assets/images/open_conference_ware/ignite_portland_header3_clear.png +0 -0
- data/app/assets/images/open_conference_ware/ignite_portland_header3_clear_cropped.png +0 -0
- data/app/assets/images/open_conference_ware/jquery.wysiwyg.gif +0 -0
- data/app/assets/images/open_conference_ware/lock.png +0 -0
- data/app/assets/images/open_conference_ware/new_window.gif +0 -0
- data/app/assets/images/open_conference_ware/openid-icon.gif +0 -0
- data/app/assets/images/open_conference_ware/pencil.png +0 -0
- data/app/assets/images/open_conference_ware/plus.png +0 -0
- data/app/assets/images/open_conference_ware/quote.png +0 -0
- data/app/assets/images/open_conference_ware/rails.png +0 -0
- data/app/assets/images/open_conference_ware/redx.png +0 -0
- data/app/assets/images/open_conference_ware/reset-fff.png +0 -0
- data/app/assets/images/open_conference_ware/spinner-big.gif +0 -0
- data/app/assets/images/open_conference_ware/spinner.gif +0 -0
- data/app/assets/images/open_conference_ware/star.png +0 -0
- data/app/assets/images/open_conference_ware/tag_blue.png +0 -0
- data/app/assets/images/open_conference_ware/time.png +0 -0
- data/app/assets/javascripts/open_conference_ware/application.js +18 -0
- data/app/assets/javascripts/open_conference_ware/base.js +15 -0
- data/app/assets/javascripts/open_conference_ware/favorites.js +76 -0
- data/app/assets/javascripts/open_conference_ware/ie.js +2 -0
- data/app/assets/javascripts/open_conference_ware/menu.js +47 -0
- data/app/assets/javascripts/open_conference_ware/persona.js +10 -0
- data/app/assets/javascripts/open_conference_ware/proposals.js +208 -0
- data/app/assets/javascripts/open_conference_ware/schedule.js +20 -0
- data/app/assets/javascripts/open_conference_ware/spinner.js +11 -0
- data/app/assets/javascripts/open_conference_ware/util.js +23 -0
- data/app/assets/stylesheets/open_conference_ware/application.css +8 -0
- data/app/assets/stylesheets/open_conference_ware/common.css.scss +491 -0
- data/app/assets/stylesheets/open_conference_ware/custom.css.scss +1033 -0
- data/app/assets/stylesheets/open_conference_ware/scaffold.css +54 -0
- data/app/controllers/open_conference_ware/application_controller.rb +488 -0
- data/app/controllers/open_conference_ware/authentications_controller.rb +42 -0
- data/app/controllers/open_conference_ware/comments_controller.rb +86 -0
- data/app/controllers/open_conference_ware/events_controller.rb +36 -0
- data/app/controllers/open_conference_ware/manage/events_controller.rb +157 -0
- data/app/controllers/open_conference_ware/manage/snippets_controller.rb +110 -0
- data/app/controllers/open_conference_ware/proposals_controller.rb +521 -0
- data/app/controllers/open_conference_ware/rooms_controller.rb +130 -0
- data/app/controllers/open_conference_ware/schedule_items_controller.rb +128 -0
- data/app/controllers/open_conference_ware/selector_votes_controller.rb +71 -0
- data/app/controllers/open_conference_ware/session_types_controller.rb +123 -0
- data/app/controllers/open_conference_ware/tracks_controller.rb +118 -0
- data/app/controllers/open_conference_ware/user_favorites_controller.rb +68 -0
- data/app/controllers/open_conference_ware/users_controller.rb +120 -0
- data/app/helpers/open_conference_ware/application_helper.rb +167 -0
- data/app/helpers/open_conference_ware/authentications_helper.rb +25 -0
- data/app/helpers/open_conference_ware/cache_if_helper.rb +13 -0
- data/app/helpers/open_conference_ware/display_link_to_helper.rb +25 -0
- data/app/helpers/open_conference_ware/display_textile_for_helper.rb +11 -0
- data/app/helpers/open_conference_ware/field_annotation_helper.rb +11 -0
- data/app/helpers/open_conference_ware/focus_helper.rb +16 -0
- data/app/helpers/open_conference_ware/localcss_helper.rb +10 -0
- data/app/helpers/open_conference_ware/page_title_helper.rb +17 -0
- data/app/helpers/open_conference_ware/proposals_helper.rb +54 -0
- data/app/helpers/open_conference_ware/rooms_helper.rb +5 -0
- data/app/helpers/open_conference_ware/scroll_to_helper.rb +15 -0
- data/app/helpers/open_conference_ware/session_types_helper.rb +5 -0
- data/app/helpers/open_conference_ware/snippets_helper.rb +36 -0
- data/app/helpers/open_conference_ware/time_range_helper.rb +198 -0
- data/app/helpers/open_conference_ware/tracks_helper.rb +9 -0
- data/app/helpers/open_conference_ware/user_favorites_helper.rb +15 -0
- data/app/mailers/open_conference_ware/speaker_mailer.rb +51 -0
- data/app/mixins/open_conference_ware/breadcrumbs_mixin.rb +76 -0
- data/app/mixins/open_conference_ware/cache_lookups_mixin.rb +128 -0
- data/app/mixins/open_conference_ware/faux_routes_mixin.rb +83 -0
- data/app/mixins/open_conference_ware/normalize_url_mixin.rb +40 -0
- data/app/mixins/open_conference_ware/public_attributes_mixin.rb +62 -0
- data/app/mixins/open_conference_ware/schedule_overlaps_mixin.rb +12 -0
- data/app/mixins/open_conference_ware/settings_checkers_mixin.rb +50 -0
- data/app/mixins/open_conference_ware/simple_slug_mixin.rb +26 -0
- data/app/models/open_conference_ware/authentication.rb +47 -0
- data/app/models/open_conference_ware/base.rb +6 -0
- data/app/models/open_conference_ware/comment.rb +26 -0
- data/app/models/open_conference_ware/event.rb +227 -0
- data/app/models/open_conference_ware/proposal.rb +625 -0
- data/app/models/open_conference_ware/room.rb +42 -0
- data/app/models/open_conference_ware/schedule.rb +216 -0
- data/app/models/open_conference_ware/schedule_item.rb +55 -0
- data/app/models/open_conference_ware/selector_vote.rb +22 -0
- data/app/models/open_conference_ware/session_type.rb +33 -0
- data/app/models/open_conference_ware/snippet.rb +58 -0
- data/app/models/open_conference_ware/track.rb +51 -0
- data/app/models/open_conference_ware/user.rb +262 -0
- data/app/models/open_conference_ware/user_favorite.rb +42 -0
- data/app/observers/open_conference_ware/cache_watcher.rb +42 -0
- data/app/views/layouts/open_conference_ware/_header.html.erb +106 -0
- data/app/views/layouts/open_conference_ware/application.atom.erb +1 -0
- data/app/views/layouts/open_conference_ware/application.html.erb +126 -0
- data/app/views/layouts/open_conference_ware/scaffold.html.erb +21 -0
- data/app/views/open_conference_ware/404.html.erb +8 -0
- data/app/views/open_conference_ware/422.html.erb +11 -0
- data/app/views/open_conference_ware/500.html.erb +11 -0
- data/app/views/open_conference_ware/_email_link.html.erb +24 -0
- data/app/views/open_conference_ware/_google_analytics.html.erb +9 -0
- data/app/views/open_conference_ware/authentications/_developer.html.erb +7 -0
- data/app/views/open_conference_ware/authentications/_openid.html.erb +11 -0
- data/app/views/open_conference_ware/authentications/_persona.html.erb +17 -0
- data/app/views/open_conference_ware/authentications/sign_in.html.erb +12 -0
- data/app/views/open_conference_ware/comments/_list.html.erb +31 -0
- data/app/views/open_conference_ware/comments/index.atom.builder +25 -0
- data/app/views/open_conference_ware/comments/index.html.erb +3 -0
- data/app/views/open_conference_ware/events/index.html.erb +9 -0
- data/app/views/open_conference_ware/events/show.html.erb +0 -0
- data/app/views/open_conference_ware/events/speakers.html.erb +7 -0
- data/app/views/open_conference_ware/manage/events/_form.html.erb +107 -0
- data/app/views/open_conference_ware/manage/events/edit.html.erb +3 -0
- data/app/views/open_conference_ware/manage/events/index.html.erb +41 -0
- data/app/views/open_conference_ware/manage/events/new.html.erb +3 -0
- data/app/views/open_conference_ware/manage/events/proposals.html.erb +133 -0
- data/app/views/open_conference_ware/manage/events/show.html.erb +181 -0
- data/app/views/open_conference_ware/manage/snippets/_form.html.erb +30 -0
- data/app/views/open_conference_ware/manage/snippets/edit.html.erb +3 -0
- data/app/views/open_conference_ware/manage/snippets/index.html.erb +34 -0
- data/app/views/open_conference_ware/manage/snippets/new.html.erb +3 -0
- data/app/views/open_conference_ware/manage/snippets/show.html.erb +32 -0
- data/app/views/open_conference_ware/proposals/_admin_controls.html.erb +33 -0
- data/app/views/open_conference_ware/proposals/_form.html.erb +381 -0
- data/app/views/open_conference_ware/proposals/_list.html.erb +164 -0
- data/app/views/open_conference_ware/proposals/_list_concise.html.erb +93 -0
- data/app/views/open_conference_ware/proposals/_manage_speakers.html.erb +30 -0
- data/app/views/open_conference_ware/proposals/_room_control.html.erb +21 -0
- data/app/views/open_conference_ware/proposals/_schedule_block.html.erb +8 -0
- data/app/views/open_conference_ware/proposals/_schedule_control.html.erb +13 -0
- data/app/views/open_conference_ware/proposals/_search_speakers.html.erb +20 -0
- data/app/views/open_conference_ware/proposals/_sub_list.html.erb +65 -0
- data/app/views/open_conference_ware/proposals/_sub_list_for_kind.html.erb +31 -0
- data/app/views/open_conference_ware/proposals/_transition_control.html.erb +16 -0
- data/app/views/open_conference_ware/proposals/create.html.erb +29 -0
- data/app/views/open_conference_ware/proposals/edit.html.erb +7 -0
- data/app/views/open_conference_ware/proposals/index.atom.builder +63 -0
- data/app/views/open_conference_ware/proposals/index.html.erb +46 -0
- data/app/views/open_conference_ware/proposals/new.html.erb +3 -0
- data/app/views/open_conference_ware/proposals/schedule.html.erb +108 -0
- data/app/views/open_conference_ware/proposals/sessions_index_terse.html.erb +22 -0
- data/app/views/open_conference_ware/proposals/show.html.erb +316 -0
- data/app/views/open_conference_ware/proposals/stats.html.erb +72 -0
- data/app/views/open_conference_ware/rooms/_form.html.erb +36 -0
- data/app/views/open_conference_ware/rooms/edit.html.erb +3 -0
- data/app/views/open_conference_ware/rooms/index.html.erb +13 -0
- data/app/views/open_conference_ware/rooms/new.html.erb +3 -0
- data/app/views/open_conference_ware/rooms/show.html.erb +27 -0
- data/app/views/open_conference_ware/schedule_items/_form.html.erb +35 -0
- data/app/views/open_conference_ware/schedule_items/edit.html.erb +3 -0
- data/app/views/open_conference_ware/schedule_items/index.html.erb +31 -0
- data/app/views/open_conference_ware/schedule_items/new.html.erb +3 -0
- data/app/views/open_conference_ware/schedule_items/show.html.erb +30 -0
- data/app/views/open_conference_ware/selector_votes/index.html.erb +81 -0
- data/app/views/open_conference_ware/session_types/_form.html.erb +23 -0
- data/app/views/open_conference_ware/session_types/edit.html.erb +3 -0
- data/app/views/open_conference_ware/session_types/index.html.erb +18 -0
- data/app/views/open_conference_ware/session_types/new.html.erb +3 -0
- data/app/views/open_conference_ware/session_types/show.html.erb +14 -0
- data/app/views/open_conference_ware/speaker_mailer/speaker_email.text.erb +14 -0
- data/app/views/open_conference_ware/tracks/_colors.css.erb +7 -0
- data/app/views/open_conference_ware/tracks/_form.html.erb +36 -0
- data/app/views/open_conference_ware/tracks/edit.html.erb +3 -0
- data/app/views/open_conference_ware/tracks/index.html.erb +25 -0
- data/app/views/open_conference_ware/tracks/new.html.erb +3 -0
- data/app/views/open_conference_ware/tracks/show.html.erb +19 -0
- data/app/views/open_conference_ware/user_favorites/index.html.erb +12 -0
- data/app/views/open_conference_ware/users/_account_box.html.erb +10 -0
- data/app/views/open_conference_ware/users/_block.html.erb +80 -0
- data/app/views/open_conference_ware/users/_list.html.erb +17 -0
- data/app/views/open_conference_ware/users/edit.html.erb +73 -0
- data/app/views/open_conference_ware/users/index.html.erb +28 -0
- data/app/views/open_conference_ware/users/new.html.erb +7 -0
- data/app/views/open_conference_ware/users/proposals.html.erb +24 -0
- data/app/views/open_conference_ware/users/show.html.erb +5 -0
- data/app/views/open_conference_ware/welcome/test.html.haml +8 -0
- data/bin/rails +8 -0
- data/ci/Gemfile.ci +13 -0
- data/ci/copy_database_config.rb +6 -0
- data/ci/database.mysql.yml +5 -0
- data/ci/database.postgresql.yml +4 -0
- data/ci/database.sqlite.yml +5 -0
- data/config/initializers/acts_as_taggable_on.rb +2 -0
- data/config/initializers/date_time_formats.rb +9 -0
- data/config/routes.rb +74 -0
- data/db/migrate/20131203235128_create_open_conference_ware_authentications.rb +14 -0
- data/db/migrate/20131203235216_create_open_conference_ware_comments.rb +14 -0
- data/db/migrate/20131203235418_create_open_conference_ware_events.rb +27 -0
- data/db/migrate/20131203235524_create_open_conference_ware_proposals.rb +36 -0
- data/db/migrate/20131203235723_create_open_conference_ware_rooms.rb +20 -0
- data/db/migrate/20131203235831_create_open_conference_ware_schedule_items.rb +18 -0
- data/db/migrate/20131203235910_create_open_conference_ware_selector_votes.rb +10 -0
- data/db/migrate/20131203235934_create_open_conference_ware_session_types.rb +14 -0
- data/db/migrate/20131204000008_create_open_conference_ware_snippets.rb +15 -0
- data/db/migrate/20131204000048_create_open_conference_ware_taggings.rb +16 -0
- data/db/migrate/20131204000129_create_open_conference_ware_tags.rb +7 -0
- data/db/migrate/20131204000147_create_open_conference_ware_tracks.rb +15 -0
- data/db/migrate/20131204000230_create_open_conference_ware_user_favorites.rb +10 -0
- data/db/migrate/20131204000251_create_open_conference_ware_users.rb +24 -0
- data/db/migrate/20131204000355_create_proposals_users_join_table.rb +8 -0
- data/db/seeds.rb +31 -0
- data/features/comment_create.feature +9 -0
- data/features/comment_destroy.feature +22 -0
- data/features/comment_new.feature +22 -0
- data/features/step_definitions/authentication_steps.rb +22 -0
- data/features/step_definitions/comment_create_steps.rb +5 -0
- data/features/step_definitions/comment_destroy_steps.rb +13 -0
- data/features/step_definitions/comment_new_steps.rb +41 -0
- data/features/step_definitions/notification_steps.rb +3 -0
- data/features/step_definitions/web_steps.rb +263 -0
- data/features/support/env.rb +58 -0
- data/features/support/helpers.rb +17 -0
- data/features/support/paths.rb +35 -0
- data/gem-public_cert.pem +22 -0
- data/lib/defer_proxy.rb +127 -0
- data/lib/ext/color_rgb_serializer_fix.rb +18 -0
- data/lib/ext/string_possessiveize.rb +7 -0
- data/lib/ext/vpim_icalendar_extra_properties.rb +25 -0
- data/lib/generators/open_conference_ware/install/USAGE +12 -0
- data/lib/generators/open_conference_ware/install/install_generator.rb +35 -0
- data/lib/generators/open_conference_ware/install/templates/config_initializer.rb +110 -0
- data/lib/generators/open_conference_ware/install/templates/omniauth_initializer.rb +33 -0
- data/lib/open_conference_ware.rb +156 -0
- data/lib/open_conference_ware/dependencies.rb +38 -0
- data/lib/open_conference_ware/engine.rb +38 -0
- data/lib/open_conference_ware/omni_auth_builder.rb +8 -0
- data/lib/open_conference_ware/version.rb +3 -0
- data/lib/rwikibot_page_drone.rb +70 -0
- data/lib/tasks/.gitkeep +0 -0
- data/lib/tasks/audio.rake +53 -0
- data/lib/tasks/cucumber.rake +65 -0
- data/lib/tasks/export.rake +170 -0
- data/lib/tasks/mediawiki.rake +96 -0
- data/lib/tasks/open_conference_ware_tasks.rake +16 -0
- data/lib/tasks/schedule.rake +103 -0
- data/lib/tasks/snippets.rake +23 -0
- data/open_conference_ware.gemspec +67 -0
- data/original_graphics/favorite.psd +0 -0
- data/spec/controllers/open_conference_ware/application_controller_spec.rb +309 -0
- data/spec/controllers/open_conference_ware/authentications_controller_spec.rb +87 -0
- data/spec/controllers/open_conference_ware/comments_controller_spec.rb +146 -0
- data/spec/controllers/open_conference_ware/events_controller_spec.rb +102 -0
- data/spec/controllers/open_conference_ware/manage_events_controller_spec.rb +103 -0
- data/spec/controllers/open_conference_ware/proposals_controller_spec.rb +1273 -0
- data/spec/controllers/open_conference_ware/rooms_controller_spec.rb +190 -0
- data/spec/controllers/open_conference_ware/selector_votes_controller_spec.rb +263 -0
- data/spec/controllers/open_conference_ware/session_types_controller_spec.rb +190 -0
- data/spec/controllers/open_conference_ware/sessions_routing_spec.rb +13 -0
- data/spec/controllers/open_conference_ware/tracks_controller_spec.rb +190 -0
- data/spec/controllers/open_conference_ware/user_favorites_controller_spec.rb +127 -0
- data/spec/controllers/open_conference_ware/users_controller_spec.rb +166 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/01_open_conference_ware.rb +110 -0
- data/spec/dummy/config/initializers/02_omniauth.rb +17 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/schema.rb +219 -0
- data/spec/dummy/db/seeds.rb +10 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/authentication_factory.rb +16 -0
- data/spec/factories/common_factory.rb +4 -0
- data/spec/factories/event_factory.rb +26 -0
- data/spec/factories/proposal_factory.rb +57 -0
- data/spec/factories/proposal_user_factory.rb +7 -0
- data/spec/factories/room_factory.rb +11 -0
- data/spec/factories/schedule_item_factory.rb +13 -0
- data/spec/factories/session_types_factory.rb +10 -0
- data/spec/factories/snippet_factory.rb +7 -0
- data/spec/factories/track_factory.rb +12 -0
- data/spec/factories/user_factory.rb +22 -0
- data/spec/factories/user_favorite_factory.rb +7 -0
- data/spec/features/sign_in_spec.rb +11 -0
- data/spec/features/snippets_spec.rb +38 -0
- data/spec/fixtures/open_conference_ware_comments.yml +27 -0
- data/spec/fixtures/open_conference_ware_events.yml +56 -0
- data/spec/fixtures/open_conference_ware_proposals.yml +256 -0
- data/spec/fixtures/open_conference_ware_rooms.yml +13 -0
- data/spec/fixtures/open_conference_ware_schedule_items.yml +23 -0
- data/spec/fixtures/open_conference_ware_session_types.yml +19 -0
- data/spec/fixtures/open_conference_ware_snippets.yml +62 -0
- data/spec/fixtures/open_conference_ware_tracks.yml +65 -0
- data/spec/fixtures/open_conference_ware_user_favorites.yml +7 -0
- data/spec/fixtures/open_conference_ware_users.yml +87 -0
- data/spec/helpers/open_conference_ware/application_helper_spec.rb +58 -0
- data/spec/helpers/open_conference_ware/authentications_helper_spec.rb +15 -0
- data/spec/helpers/open_conference_ware/display_link_to_helper_spec.rb +52 -0
- data/spec/helpers/open_conference_ware/proposals_helper_spec.rb +32 -0
- data/spec/helpers/open_conference_ware/rooms_helper_spec.rb +11 -0
- data/spec/helpers/open_conference_ware/session_types_helper_spec.rb +15 -0
- data/spec/helpers/open_conference_ware/time_range_helper_spec.rb +56 -0
- data/spec/helpers/open_conference_ware/tracks_helper_spec.rb +11 -0
- data/spec/helpers/open_conference_ware/user_favorites_helper_spec.rb +11 -0
- data/spec/integration/open_conference_ware/cache_lookups_mixin_spec.rb +122 -0
- data/spec/mixins/open_conference_ware/normalize_url_mixin_spec.rb +65 -0
- data/spec/models/open_conference_ware/authentication_spec.rb +67 -0
- data/spec/models/open_conference_ware/event_spec.rb +211 -0
- data/spec/models/open_conference_ware/proposal_spec.rb +606 -0
- data/spec/models/open_conference_ware/room_spec.rb +14 -0
- data/spec/models/open_conference_ware/schedule_item_spec.rb +12 -0
- data/spec/models/open_conference_ware/schedule_spec.rb +358 -0
- data/spec/models/open_conference_ware/selector_vote_spec.rb +12 -0
- data/spec/models/open_conference_ware/session_type_spec.rb +15 -0
- data/spec/models/open_conference_ware/snippet_spec.rb +13 -0
- data/spec/models/open_conference_ware/speaker_mailer_spec.rb +91 -0
- data/spec/models/open_conference_ware/track_spec.rb +36 -0
- data/spec/models/open_conference_ware/user_favorite_spec.rb +41 -0
- data/spec/models/open_conference_ware/user_spec.rb +77 -0
- data/spec/ocw_config.rb +76 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/spec_helper_customizations.rb +125 -0
- data/spec/support/add_all_helpers_to_view.rb +24 -0
- data/spec/support/authenticated_test_helper.rb +27 -0
- data/spec/support/fixture_shortcuts.rb +18 -0
- data/spec/support/omniauth.rb +34 -0
- data/spec/support/valid_params_extraction.rb +15 -0
- data/spec/views/open_conference_ware/proposals/_room_control.html.erb_spec.rb +23 -0
- data/spec/views/open_conference_ware/proposals/_transition_control.html.erb_spec.rb +14 -0
- data/spec/views/open_conference_ware/proposals/show.html.erb_spec.rb +128 -0
- data/spec/views/open_conference_ware/rooms/index.html.erb_spec.rb +19 -0
- data/spec/views/open_conference_ware/rooms/show.html.erb_spec.rb +15 -0
- data/spec/views/open_conference_ware/selector_votes/index.html.erb_spec.rb +41 -0
- data/spec/views/open_conference_ware/session_types/edit.html.erb_spec.rb +25 -0
- data/spec/views/open_conference_ware/session_types/index.html.erb_spec.rb +19 -0
- data/spec/views/open_conference_ware/session_types/new.html.erb_spec.rb +24 -0
- data/spec/views/open_conference_ware/session_types/show.html.erb_spec.rb +18 -0
- data/spec/views/open_conference_ware/tracks/edit.html.erb_spec.rb +25 -0
- data/spec/views/open_conference_ware/tracks/index.html.erb_spec.rb +56 -0
- data/spec/views/open_conference_ware/tracks/new.html.erb_spec.rb +26 -0
- data/spec/views/open_conference_ware/tracks/show.html.erb_spec.rb +23 -0
- data/spec/views/open_conference_ware/user_favorites/index.html.erb_spec.rb +29 -0
- data/spec/views/open_conference_ware/users/index_spec.rb +24 -0
- data/util/add_id3_tags_to_mp3.rb +57 -0
- data/util/schedule_demo.rb +36 -0
- data/util/seed_schedule.rb +61 -0
- data/util/sessions_to_lanyrd.rb +166 -0
- data/util/transfer_schedule_items.rb +25 -0
- data/util/update_proposal_status_from_voting_spreadsheet.rb +26 -0
- data/util/user_favorites_contention_report.rb +42 -0
- data/util/user_favorites_contention_report.sh +9 -0
- data/vendor/assets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/vendor/assets/fonts/glyphicons-halflings-regular.svg +228 -0
- data/vendor/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/vendor/assets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/vendor/assets/images/farbtastic/marker.png +0 -0
- data/vendor/assets/images/farbtastic/mask.png +0 -0
- data/vendor/assets/images/farbtastic/wheel.png +0 -0
- data/vendor/assets/images/idselector/aol.ico +0 -0
- data/vendor/assets/images/idselector/arrow.gif +0 -0
- data/vendor/assets/images/idselector/arrow_white_back.png +0 -0
- data/vendor/assets/images/idselector/arrow_white_forward.png +0 -0
- data/vendor/assets/images/idselector/blogger.ico +0 -0
- data/vendor/assets/images/idselector/claimid.ico +0 -0
- data/vendor/assets/images/idselector/flickr.ico +0 -0
- data/vendor/assets/images/idselector/google.ico +0 -0
- data/vendor/assets/images/idselector/lj.ico +0 -0
- data/vendor/assets/images/idselector/myopenid.ico +0 -0
- data/vendor/assets/images/idselector/openid.ico +0 -0
- data/vendor/assets/images/idselector/technorati.ico +0 -0
- data/vendor/assets/images/idselector/verisign.ico +0 -0
- data/vendor/assets/images/idselector/vidoop2.ico +0 -0
- data/vendor/assets/images/idselector/vox.ico +0 -0
- data/vendor/assets/images/idselector/yahoo.ico +0 -0
- data/vendor/assets/javascripts/audiojs/audio.min.js +23 -0
- data/vendor/assets/javascripts/audiojs/audiojs.swf +0 -0
- data/vendor/assets/javascripts/audiojs/player-graphics.gif +0 -0
- data/vendor/assets/javascripts/bootstrap.js +1999 -0
- data/vendor/assets/javascripts/farbtastic.js +329 -0
- data/vendor/assets/javascripts/html5shiv.js +8 -0
- data/vendor/assets/javascripts/idselector.js +538 -0
- data/vendor/assets/javascripts/idselector~original.js +532 -0
- data/vendor/assets/javascripts/jquery-migrate-1.2.1.js +521 -0
- data/vendor/assets/javascripts/jquery.localScroll.js +106 -0
- data/vendor/assets/javascripts/jquery.scrollTo.js +174 -0
- data/vendor/assets/javascripts/respond.min.js +6 -0
- data/vendor/assets/stylesheets/bootstrap-theme.css +384 -0
- data/vendor/assets/stylesheets/bootstrap.css +6805 -0
- data/vendor/assets/stylesheets/farbtastic.css.scss +38 -0
- data/vendor/assets/stylesheets/persona-buttons.css +229 -0
- data/vendor/assets/stylesheets/reset-fonts-grids.css +7 -0
- metadata +1174 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module OpenConferenceWare
|
|
2
|
+
module ScheduleOverlapsMixin
|
|
3
|
+
def overlaps?(object)
|
|
4
|
+
raise ArgumentError unless object.respond_to?(:start_time) && object.respond_to?(:end_time)
|
|
5
|
+
if self.start_time != self.end_time && object.start_time != object.end_time
|
|
6
|
+
(self.start_time.to_i..self.end_time.to_i).overlaps?(object.start_time.to_i..object.end_time.to_i) && (self.end_time != object.start_time && self.start_time != object.end_time)
|
|
7
|
+
else
|
|
8
|
+
self.start_time == object.start_time
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module OpenConferenceWare
|
|
2
|
+
|
|
3
|
+
# = SettingsCheckersMixin
|
|
4
|
+
#
|
|
5
|
+
# The SettingsCheckersMixin provides methods for checking the status of the
|
|
6
|
+
# settings, setup via the SettingsReader. These let you write code like
|
|
7
|
+
# +#user_profiles?+ rather than +SETTINGS.has_user_profiles+.
|
|
8
|
+
module SettingsCheckersMixin
|
|
9
|
+
|
|
10
|
+
def self.included(mixee)
|
|
11
|
+
mixee.extend(Methods)
|
|
12
|
+
|
|
13
|
+
# Add view helper methods to controllers
|
|
14
|
+
if mixee.ancestors.include?(ActionController::Base)
|
|
15
|
+
mixee.class_eval do
|
|
16
|
+
Methods.instance_methods.each do |name|
|
|
17
|
+
helper_method(name)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Methods
|
|
24
|
+
# Create methods like +#event_tracks?+ as wrappers for +OpenConferenceWare.have_event_tracks+.
|
|
25
|
+
%w[
|
|
26
|
+
anonymous_proposals
|
|
27
|
+
event_proposal_comments_after_deadline
|
|
28
|
+
event_tracks
|
|
29
|
+
event_session_types
|
|
30
|
+
events_picker
|
|
31
|
+
event_rooms
|
|
32
|
+
multiple_presenters
|
|
33
|
+
proposal_excerpts
|
|
34
|
+
proposal_speaking_experience
|
|
35
|
+
proposal_start_times
|
|
36
|
+
proposal_statuses
|
|
37
|
+
user_pictures
|
|
38
|
+
user_profiles
|
|
39
|
+
].each do |name|
|
|
40
|
+
define_method("#{name}?") do
|
|
41
|
+
return OpenConferenceWare.send("have_#{name}")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
include Methods
|
|
47
|
+
extend Methods
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module OpenConferenceWare
|
|
2
|
+
module SimpleSlugMixin
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.extend ClassMethods
|
|
5
|
+
base.class_eval do
|
|
6
|
+
class << self
|
|
7
|
+
alias_method_chain :find, :slug
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_param
|
|
13
|
+
slug
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def find_with_slug(id, options = {})
|
|
18
|
+
if id.is_a?(Symbol) || id.to_s =~ /\A\d+\Z/
|
|
19
|
+
find_without_slug(id, options)
|
|
20
|
+
else
|
|
21
|
+
find_by_slug(id, options)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module OpenConferenceWare
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: authentications
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# user_id :integer
|
|
9
|
+
# provider :string(255)
|
|
10
|
+
# uid :string(255)
|
|
11
|
+
# name :string(255)
|
|
12
|
+
# email :string(255)
|
|
13
|
+
# info :text
|
|
14
|
+
# created_at :datetime not null
|
|
15
|
+
# updated_at :datetime not null
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
class Authentication < OpenConferenceWare::Base
|
|
19
|
+
belongs_to :user
|
|
20
|
+
serialize :info, JSON
|
|
21
|
+
|
|
22
|
+
after_initialize do |auth|
|
|
23
|
+
auth.info ||= {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.find_and_update_or_create_from_auth_hash(auth_hash)
|
|
27
|
+
auth = find_or_initialize_by(
|
|
28
|
+
provider: auth_hash.provider,
|
|
29
|
+
uid: auth_hash.uid
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
auth.name = auth_hash.info.name
|
|
33
|
+
auth.email = auth_hash.info.email
|
|
34
|
+
auth.info = auth_hash.info
|
|
35
|
+
|
|
36
|
+
auth.save && auth
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def has_first_and_last_name?
|
|
40
|
+
!!(info['first_name'] && info['last_name'])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def first_url
|
|
44
|
+
info['urls'].values.first if info['urls'].is_a?(Hash)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module OpenConferenceWare
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: comments
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# name :string(255)
|
|
9
|
+
# email :string(255)
|
|
10
|
+
# message :text
|
|
11
|
+
# proposal_id :integer
|
|
12
|
+
# created_at :datetime
|
|
13
|
+
# updated_at :datetime
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
class Comment < OpenConferenceWare::Base
|
|
17
|
+
belongs_to :proposal
|
|
18
|
+
|
|
19
|
+
validates_presence_of :email, :message, :proposal_id
|
|
20
|
+
|
|
21
|
+
# Make sure there's a legitimate proposal attached?!
|
|
22
|
+
validates_presence_of :proposal, message: "must be present"
|
|
23
|
+
|
|
24
|
+
scope :listable, lambda { order("created_at desc").where("created_at IS NOT NULL") }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
module OpenConferenceWare
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: events
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# title :string(255)
|
|
9
|
+
# deadline :datetime
|
|
10
|
+
# open_text :text
|
|
11
|
+
# closed_text :text
|
|
12
|
+
# created_at :datetime
|
|
13
|
+
# updated_at :datetime
|
|
14
|
+
# proposal_status_published :boolean default(FALSE), not null
|
|
15
|
+
# session_text :text
|
|
16
|
+
# tracks_text :text
|
|
17
|
+
# start_date :datetime
|
|
18
|
+
# end_date :datetime
|
|
19
|
+
# accept_proposal_comments_after_deadline :boolean default(FALSE)
|
|
20
|
+
# slug :string(255)
|
|
21
|
+
# schedule_published :boolean default(FALSE)
|
|
22
|
+
# parent_id :integer
|
|
23
|
+
# proposal_titles_locked :boolean default(FALSE)
|
|
24
|
+
# accept_selector_votes :boolean default(FALSE)
|
|
25
|
+
# show_proposal_confirmation_controls :boolean default(FALSE)
|
|
26
|
+
#
|
|
27
|
+
|
|
28
|
+
class Event < OpenConferenceWare::Base
|
|
29
|
+
# Mixins
|
|
30
|
+
### Provide cached Snippet.lookup(id) method.
|
|
31
|
+
include CacheLookupsMixin
|
|
32
|
+
include SimpleSlugMixin
|
|
33
|
+
|
|
34
|
+
cache_lookups_for :slug, order: 'deadline desc', include: [:tracks, :rooms]
|
|
35
|
+
|
|
36
|
+
# Associations
|
|
37
|
+
has_many :proposals, dependent: :destroy
|
|
38
|
+
has_many :tracks, dependent: :destroy
|
|
39
|
+
has_many :session_types, dependent: :destroy
|
|
40
|
+
has_many :rooms, dependent: :destroy
|
|
41
|
+
has_many :schedule_items, dependent: :destroy
|
|
42
|
+
has_many :children, class_name: 'Event', foreign_key: 'parent_id', dependent: :destroy
|
|
43
|
+
belongs_to :parent, class_name: 'Event', foreign_key: 'parent_id'
|
|
44
|
+
has_many :selector_votes, through: :proposals, dependent: :destroy
|
|
45
|
+
|
|
46
|
+
# Validations
|
|
47
|
+
validates_presence_of \
|
|
48
|
+
:slug,
|
|
49
|
+
:title,
|
|
50
|
+
:deadline,
|
|
51
|
+
:open_text,
|
|
52
|
+
:closed_text
|
|
53
|
+
validates_uniqueness_of :slug
|
|
54
|
+
validates_uniqueness_of :title
|
|
55
|
+
|
|
56
|
+
# Is this event accepting proposals?
|
|
57
|
+
def accepting_proposals?
|
|
58
|
+
return Time.now < (self.deadline || Time.at(0))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns an array of the dates when this event is happening.
|
|
62
|
+
def dates
|
|
63
|
+
if self.start_date.nil? || self.end_date.nil?
|
|
64
|
+
return []
|
|
65
|
+
else
|
|
66
|
+
return Array(self.start_date.to_date .. self.end_date.to_date)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Formats this event's dates for use in a select form control.
|
|
71
|
+
def dates_for_select
|
|
72
|
+
return [['','']] + self.dates.map{|date| [date.strftime("%B %d, %Y"), date.strftime("%Y-%m-%d")]}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Determines if the event is currently taking place.
|
|
76
|
+
def underway?
|
|
77
|
+
self.start_date && self.end_date && Time.zone.now.between?(self.start_date, self.end_date)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Is this the current event?
|
|
81
|
+
def current?
|
|
82
|
+
return self == Event.current
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Return the current Event. Determines which event to return by checking to
|
|
86
|
+
# see if a snippet says which is current, else tries to return the event
|
|
87
|
+
# with the latest deadline, else returns a nil.
|
|
88
|
+
def self.current
|
|
89
|
+
query = lambda { self.current_by_settings || self.current_by_deadline }
|
|
90
|
+
return self.cache_lookups? ?
|
|
91
|
+
self.fetch_object('event_current', &query) :
|
|
92
|
+
query.call
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Return current event by finding it by deadline.
|
|
96
|
+
def self.current_by_deadline
|
|
97
|
+
return Event.order('deadline desc').first
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Return current event by finding it through OpenConferenceWare global.
|
|
101
|
+
# TODO Get the current event from an attribute in the Site object.
|
|
102
|
+
def self.current_by_settings
|
|
103
|
+
if slug = OpenConferenceWare.current_event_slug
|
|
104
|
+
begin
|
|
105
|
+
return Event.find_by_slug(slug)
|
|
106
|
+
rescue ActiveRecord::RecordNotFound
|
|
107
|
+
return nil
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
return nil
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Return an array of this Event's Proposals with their Tracks for use by proposals#stats.
|
|
115
|
+
def proposals_for_stats
|
|
116
|
+
return self.proposals.
|
|
117
|
+
order("created_at").
|
|
118
|
+
where("proposals.id, proposals.track_id, proposals.created_at, proposals.submitted_at, proposals.session_type_id, proposals.status").
|
|
119
|
+
includes(:track, :session_type).
|
|
120
|
+
all
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Return an array of the Event's ScheduleItems and Proposal sessions that
|
|
124
|
+
# have been scheduled and given a room location. Optionally specify +is_admin+
|
|
125
|
+
# to display schedule if it's not been published yet.
|
|
126
|
+
def calendar_items(is_admin=false)
|
|
127
|
+
results = []
|
|
128
|
+
if self.schedule_published? || is_admin
|
|
129
|
+
results += self.proposals.confirmed.scheduled.includes(:users, :room, :session_type, {track: :event})
|
|
130
|
+
if is_admin
|
|
131
|
+
results += self.proposals.accepted.scheduled.includes(:users, :room, :session_type, {track: :event})
|
|
132
|
+
end
|
|
133
|
+
elsif is_admin
|
|
134
|
+
end
|
|
135
|
+
results += self.schedule_items.includes(:room)
|
|
136
|
+
results += (self.children.map{|child| child.calendar_items(is_admin)}.flatten)
|
|
137
|
+
return results
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Return list of people that submitted to this event.
|
|
141
|
+
def users
|
|
142
|
+
return User.submitted_to(self)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Return list of speakers for this event.
|
|
146
|
+
def speakers
|
|
147
|
+
return User.speaking_at(self)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Return records for this event that are of the given +kind+ (e.g.,
|
|
151
|
+
# :proposals or :sessions).
|
|
152
|
+
def populated_proposals(kind=:proposals)
|
|
153
|
+
case kind
|
|
154
|
+
when :proposals
|
|
155
|
+
return self.proposals.populated
|
|
156
|
+
when :sessions
|
|
157
|
+
return self.proposals.populated.confirmed
|
|
158
|
+
else
|
|
159
|
+
raise ArgumentError, "Unknown kind: #{kind}"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Return other Event objects.
|
|
164
|
+
def other_events
|
|
165
|
+
return self.class.select("id, title").order("title asc").where('id != ?', self.id)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Return array of Rooms for this event and its parent event.
|
|
169
|
+
def rooms_inherit
|
|
170
|
+
return [self.parent.try(:rooms), self.rooms].flatten.compact.sort_by(&:name)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Return array of Tracks for this event, its parent, and its siblings.
|
|
174
|
+
def tracks_combined
|
|
175
|
+
return [self.tracks_descend, self.parent.try(:tracks_descend)].flatten.compact.uniq.sort_by(&:title)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Return array of Tracks for this event and its children.
|
|
179
|
+
def tracks_descend
|
|
180
|
+
return (self.tracks + self.children.map(&:tracks)).flatten.uniq.sort_by(&:title)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Return start_time for either self or parent Event.
|
|
184
|
+
def start_date
|
|
185
|
+
return self.parent_id ? self.parent.start_date : self.read_attribute(:start_date)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Return end_time for either self or parent Event.
|
|
189
|
+
def end_date
|
|
190
|
+
return self.parent_id ? self.parent.end_date : self.read_attribute(:end_date)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Return the parent event or this Event.
|
|
194
|
+
def parent_or_self
|
|
195
|
+
return self.parent_id ?
|
|
196
|
+
self.parent :
|
|
197
|
+
self
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Return all of this event's children and its children's children all the way down.
|
|
201
|
+
def descendents
|
|
202
|
+
return [self.children, self.children.map(&:descendents)].flatten.uniq
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Return the parent and all it's descendants
|
|
206
|
+
def family
|
|
207
|
+
return [self.parent_or_self, self.parent_or_self.descendents].flatten.uniq
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Return proposals that are related to this event, it's children or its parent.
|
|
211
|
+
def related_proposals(some_proposals)
|
|
212
|
+
[].tap do |related|
|
|
213
|
+
parent = self.parent_or_self
|
|
214
|
+
for proposal in some_proposals
|
|
215
|
+
catch :found do
|
|
216
|
+
for an_event in self.family
|
|
217
|
+
if proposal.event_id == an_event.id
|
|
218
|
+
related << proposal
|
|
219
|
+
throw :found
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
module OpenConferenceWare
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: proposals
|
|
6
|
+
#
|
|
7
|
+
# id :integer not null, primary key
|
|
8
|
+
# user_id :integer
|
|
9
|
+
# presenter :string(255)
|
|
10
|
+
# affiliation :string(255)
|
|
11
|
+
# email :string(255)
|
|
12
|
+
# website :string(255)
|
|
13
|
+
# biography :text
|
|
14
|
+
# title :string(255)
|
|
15
|
+
# description :text
|
|
16
|
+
# agreement :boolean default(TRUE)
|
|
17
|
+
# created_at :datetime
|
|
18
|
+
# updated_at :datetime
|
|
19
|
+
# event_id :integer
|
|
20
|
+
# submitted_at :datetime
|
|
21
|
+
# note_to_organizers :text
|
|
22
|
+
# excerpt :text
|
|
23
|
+
# track_id :integer
|
|
24
|
+
# session_type_id :integer
|
|
25
|
+
# status :string(255) default("proposed"), not null
|
|
26
|
+
# room_id :integer
|
|
27
|
+
# start_time :datetime
|
|
28
|
+
# audio_url :string(255)
|
|
29
|
+
# speaking_experience :text
|
|
30
|
+
# audience_level :string(255)
|
|
31
|
+
# notified_at :datetime
|
|
32
|
+
#
|
|
33
|
+
|
|
34
|
+
class Proposal < OpenConferenceWare::Base
|
|
35
|
+
# Provide ::validate_url_attribute
|
|
36
|
+
include NormalizeUrlMixin
|
|
37
|
+
|
|
38
|
+
# Provide ::event_tracks? and other methods for accessing SETTING
|
|
39
|
+
include SettingsCheckersMixin
|
|
40
|
+
|
|
41
|
+
# Provide ::lookup
|
|
42
|
+
include CacheLookupsMixin
|
|
43
|
+
|
|
44
|
+
# Provide ::overlaps?
|
|
45
|
+
include ScheduleOverlapsMixin
|
|
46
|
+
|
|
47
|
+
# Public attributes for export
|
|
48
|
+
include PublicAttributesMixin
|
|
49
|
+
set_public_attributes :id, :user_id,
|
|
50
|
+
:presenter, :affiliation, :website,
|
|
51
|
+
:biography, :title, :description,
|
|
52
|
+
:created_at, :updated_at, :submitted_at,
|
|
53
|
+
:start_time, :end_time,
|
|
54
|
+
:event_id, :event_title,
|
|
55
|
+
:room_id, :room_title,
|
|
56
|
+
:session_type_id, :session_type_title,
|
|
57
|
+
:track_id, :track_title,
|
|
58
|
+
:user_ids, :user_titles
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
cache_lookups_for :id, order: 'submitted_at desc', include: [:event, :track, :room, :users]
|
|
62
|
+
|
|
63
|
+
# Provide #tags
|
|
64
|
+
acts_as_taggable_on :tags
|
|
65
|
+
|
|
66
|
+
# Acts As State Machine
|
|
67
|
+
include AASM
|
|
68
|
+
|
|
69
|
+
aasm_column :status
|
|
70
|
+
|
|
71
|
+
aasm_initial_state :proposed
|
|
72
|
+
|
|
73
|
+
aasm_state :proposed
|
|
74
|
+
aasm_state :accepted
|
|
75
|
+
aasm_state :waitlisted
|
|
76
|
+
aasm_state :rejected
|
|
77
|
+
aasm_state :confirmed
|
|
78
|
+
aasm_state :declined
|
|
79
|
+
aasm_state :junk
|
|
80
|
+
aasm_state :cancelled
|
|
81
|
+
|
|
82
|
+
aasm_event :accept do
|
|
83
|
+
transitions from: :proposed, to: :accepted
|
|
84
|
+
transitions from: :rejected, to: :accepted
|
|
85
|
+
transitions from: :waitlisted, to: :accepted
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
aasm_event :reject do
|
|
89
|
+
transitions from: :proposed, to: :rejected
|
|
90
|
+
transitions from: :accepted, to: :rejected
|
|
91
|
+
transitions from: :waitlisted, to: :rejected
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
aasm_event :waitlist do
|
|
95
|
+
transitions from: :proposed, to: :waitlisted
|
|
96
|
+
transitions from: :accepted, to: :waitlisted
|
|
97
|
+
transitions from: :rejected, to: :waitlisted
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
aasm_event :confirm do
|
|
101
|
+
transitions from: :accepted, to: :confirmed
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
aasm_event :decline do
|
|
105
|
+
transitions from: :accepted, to: :declined
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
aasm_event :accept_and_confirm do
|
|
109
|
+
transitions from: :proposed, to: :confirmed
|
|
110
|
+
transitions from: :waitlisted, to: :confirmed
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
aasm_event :accept_and_decline do
|
|
114
|
+
transitions from: :proposed, to: :declined
|
|
115
|
+
transitions from: :waitlisted, to: :declined
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
aasm_event :mark_as_junk do
|
|
119
|
+
transitions from: :proposed, to: :junk
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
aasm_event :reset_status do
|
|
123
|
+
transitions from: %w(accepted rejected waitlisted confirmed declined junk cancelled), to: :proposed
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
aasm_event :cancel do
|
|
127
|
+
transitions from: :confirmed, to: :cancelled
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Associations
|
|
131
|
+
belongs_to :event
|
|
132
|
+
belongs_to :track
|
|
133
|
+
belongs_to :session_type
|
|
134
|
+
belongs_to :room
|
|
135
|
+
has_many :comments, dependent: :destroy
|
|
136
|
+
has_many :user_favorites, dependent: :destroy
|
|
137
|
+
has_many :users_who_favor, through: :user_favorites, source: :user
|
|
138
|
+
has_many :selector_votes
|
|
139
|
+
|
|
140
|
+
has_and_belongs_to_many :users do
|
|
141
|
+
def fullnames
|
|
142
|
+
self.map(&:fullname).join(', ')
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def emails
|
|
146
|
+
self.map(&:email).join(', ')
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Named scopes
|
|
151
|
+
scope :unconfirmed, lambda { where("status != ?", "confirmed") }
|
|
152
|
+
scope :populated, lambda { order(:submitted_at).includes( {event: [:rooms, :tracks]}, :session_type, :track, :room, :users ) }
|
|
153
|
+
scope :scheduled, lambda { where("start_time IS NOT NULL") }
|
|
154
|
+
scope :located, lambda { where("room_id IS NOT NULL") }
|
|
155
|
+
scope :for_event, lambda { |event| where(event_id: event) }
|
|
156
|
+
|
|
157
|
+
# Validations
|
|
158
|
+
validates_presence_of :title, :description, :event_id
|
|
159
|
+
validates_acceptance_of :agreement, accept: true, message: "must be accepted", if: -> { OpenConferenceWare.agreement.present? }
|
|
160
|
+
validates_presence_of :excerpt, if: :proposal_excerpts?
|
|
161
|
+
validates_presence_of :track, if: :event_tracks?
|
|
162
|
+
validates_presence_of :session_type, if: :event_session_types?
|
|
163
|
+
validates_presence_of :presenter, :email, :biography, unless: :user_profiles?
|
|
164
|
+
validates_presence_of :speaking_experience, if: :proposal_speaking_experience?
|
|
165
|
+
validates_presence_of :audience_level, if: Proc.new { Proposal.audience_levels }
|
|
166
|
+
validates_inclusion_of :audience_level, if: Proc.new { Proposal.audience_levels }, allow_blank: true,
|
|
167
|
+
in: OpenConferenceWare.proposal_audience_levels ?
|
|
168
|
+
OpenConferenceWare.proposal_audience_levels.flatten.map { |level| level.with_indifferent_access['slug'] } :
|
|
169
|
+
[]
|
|
170
|
+
validate :validate_complete_user_profile, if: :user_profiles?
|
|
171
|
+
validate :url_validator
|
|
172
|
+
|
|
173
|
+
# Triggers
|
|
174
|
+
before_save :populate_submitted_at
|
|
175
|
+
|
|
176
|
+
# CSV Export
|
|
177
|
+
|
|
178
|
+
base_comma_attributes = Proc.new {
|
|
179
|
+
id
|
|
180
|
+
submitted_at
|
|
181
|
+
track title: "Track" if OpenConferenceWare.have_event_tracks
|
|
182
|
+
title
|
|
183
|
+
excerpt if OpenConferenceWare.have_proposal_excerpts
|
|
184
|
+
description
|
|
185
|
+
audience_level_label "Audience Level"
|
|
186
|
+
|
|
187
|
+
if OpenConferenceWare.have_event_session_types
|
|
188
|
+
session_type title: "Session Type"
|
|
189
|
+
session_type duration: "Duration"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# TODO how to better support multiple speakers!?
|
|
193
|
+
if OpenConferenceWare.have_multiple_presenters
|
|
194
|
+
users fullnames: "Speakers"
|
|
195
|
+
else
|
|
196
|
+
presenter
|
|
197
|
+
affiliation
|
|
198
|
+
website
|
|
199
|
+
biography
|
|
200
|
+
end
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
schedule_comma_attributes = Proc.new {
|
|
204
|
+
room name: "Room Name"
|
|
205
|
+
start_time("Start Time") {|t| t.try(:xmlschema) }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
comma do
|
|
209
|
+
instance_eval &base_comma_attributes
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
comma :schedule do
|
|
213
|
+
instance_eval &base_comma_attributes
|
|
214
|
+
instance_eval &schedule_comma_attributes
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
comma :admin do
|
|
218
|
+
instance_eval &base_comma_attributes
|
|
219
|
+
speaking_experience
|
|
220
|
+
status
|
|
221
|
+
instance_eval &schedule_comma_attributes
|
|
222
|
+
|
|
223
|
+
if OpenConferenceWare.have_multiple_presenters
|
|
224
|
+
users :emails
|
|
225
|
+
else
|
|
226
|
+
email
|
|
227
|
+
end
|
|
228
|
+
note_to_organizers
|
|
229
|
+
comments_text
|
|
230
|
+
user_favorites size: 'Favorites count'
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
comma :selector_votes do
|
|
234
|
+
instance_eval &base_comma_attributes
|
|
235
|
+
|
|
236
|
+
user_favorites size: 'Favorites count'
|
|
237
|
+
selector_vote_points 'Selector points'
|
|
238
|
+
selector_votes_for_comma 'Selector votes'
|
|
239
|
+
comments_for_comma 'Comments'
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Return the first User owner. Burst into flames if no user or multiple users listed.
|
|
243
|
+
def user
|
|
244
|
+
raise ArgumentError, "Can't lookup user when in multiple presenters mode" if multiple_presenters?
|
|
245
|
+
return self.users.first
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# generates a unique slug for the proposal
|
|
249
|
+
def slug
|
|
250
|
+
return "#{OpenConferenceWare.organization_slug}#{event.try(:slug)}-%04d" % id
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# returns a proposal's duration based on its session type
|
|
254
|
+
def duration
|
|
255
|
+
self.session_type.try(:duration)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Return the time this session ends.
|
|
259
|
+
def end_time
|
|
260
|
+
if self.start_time
|
|
261
|
+
self.start_time + (self.duration || 0).minutes
|
|
262
|
+
else
|
|
263
|
+
nil
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Return array of arrays, the first representing the current state, the rest
|
|
268
|
+
# representing optional states. Of each pair, the first element is the title,
|
|
269
|
+
# the second is the status.
|
|
270
|
+
def titles_and_statuses
|
|
271
|
+
result = [["(currently '#{self.aasm_current_state.to_s.titleize}')", nil]]
|
|
272
|
+
result += self.aasm_events_for_current_state.map{|s|[s.to_s.titleize, s.to_s]}.sort_by{|title, state| title}
|
|
273
|
+
return result
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# allows an interface to state machine through update_attributes transition key
|
|
277
|
+
attr_accessor :transition
|
|
278
|
+
def transition=(event)
|
|
279
|
+
send("#{event}!") if !event.blank? && aasm_events_for_current_state.include?(event.to_sym)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Is this +user+ allowed to alter this proposal?
|
|
283
|
+
def can_alter?(user)
|
|
284
|
+
return false unless user
|
|
285
|
+
|
|
286
|
+
user = User.get(user)
|
|
287
|
+
if user.admin?
|
|
288
|
+
return true
|
|
289
|
+
else
|
|
290
|
+
return self.users(true).include?(user)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Return the comments as text.
|
|
295
|
+
def comments_text
|
|
296
|
+
return self.comments.inject("") do |string, comment|
|
|
297
|
+
string +
|
|
298
|
+
(string.empty? ? "" : "\n") +
|
|
299
|
+
comment.email +
|
|
300
|
+
": " +
|
|
301
|
+
comment.message
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Save original created_at time because it doesn't survive database reloads.
|
|
306
|
+
def populate_submitted_at
|
|
307
|
+
self.submitted_at ||= self.created_at || Time.now
|
|
308
|
+
return true
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Validation for making sure user has a complete profile
|
|
312
|
+
def validate_complete_user_profile
|
|
313
|
+
unless self.user_has_complete_profile?
|
|
314
|
+
self.errors.add(:user, "must have a complete profile")
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Does this profile have a user with a complete profile?
|
|
319
|
+
def user_has_complete_profile?
|
|
320
|
+
self.users.each do |user|
|
|
321
|
+
if user.blank? || !user.complete_profile?
|
|
322
|
+
return false
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
return true
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Add user by record or id if needed. Return user object if added.
|
|
329
|
+
def add_user(user)
|
|
330
|
+
user = User.get(user)
|
|
331
|
+
|
|
332
|
+
if self.users.include?(user)
|
|
333
|
+
return nil
|
|
334
|
+
else
|
|
335
|
+
CacheWatcher.expire
|
|
336
|
+
self.users << user
|
|
337
|
+
return user
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Remove user by record or id if needed. Return user object if removed.
|
|
342
|
+
def remove_user(user)
|
|
343
|
+
user = User.get(user)
|
|
344
|
+
|
|
345
|
+
if self.users.include?(user)
|
|
346
|
+
CacheWatcher.expire
|
|
347
|
+
self.users.delete(user)
|
|
348
|
+
return user
|
|
349
|
+
else
|
|
350
|
+
return nil
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Return the object with profile information (e.g., biography). The
|
|
355
|
+
# object can be either a Proposal or a User, or false when there isn't
|
|
356
|
+
# just one presenter per proposal.
|
|
357
|
+
def profile
|
|
358
|
+
if multiple_presenters?
|
|
359
|
+
return false
|
|
360
|
+
elsif user_profiles?
|
|
361
|
+
return user
|
|
362
|
+
else
|
|
363
|
+
return self
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Validate that the record has a blank or valid URL, else add a
|
|
368
|
+
# validation error.
|
|
369
|
+
def url_validator
|
|
370
|
+
validate_url_attribute(:website)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Return string with a "mailto:" link for contacting the proposal's speakers.
|
|
374
|
+
def mailto_link
|
|
375
|
+
link = "mailto:"
|
|
376
|
+
return link << mailto_emails
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Return string with the proposal's speakers' emails.
|
|
380
|
+
def mailto_emails
|
|
381
|
+
if multiple_presenters?
|
|
382
|
+
return self.users.map(&:email).join(", ")
|
|
383
|
+
else
|
|
384
|
+
return self.profile.email
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Returns a string labeling a proposal object as either a proposal or a session depending on its state.
|
|
389
|
+
def kind_label
|
|
390
|
+
return self.confirmed? ? 'session' : 'proposal'
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Returns URL of session notes for this proposal, if available.
|
|
394
|
+
#
|
|
395
|
+
# Reads optional OpenConferenceWare.session_notes_url_format. This 'printf' format
|
|
396
|
+
# contains positional variables that filled by Proposal#session_notes_url:
|
|
397
|
+
# * %1 => site's public URL
|
|
398
|
+
# * %2 => parent OR event slug
|
|
399
|
+
# * %3 => event slug
|
|
400
|
+
#
|
|
401
|
+
# E.g., '%1$s%2$s/wiki/' may translate to 'http://my_site.com/my_parent_slug/wiki'
|
|
402
|
+
def session_notes_url
|
|
403
|
+
escape = lambda{|string| self.class._session_notes_url_escape(string)}
|
|
404
|
+
|
|
405
|
+
if OpenConferenceWare.public_url && OpenConferenceWare.session_notes_url_format && ! self.title.blank?
|
|
406
|
+
return (
|
|
407
|
+
sprintf(
|
|
408
|
+
OpenConferenceWare.session_notes_url_format,
|
|
409
|
+
OpenConferenceWare.public_url,
|
|
410
|
+
escape[self.event.parent_or_self.slug],
|
|
411
|
+
escape[self.event.slug]
|
|
412
|
+
) \
|
|
413
|
+
+ escape[self.title]
|
|
414
|
+
)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Return escaped string for use in a URL in the session notes wiki.
|
|
419
|
+
def self._session_notes_url_escape(string)
|
|
420
|
+
return CGI.escape(string.gsub(/\s/, '_').gsub(/[\\\/\(\)\[\]]+/, '-').gsub(/[<>]/,'').squeeze('_').squeeze('-'))
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# Return the proposal's title downcased or nil.
|
|
424
|
+
def title_downcased
|
|
425
|
+
return self.title.try(:downcase)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Return array of +proposals+ sorted by +field+ (e.g., "title") in +ascending+ order.
|
|
429
|
+
def self.sort(proposals, field="title", is_ascending=true)
|
|
430
|
+
proposals = \
|
|
431
|
+
case field.to_sym
|
|
432
|
+
when :track
|
|
433
|
+
partitioned = proposals.partition{|proposal| proposal.track.nil?}
|
|
434
|
+
without_tracks = partitioned.first.sort_by(&:title)
|
|
435
|
+
with_tracks = partitioned.last.select(&:track).sort_by{|proposal| [proposal.track, proposal.title]}
|
|
436
|
+
with_tracks + without_tracks
|
|
437
|
+
when :start_time
|
|
438
|
+
proposals.select{|proposal| !proposal.start_time.nil? }.sort_by{|proposal| proposal.start_time.to_i }.concat(proposals.select{|proposal| proposal.start_time.nil?})
|
|
439
|
+
when :submitted_at
|
|
440
|
+
proposals.sort_by(&:submitted_at)
|
|
441
|
+
when :title
|
|
442
|
+
proposals.sort_by{|proposal| proposal.title_downcased}
|
|
443
|
+
when :status
|
|
444
|
+
proposals.sort_by{|proposal| [proposal.status, proposal.title_downcased]}
|
|
445
|
+
else
|
|
446
|
+
proposals.sort_by(&:submitted_at)
|
|
447
|
+
end
|
|
448
|
+
proposals = proposals.reverse unless is_ascending
|
|
449
|
+
return proposals
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Return a string of iCalendar data for the given +items+.
|
|
453
|
+
#
|
|
454
|
+
# Options:
|
|
455
|
+
# * title: String to use as the calendar title. Optional.
|
|
456
|
+
# * url_helper: Lambda that's called with an item that should return
|
|
457
|
+
# the URL for the item. Optional, defaults to not returning a URL.
|
|
458
|
+
def self.to_icalendar(items, opts={})
|
|
459
|
+
title = opts[:title] || "Schedule"
|
|
460
|
+
url_helper = opts[:url_helper]
|
|
461
|
+
|
|
462
|
+
calendar = Vpim::Icalendar.create2(Vpim::PRODID)
|
|
463
|
+
calendar.title = title
|
|
464
|
+
calendar.time_zone = Time.zone.tzinfo.name
|
|
465
|
+
items.each do |item|
|
|
466
|
+
next if item.start_time.nil?
|
|
467
|
+
calendar.add_event do |e|
|
|
468
|
+
e.dtstart item.start_time
|
|
469
|
+
e.dtend item.start_time + item.duration.minutes if item.duration
|
|
470
|
+
e.summary item.title
|
|
471
|
+
e.created item.created_at if item.created_at
|
|
472
|
+
e.lastmod item.updated_at if item.updated_at
|
|
473
|
+
e.description((item.respond_to?(:users) ? "#{item.users.map(&:fullname).join(', ')}: " : '') + item.excerpt)
|
|
474
|
+
if item.room
|
|
475
|
+
e.set_text 'LOCATION', item.room.name
|
|
476
|
+
end
|
|
477
|
+
if url_helper
|
|
478
|
+
url = url_helper.call(item)
|
|
479
|
+
e.url url
|
|
480
|
+
e.uid url
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
return calendar.encode.sub(/CALSCALE:Gregorian/, "CALSCALE:Gregorian\nMETHOD:PUBLISH")
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def self.populated_proposals_for(container)
|
|
488
|
+
args = [:event, :room, :session_type, :track, :users]
|
|
489
|
+
case container
|
|
490
|
+
when User
|
|
491
|
+
# Can't eager fetch users for users for some reason, yet all other combinations work fine.
|
|
492
|
+
args.delete(:users)
|
|
493
|
+
end
|
|
494
|
+
return container.proposals.includes(args)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Is this proposal related to the +event+, as in to the event, its parent or children?
|
|
498
|
+
def related_to_event?(some_event)
|
|
499
|
+
for an_event in [some_event, some_event.parent, some_event.parent_or_self.children].compact.flatten
|
|
500
|
+
return true if self.event_id == an_event.id
|
|
501
|
+
end
|
|
502
|
+
return false
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Return next proposal in this event after this one, or nil if none.
|
|
506
|
+
def next_proposal
|
|
507
|
+
return self.event.proposals.where("id > ?", self.id).order("created_at ASC").first
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# Return previous proposal in this event after this one, or nil if none.
|
|
511
|
+
def previous_proposal
|
|
512
|
+
return self.event.proposals.where("id < ?", self.id).order("created_at DESC").first
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Return the integer sum of the selector votes rating for this proposal. Skips
|
|
516
|
+
# the "-1" votes because these mean "I don't know how to rate this proposal".
|
|
517
|
+
def selector_vote_points
|
|
518
|
+
return self.selector_votes.map(&:rating).reject{|o| o == -1}.sum
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Return the integer number of votes submitted that aren't abstensions.
|
|
522
|
+
def selector_votes_count
|
|
523
|
+
return self.selector_votes.map(&:rating).reject{|o| o == -1}.size
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
#---[ Accessors for getting the titles of related objects ]-------------
|
|
527
|
+
|
|
528
|
+
def track_title
|
|
529
|
+
return self.track.try(:title)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def room_title
|
|
533
|
+
return self.room.try(:name)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def session_type_title
|
|
537
|
+
return self.session_type.try(:title)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def event_title
|
|
541
|
+
return self.event.try(:slug)
|
|
542
|
+
end
|
|
543
|
+
alias_method :event_slug, :event_title
|
|
544
|
+
|
|
545
|
+
def user_titles
|
|
546
|
+
return self.users.map(&:label).map(&:to_s)
|
|
547
|
+
end
|
|
548
|
+
alias_method :user_labels, :user_titles
|
|
549
|
+
|
|
550
|
+
#---[ Audience level ]--------------------------------------------------
|
|
551
|
+
|
|
552
|
+
# Return the audience levels. May be nil if not defined.
|
|
553
|
+
#
|
|
554
|
+
# Structure: array of hashes with a "label" and "slug".
|
|
555
|
+
#
|
|
556
|
+
# Example:
|
|
557
|
+
# [
|
|
558
|
+
# {"label"=>"Beginner", "slug"=>"a"},
|
|
559
|
+
# {"label"=>"Intermediate", "slug"=>"b"},
|
|
560
|
+
# {"label"=>"Advanced", "slug"=>"c"}
|
|
561
|
+
# ]
|
|
562
|
+
def self.audience_levels
|
|
563
|
+
OpenConferenceWare.proposal_audience_levels.present? &&
|
|
564
|
+
OpenConferenceWare.proposal_audience_levels.map(&:with_indifferent_access)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
# Return the text hint describing the audience level UI control.
|
|
568
|
+
def self.audience_level_hint
|
|
569
|
+
OpenConferenceWare.proposal_audience_level_hint
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
# Return the string label for the audience level, or nil if not set.
|
|
573
|
+
def audience_level_label
|
|
574
|
+
if ! self.audience_level.blank? && self.class.audience_levels
|
|
575
|
+
return self.class.audience_levels.find { |level| level['slug'] == self.audience_level }['label']
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
#---[ Notify speakers ]---------------------------------------------
|
|
580
|
+
|
|
581
|
+
# returns [sent-emails, already-notified-emails]
|
|
582
|
+
def notify_accepted_speakers
|
|
583
|
+
if accepted?
|
|
584
|
+
if !notified_at
|
|
585
|
+
SpeakerMailer.speaker_accepted_email(self).deliver
|
|
586
|
+
self.notified_at = Time.now
|
|
587
|
+
self.save
|
|
588
|
+
return [self.mailto_emails, nil]
|
|
589
|
+
else
|
|
590
|
+
return [nil, self.mailto_emails]
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
return [nil, nil]
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# returns [sent-emails, already-notified-emails]
|
|
597
|
+
def notify_rejected_speakers
|
|
598
|
+
if rejected?
|
|
599
|
+
if !notified_at
|
|
600
|
+
SpeakerMailer.speaker_rejected_email(self).deliver
|
|
601
|
+
self.notified_at = Time.now
|
|
602
|
+
self.save
|
|
603
|
+
return [ self.mailto_emails, nil ]
|
|
604
|
+
else
|
|
605
|
+
return [ nil, self.mailto_emails ]
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
return [ nil, nil ]
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
#---[ Accessors for comma ]---------------------------------------------
|
|
612
|
+
|
|
613
|
+
def selector_votes_for_comma
|
|
614
|
+
return self.selector_votes.map do |selector_vote|
|
|
615
|
+
"#{selector_vote.rating == -1 ? 'Abstain' : selector_vote.rating}: #{selector_vote.comment}"
|
|
616
|
+
end.join("\n")
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def comments_for_comma
|
|
620
|
+
return self.comments.map do |comment|
|
|
621
|
+
"#{comment.email}: #{comment.message}"
|
|
622
|
+
end.join("\n")
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
end
|