open_conference_ware 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (471) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.autotest +14 -0
  3. data/.coveralls.yml +1 -0
  4. data/.gitignore +21 -0
  5. data/.rspec +1 -0
  6. data/.travis.yml +18 -0
  7. data/CHANGES.md +280 -0
  8. data/CONTRIBUTORS.markdown +17 -0
  9. data/Gemfile +21 -0
  10. data/Gemfile.lock +296 -0
  11. data/Guardfile +24 -0
  12. data/LICENSE.txt +23 -0
  13. data/README.markdown +173 -0
  14. data/Rakefile +27 -0
  15. data/TODO.txt +22 -0
  16. data/app/assets/images/open_conference_ware/accept.png +0 -0
  17. data/app/assets/images/open_conference_ware/arrow-left.png +0 -0
  18. data/app/assets/images/open_conference_ware/arrow-out.png +0 -0
  19. data/app/assets/images/open_conference_ware/arrow-right.png +0 -0
  20. data/app/assets/images/open_conference_ware/arrow-up.png +0 -0
  21. data/app/assets/images/open_conference_ware/arrow_refresh.png +0 -0
  22. data/app/assets/images/open_conference_ware/comment.png +0 -0
  23. data/app/assets/images/open_conference_ware/comment_edit.png +0 -0
  24. data/app/assets/images/open_conference_ware/delete.png +0 -0
  25. data/app/assets/images/open_conference_ware/disk.png +0 -0
  26. data/app/assets/images/open_conference_ware/document-blank.png +0 -0
  27. data/app/assets/images/open_conference_ware/document.png +0 -0
  28. data/app/assets/images/open_conference_ware/edit.png +0 -0
  29. data/app/assets/images/open_conference_ware/extras/001_01.png +0 -0
  30. data/app/assets/images/open_conference_ware/extras/001_05.png +0 -0
  31. data/app/assets/images/open_conference_ware/extras/001_05x16.png +0 -0
  32. data/app/assets/images/open_conference_ware/extras/001_21.png +0 -0
  33. data/app/assets/images/open_conference_ware/extras/001_23.png +0 -0
  34. data/app/assets/images/open_conference_ware/extras/001_24.png +0 -0
  35. data/app/assets/images/open_conference_ware/extras/001_29.png +0 -0
  36. data/app/assets/images/open_conference_ware/extras/01.png +0 -0
  37. data/app/assets/images/open_conference_ware/extras/05.png +0 -0
  38. data/app/assets/images/open_conference_ware/extras/06.png +0 -0
  39. data/app/assets/images/open_conference_ware/extras/1.png +0 -0
  40. data/app/assets/images/open_conference_ware/extras/14.png +0 -0
  41. data/app/assets/images/open_conference_ware/extras/16.png +0 -0
  42. data/app/assets/images/open_conference_ware/extras/2.png +0 -0
  43. data/app/assets/images/open_conference_ware/extras/21.png +0 -0
  44. data/app/assets/images/open_conference_ware/extras/28.png +0 -0
  45. data/app/assets/images/open_conference_ware/extras/3.png +0 -0
  46. data/app/assets/images/open_conference_ware/extras/31.png +0 -0
  47. data/app/assets/images/open_conference_ware/extras/32.png +0 -0
  48. data/app/assets/images/open_conference_ware/extras/35.png +0 -0
  49. data/app/assets/images/open_conference_ware/extras/36.png +0 -0
  50. data/app/assets/images/open_conference_ware/extras/4.png +0 -0
  51. data/app/assets/images/open_conference_ware/extras/44.png +0 -0
  52. data/app/assets/images/open_conference_ware/extras/45.png +0 -0
  53. data/app/assets/images/open_conference_ware/extras/46.png +0 -0
  54. data/app/assets/images/open_conference_ware/extras/cancel.png +0 -0
  55. data/app/assets/images/open_conference_ware/extras/cancelx16.png +0 -0
  56. data/app/assets/images/open_conference_ware/extras/icon.trashcan.gif +0 -0
  57. data/app/assets/images/open_conference_ware/favorite.png +0 -0
  58. data/app/assets/images/open_conference_ware/feed.png +0 -0
  59. data/app/assets/images/open_conference_ware/heart.png +0 -0
  60. data/app/assets/images/open_conference_ware/house.png +0 -0
  61. data/app/assets/images/open_conference_ware/ignite_portland_header3_clear.png +0 -0
  62. data/app/assets/images/open_conference_ware/ignite_portland_header3_clear_cropped.png +0 -0
  63. data/app/assets/images/open_conference_ware/jquery.wysiwyg.gif +0 -0
  64. data/app/assets/images/open_conference_ware/lock.png +0 -0
  65. data/app/assets/images/open_conference_ware/new_window.gif +0 -0
  66. data/app/assets/images/open_conference_ware/openid-icon.gif +0 -0
  67. data/app/assets/images/open_conference_ware/pencil.png +0 -0
  68. data/app/assets/images/open_conference_ware/plus.png +0 -0
  69. data/app/assets/images/open_conference_ware/quote.png +0 -0
  70. data/app/assets/images/open_conference_ware/rails.png +0 -0
  71. data/app/assets/images/open_conference_ware/redx.png +0 -0
  72. data/app/assets/images/open_conference_ware/reset-fff.png +0 -0
  73. data/app/assets/images/open_conference_ware/spinner-big.gif +0 -0
  74. data/app/assets/images/open_conference_ware/spinner.gif +0 -0
  75. data/app/assets/images/open_conference_ware/star.png +0 -0
  76. data/app/assets/images/open_conference_ware/tag_blue.png +0 -0
  77. data/app/assets/images/open_conference_ware/time.png +0 -0
  78. data/app/assets/javascripts/open_conference_ware/application.js +18 -0
  79. data/app/assets/javascripts/open_conference_ware/base.js +15 -0
  80. data/app/assets/javascripts/open_conference_ware/favorites.js +76 -0
  81. data/app/assets/javascripts/open_conference_ware/ie.js +2 -0
  82. data/app/assets/javascripts/open_conference_ware/menu.js +47 -0
  83. data/app/assets/javascripts/open_conference_ware/persona.js +10 -0
  84. data/app/assets/javascripts/open_conference_ware/proposals.js +208 -0
  85. data/app/assets/javascripts/open_conference_ware/schedule.js +20 -0
  86. data/app/assets/javascripts/open_conference_ware/spinner.js +11 -0
  87. data/app/assets/javascripts/open_conference_ware/util.js +23 -0
  88. data/app/assets/stylesheets/open_conference_ware/application.css +8 -0
  89. data/app/assets/stylesheets/open_conference_ware/common.css.scss +491 -0
  90. data/app/assets/stylesheets/open_conference_ware/custom.css.scss +1033 -0
  91. data/app/assets/stylesheets/open_conference_ware/scaffold.css +54 -0
  92. data/app/controllers/open_conference_ware/application_controller.rb +488 -0
  93. data/app/controllers/open_conference_ware/authentications_controller.rb +42 -0
  94. data/app/controllers/open_conference_ware/comments_controller.rb +86 -0
  95. data/app/controllers/open_conference_ware/events_controller.rb +36 -0
  96. data/app/controllers/open_conference_ware/manage/events_controller.rb +157 -0
  97. data/app/controllers/open_conference_ware/manage/snippets_controller.rb +110 -0
  98. data/app/controllers/open_conference_ware/proposals_controller.rb +521 -0
  99. data/app/controllers/open_conference_ware/rooms_controller.rb +130 -0
  100. data/app/controllers/open_conference_ware/schedule_items_controller.rb +128 -0
  101. data/app/controllers/open_conference_ware/selector_votes_controller.rb +71 -0
  102. data/app/controllers/open_conference_ware/session_types_controller.rb +123 -0
  103. data/app/controllers/open_conference_ware/tracks_controller.rb +118 -0
  104. data/app/controllers/open_conference_ware/user_favorites_controller.rb +68 -0
  105. data/app/controllers/open_conference_ware/users_controller.rb +120 -0
  106. data/app/helpers/open_conference_ware/application_helper.rb +167 -0
  107. data/app/helpers/open_conference_ware/authentications_helper.rb +25 -0
  108. data/app/helpers/open_conference_ware/cache_if_helper.rb +13 -0
  109. data/app/helpers/open_conference_ware/display_link_to_helper.rb +25 -0
  110. data/app/helpers/open_conference_ware/display_textile_for_helper.rb +11 -0
  111. data/app/helpers/open_conference_ware/field_annotation_helper.rb +11 -0
  112. data/app/helpers/open_conference_ware/focus_helper.rb +16 -0
  113. data/app/helpers/open_conference_ware/localcss_helper.rb +10 -0
  114. data/app/helpers/open_conference_ware/page_title_helper.rb +17 -0
  115. data/app/helpers/open_conference_ware/proposals_helper.rb +54 -0
  116. data/app/helpers/open_conference_ware/rooms_helper.rb +5 -0
  117. data/app/helpers/open_conference_ware/scroll_to_helper.rb +15 -0
  118. data/app/helpers/open_conference_ware/session_types_helper.rb +5 -0
  119. data/app/helpers/open_conference_ware/snippets_helper.rb +36 -0
  120. data/app/helpers/open_conference_ware/time_range_helper.rb +198 -0
  121. data/app/helpers/open_conference_ware/tracks_helper.rb +9 -0
  122. data/app/helpers/open_conference_ware/user_favorites_helper.rb +15 -0
  123. data/app/mailers/open_conference_ware/speaker_mailer.rb +51 -0
  124. data/app/mixins/open_conference_ware/breadcrumbs_mixin.rb +76 -0
  125. data/app/mixins/open_conference_ware/cache_lookups_mixin.rb +128 -0
  126. data/app/mixins/open_conference_ware/faux_routes_mixin.rb +83 -0
  127. data/app/mixins/open_conference_ware/normalize_url_mixin.rb +40 -0
  128. data/app/mixins/open_conference_ware/public_attributes_mixin.rb +62 -0
  129. data/app/mixins/open_conference_ware/schedule_overlaps_mixin.rb +12 -0
  130. data/app/mixins/open_conference_ware/settings_checkers_mixin.rb +50 -0
  131. data/app/mixins/open_conference_ware/simple_slug_mixin.rb +26 -0
  132. data/app/models/open_conference_ware/authentication.rb +47 -0
  133. data/app/models/open_conference_ware/base.rb +6 -0
  134. data/app/models/open_conference_ware/comment.rb +26 -0
  135. data/app/models/open_conference_ware/event.rb +227 -0
  136. data/app/models/open_conference_ware/proposal.rb +625 -0
  137. data/app/models/open_conference_ware/room.rb +42 -0
  138. data/app/models/open_conference_ware/schedule.rb +216 -0
  139. data/app/models/open_conference_ware/schedule_item.rb +55 -0
  140. data/app/models/open_conference_ware/selector_vote.rb +22 -0
  141. data/app/models/open_conference_ware/session_type.rb +33 -0
  142. data/app/models/open_conference_ware/snippet.rb +58 -0
  143. data/app/models/open_conference_ware/track.rb +51 -0
  144. data/app/models/open_conference_ware/user.rb +262 -0
  145. data/app/models/open_conference_ware/user_favorite.rb +42 -0
  146. data/app/observers/open_conference_ware/cache_watcher.rb +42 -0
  147. data/app/views/layouts/open_conference_ware/_header.html.erb +106 -0
  148. data/app/views/layouts/open_conference_ware/application.atom.erb +1 -0
  149. data/app/views/layouts/open_conference_ware/application.html.erb +126 -0
  150. data/app/views/layouts/open_conference_ware/scaffold.html.erb +21 -0
  151. data/app/views/open_conference_ware/404.html.erb +8 -0
  152. data/app/views/open_conference_ware/422.html.erb +11 -0
  153. data/app/views/open_conference_ware/500.html.erb +11 -0
  154. data/app/views/open_conference_ware/_email_link.html.erb +24 -0
  155. data/app/views/open_conference_ware/_google_analytics.html.erb +9 -0
  156. data/app/views/open_conference_ware/authentications/_developer.html.erb +7 -0
  157. data/app/views/open_conference_ware/authentications/_openid.html.erb +11 -0
  158. data/app/views/open_conference_ware/authentications/_persona.html.erb +17 -0
  159. data/app/views/open_conference_ware/authentications/sign_in.html.erb +12 -0
  160. data/app/views/open_conference_ware/comments/_list.html.erb +31 -0
  161. data/app/views/open_conference_ware/comments/index.atom.builder +25 -0
  162. data/app/views/open_conference_ware/comments/index.html.erb +3 -0
  163. data/app/views/open_conference_ware/events/index.html.erb +9 -0
  164. data/app/views/open_conference_ware/events/show.html.erb +0 -0
  165. data/app/views/open_conference_ware/events/speakers.html.erb +7 -0
  166. data/app/views/open_conference_ware/manage/events/_form.html.erb +107 -0
  167. data/app/views/open_conference_ware/manage/events/edit.html.erb +3 -0
  168. data/app/views/open_conference_ware/manage/events/index.html.erb +41 -0
  169. data/app/views/open_conference_ware/manage/events/new.html.erb +3 -0
  170. data/app/views/open_conference_ware/manage/events/proposals.html.erb +133 -0
  171. data/app/views/open_conference_ware/manage/events/show.html.erb +181 -0
  172. data/app/views/open_conference_ware/manage/snippets/_form.html.erb +30 -0
  173. data/app/views/open_conference_ware/manage/snippets/edit.html.erb +3 -0
  174. data/app/views/open_conference_ware/manage/snippets/index.html.erb +34 -0
  175. data/app/views/open_conference_ware/manage/snippets/new.html.erb +3 -0
  176. data/app/views/open_conference_ware/manage/snippets/show.html.erb +32 -0
  177. data/app/views/open_conference_ware/proposals/_admin_controls.html.erb +33 -0
  178. data/app/views/open_conference_ware/proposals/_form.html.erb +381 -0
  179. data/app/views/open_conference_ware/proposals/_list.html.erb +164 -0
  180. data/app/views/open_conference_ware/proposals/_list_concise.html.erb +93 -0
  181. data/app/views/open_conference_ware/proposals/_manage_speakers.html.erb +30 -0
  182. data/app/views/open_conference_ware/proposals/_room_control.html.erb +21 -0
  183. data/app/views/open_conference_ware/proposals/_schedule_block.html.erb +8 -0
  184. data/app/views/open_conference_ware/proposals/_schedule_control.html.erb +13 -0
  185. data/app/views/open_conference_ware/proposals/_search_speakers.html.erb +20 -0
  186. data/app/views/open_conference_ware/proposals/_sub_list.html.erb +65 -0
  187. data/app/views/open_conference_ware/proposals/_sub_list_for_kind.html.erb +31 -0
  188. data/app/views/open_conference_ware/proposals/_transition_control.html.erb +16 -0
  189. data/app/views/open_conference_ware/proposals/create.html.erb +29 -0
  190. data/app/views/open_conference_ware/proposals/edit.html.erb +7 -0
  191. data/app/views/open_conference_ware/proposals/index.atom.builder +63 -0
  192. data/app/views/open_conference_ware/proposals/index.html.erb +46 -0
  193. data/app/views/open_conference_ware/proposals/new.html.erb +3 -0
  194. data/app/views/open_conference_ware/proposals/schedule.html.erb +108 -0
  195. data/app/views/open_conference_ware/proposals/sessions_index_terse.html.erb +22 -0
  196. data/app/views/open_conference_ware/proposals/show.html.erb +316 -0
  197. data/app/views/open_conference_ware/proposals/stats.html.erb +72 -0
  198. data/app/views/open_conference_ware/rooms/_form.html.erb +36 -0
  199. data/app/views/open_conference_ware/rooms/edit.html.erb +3 -0
  200. data/app/views/open_conference_ware/rooms/index.html.erb +13 -0
  201. data/app/views/open_conference_ware/rooms/new.html.erb +3 -0
  202. data/app/views/open_conference_ware/rooms/show.html.erb +27 -0
  203. data/app/views/open_conference_ware/schedule_items/_form.html.erb +35 -0
  204. data/app/views/open_conference_ware/schedule_items/edit.html.erb +3 -0
  205. data/app/views/open_conference_ware/schedule_items/index.html.erb +31 -0
  206. data/app/views/open_conference_ware/schedule_items/new.html.erb +3 -0
  207. data/app/views/open_conference_ware/schedule_items/show.html.erb +30 -0
  208. data/app/views/open_conference_ware/selector_votes/index.html.erb +81 -0
  209. data/app/views/open_conference_ware/session_types/_form.html.erb +23 -0
  210. data/app/views/open_conference_ware/session_types/edit.html.erb +3 -0
  211. data/app/views/open_conference_ware/session_types/index.html.erb +18 -0
  212. data/app/views/open_conference_ware/session_types/new.html.erb +3 -0
  213. data/app/views/open_conference_ware/session_types/show.html.erb +14 -0
  214. data/app/views/open_conference_ware/speaker_mailer/speaker_email.text.erb +14 -0
  215. data/app/views/open_conference_ware/tracks/_colors.css.erb +7 -0
  216. data/app/views/open_conference_ware/tracks/_form.html.erb +36 -0
  217. data/app/views/open_conference_ware/tracks/edit.html.erb +3 -0
  218. data/app/views/open_conference_ware/tracks/index.html.erb +25 -0
  219. data/app/views/open_conference_ware/tracks/new.html.erb +3 -0
  220. data/app/views/open_conference_ware/tracks/show.html.erb +19 -0
  221. data/app/views/open_conference_ware/user_favorites/index.html.erb +12 -0
  222. data/app/views/open_conference_ware/users/_account_box.html.erb +10 -0
  223. data/app/views/open_conference_ware/users/_block.html.erb +80 -0
  224. data/app/views/open_conference_ware/users/_list.html.erb +17 -0
  225. data/app/views/open_conference_ware/users/edit.html.erb +73 -0
  226. data/app/views/open_conference_ware/users/index.html.erb +28 -0
  227. data/app/views/open_conference_ware/users/new.html.erb +7 -0
  228. data/app/views/open_conference_ware/users/proposals.html.erb +24 -0
  229. data/app/views/open_conference_ware/users/show.html.erb +5 -0
  230. data/app/views/open_conference_ware/welcome/test.html.haml +8 -0
  231. data/bin/rails +8 -0
  232. data/ci/Gemfile.ci +13 -0
  233. data/ci/copy_database_config.rb +6 -0
  234. data/ci/database.mysql.yml +5 -0
  235. data/ci/database.postgresql.yml +4 -0
  236. data/ci/database.sqlite.yml +5 -0
  237. data/config/initializers/acts_as_taggable_on.rb +2 -0
  238. data/config/initializers/date_time_formats.rb +9 -0
  239. data/config/routes.rb +74 -0
  240. data/db/migrate/20131203235128_create_open_conference_ware_authentications.rb +14 -0
  241. data/db/migrate/20131203235216_create_open_conference_ware_comments.rb +14 -0
  242. data/db/migrate/20131203235418_create_open_conference_ware_events.rb +27 -0
  243. data/db/migrate/20131203235524_create_open_conference_ware_proposals.rb +36 -0
  244. data/db/migrate/20131203235723_create_open_conference_ware_rooms.rb +20 -0
  245. data/db/migrate/20131203235831_create_open_conference_ware_schedule_items.rb +18 -0
  246. data/db/migrate/20131203235910_create_open_conference_ware_selector_votes.rb +10 -0
  247. data/db/migrate/20131203235934_create_open_conference_ware_session_types.rb +14 -0
  248. data/db/migrate/20131204000008_create_open_conference_ware_snippets.rb +15 -0
  249. data/db/migrate/20131204000048_create_open_conference_ware_taggings.rb +16 -0
  250. data/db/migrate/20131204000129_create_open_conference_ware_tags.rb +7 -0
  251. data/db/migrate/20131204000147_create_open_conference_ware_tracks.rb +15 -0
  252. data/db/migrate/20131204000230_create_open_conference_ware_user_favorites.rb +10 -0
  253. data/db/migrate/20131204000251_create_open_conference_ware_users.rb +24 -0
  254. data/db/migrate/20131204000355_create_proposals_users_join_table.rb +8 -0
  255. data/db/seeds.rb +31 -0
  256. data/features/comment_create.feature +9 -0
  257. data/features/comment_destroy.feature +22 -0
  258. data/features/comment_new.feature +22 -0
  259. data/features/step_definitions/authentication_steps.rb +22 -0
  260. data/features/step_definitions/comment_create_steps.rb +5 -0
  261. data/features/step_definitions/comment_destroy_steps.rb +13 -0
  262. data/features/step_definitions/comment_new_steps.rb +41 -0
  263. data/features/step_definitions/notification_steps.rb +3 -0
  264. data/features/step_definitions/web_steps.rb +263 -0
  265. data/features/support/env.rb +58 -0
  266. data/features/support/helpers.rb +17 -0
  267. data/features/support/paths.rb +35 -0
  268. data/gem-public_cert.pem +22 -0
  269. data/lib/defer_proxy.rb +127 -0
  270. data/lib/ext/color_rgb_serializer_fix.rb +18 -0
  271. data/lib/ext/string_possessiveize.rb +7 -0
  272. data/lib/ext/vpim_icalendar_extra_properties.rb +25 -0
  273. data/lib/generators/open_conference_ware/install/USAGE +12 -0
  274. data/lib/generators/open_conference_ware/install/install_generator.rb +35 -0
  275. data/lib/generators/open_conference_ware/install/templates/config_initializer.rb +110 -0
  276. data/lib/generators/open_conference_ware/install/templates/omniauth_initializer.rb +33 -0
  277. data/lib/open_conference_ware.rb +156 -0
  278. data/lib/open_conference_ware/dependencies.rb +38 -0
  279. data/lib/open_conference_ware/engine.rb +38 -0
  280. data/lib/open_conference_ware/omni_auth_builder.rb +8 -0
  281. data/lib/open_conference_ware/version.rb +3 -0
  282. data/lib/rwikibot_page_drone.rb +70 -0
  283. data/lib/tasks/.gitkeep +0 -0
  284. data/lib/tasks/audio.rake +53 -0
  285. data/lib/tasks/cucumber.rake +65 -0
  286. data/lib/tasks/export.rake +170 -0
  287. data/lib/tasks/mediawiki.rake +96 -0
  288. data/lib/tasks/open_conference_ware_tasks.rake +16 -0
  289. data/lib/tasks/schedule.rake +103 -0
  290. data/lib/tasks/snippets.rake +23 -0
  291. data/open_conference_ware.gemspec +67 -0
  292. data/original_graphics/favorite.psd +0 -0
  293. data/spec/controllers/open_conference_ware/application_controller_spec.rb +309 -0
  294. data/spec/controllers/open_conference_ware/authentications_controller_spec.rb +87 -0
  295. data/spec/controllers/open_conference_ware/comments_controller_spec.rb +146 -0
  296. data/spec/controllers/open_conference_ware/events_controller_spec.rb +102 -0
  297. data/spec/controllers/open_conference_ware/manage_events_controller_spec.rb +103 -0
  298. data/spec/controllers/open_conference_ware/proposals_controller_spec.rb +1273 -0
  299. data/spec/controllers/open_conference_ware/rooms_controller_spec.rb +190 -0
  300. data/spec/controllers/open_conference_ware/selector_votes_controller_spec.rb +263 -0
  301. data/spec/controllers/open_conference_ware/session_types_controller_spec.rb +190 -0
  302. data/spec/controllers/open_conference_ware/sessions_routing_spec.rb +13 -0
  303. data/spec/controllers/open_conference_ware/tracks_controller_spec.rb +190 -0
  304. data/spec/controllers/open_conference_ware/user_favorites_controller_spec.rb +127 -0
  305. data/spec/controllers/open_conference_ware/users_controller_spec.rb +166 -0
  306. data/spec/dummy/README.rdoc +28 -0
  307. data/spec/dummy/Rakefile +6 -0
  308. data/spec/dummy/app/assets/images/.keep +0 -0
  309. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  310. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  311. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  312. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  313. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  314. data/spec/dummy/app/mailers/.keep +0 -0
  315. data/spec/dummy/app/models/.keep +0 -0
  316. data/spec/dummy/app/models/concerns/.keep +0 -0
  317. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  318. data/spec/dummy/bin/bundle +3 -0
  319. data/spec/dummy/bin/rails +4 -0
  320. data/spec/dummy/bin/rake +4 -0
  321. data/spec/dummy/config.ru +4 -0
  322. data/spec/dummy/config/application.rb +30 -0
  323. data/spec/dummy/config/boot.rb +5 -0
  324. data/spec/dummy/config/database.yml +25 -0
  325. data/spec/dummy/config/environment.rb +5 -0
  326. data/spec/dummy/config/environments/development.rb +29 -0
  327. data/spec/dummy/config/environments/production.rb +80 -0
  328. data/spec/dummy/config/environments/test.rb +36 -0
  329. data/spec/dummy/config/initializers/01_open_conference_ware.rb +110 -0
  330. data/spec/dummy/config/initializers/02_omniauth.rb +17 -0
  331. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  332. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  333. data/spec/dummy/config/initializers/inflections.rb +16 -0
  334. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  335. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  336. data/spec/dummy/config/initializers/session_store.rb +3 -0
  337. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  338. data/spec/dummy/config/locales/en.yml +23 -0
  339. data/spec/dummy/config/routes.rb +3 -0
  340. data/spec/dummy/db/schema.rb +219 -0
  341. data/spec/dummy/db/seeds.rb +10 -0
  342. data/spec/dummy/lib/assets/.keep +0 -0
  343. data/spec/dummy/log/.keep +0 -0
  344. data/spec/dummy/public/404.html +58 -0
  345. data/spec/dummy/public/422.html +58 -0
  346. data/spec/dummy/public/500.html +57 -0
  347. data/spec/dummy/public/favicon.ico +0 -0
  348. data/spec/factories/authentication_factory.rb +16 -0
  349. data/spec/factories/common_factory.rb +4 -0
  350. data/spec/factories/event_factory.rb +26 -0
  351. data/spec/factories/proposal_factory.rb +57 -0
  352. data/spec/factories/proposal_user_factory.rb +7 -0
  353. data/spec/factories/room_factory.rb +11 -0
  354. data/spec/factories/schedule_item_factory.rb +13 -0
  355. data/spec/factories/session_types_factory.rb +10 -0
  356. data/spec/factories/snippet_factory.rb +7 -0
  357. data/spec/factories/track_factory.rb +12 -0
  358. data/spec/factories/user_factory.rb +22 -0
  359. data/spec/factories/user_favorite_factory.rb +7 -0
  360. data/spec/features/sign_in_spec.rb +11 -0
  361. data/spec/features/snippets_spec.rb +38 -0
  362. data/spec/fixtures/open_conference_ware_comments.yml +27 -0
  363. data/spec/fixtures/open_conference_ware_events.yml +56 -0
  364. data/spec/fixtures/open_conference_ware_proposals.yml +256 -0
  365. data/spec/fixtures/open_conference_ware_rooms.yml +13 -0
  366. data/spec/fixtures/open_conference_ware_schedule_items.yml +23 -0
  367. data/spec/fixtures/open_conference_ware_session_types.yml +19 -0
  368. data/spec/fixtures/open_conference_ware_snippets.yml +62 -0
  369. data/spec/fixtures/open_conference_ware_tracks.yml +65 -0
  370. data/spec/fixtures/open_conference_ware_user_favorites.yml +7 -0
  371. data/spec/fixtures/open_conference_ware_users.yml +87 -0
  372. data/spec/helpers/open_conference_ware/application_helper_spec.rb +58 -0
  373. data/spec/helpers/open_conference_ware/authentications_helper_spec.rb +15 -0
  374. data/spec/helpers/open_conference_ware/display_link_to_helper_spec.rb +52 -0
  375. data/spec/helpers/open_conference_ware/proposals_helper_spec.rb +32 -0
  376. data/spec/helpers/open_conference_ware/rooms_helper_spec.rb +11 -0
  377. data/spec/helpers/open_conference_ware/session_types_helper_spec.rb +15 -0
  378. data/spec/helpers/open_conference_ware/time_range_helper_spec.rb +56 -0
  379. data/spec/helpers/open_conference_ware/tracks_helper_spec.rb +11 -0
  380. data/spec/helpers/open_conference_ware/user_favorites_helper_spec.rb +11 -0
  381. data/spec/integration/open_conference_ware/cache_lookups_mixin_spec.rb +122 -0
  382. data/spec/mixins/open_conference_ware/normalize_url_mixin_spec.rb +65 -0
  383. data/spec/models/open_conference_ware/authentication_spec.rb +67 -0
  384. data/spec/models/open_conference_ware/event_spec.rb +211 -0
  385. data/spec/models/open_conference_ware/proposal_spec.rb +606 -0
  386. data/spec/models/open_conference_ware/room_spec.rb +14 -0
  387. data/spec/models/open_conference_ware/schedule_item_spec.rb +12 -0
  388. data/spec/models/open_conference_ware/schedule_spec.rb +358 -0
  389. data/spec/models/open_conference_ware/selector_vote_spec.rb +12 -0
  390. data/spec/models/open_conference_ware/session_type_spec.rb +15 -0
  391. data/spec/models/open_conference_ware/snippet_spec.rb +13 -0
  392. data/spec/models/open_conference_ware/speaker_mailer_spec.rb +91 -0
  393. data/spec/models/open_conference_ware/track_spec.rb +36 -0
  394. data/spec/models/open_conference_ware/user_favorite_spec.rb +41 -0
  395. data/spec/models/open_conference_ware/user_spec.rb +77 -0
  396. data/spec/ocw_config.rb +76 -0
  397. data/spec/rcov.opts +2 -0
  398. data/spec/spec.opts +3 -0
  399. data/spec/spec_helper.rb +55 -0
  400. data/spec/spec_helper_customizations.rb +125 -0
  401. data/spec/support/add_all_helpers_to_view.rb +24 -0
  402. data/spec/support/authenticated_test_helper.rb +27 -0
  403. data/spec/support/fixture_shortcuts.rb +18 -0
  404. data/spec/support/omniauth.rb +34 -0
  405. data/spec/support/valid_params_extraction.rb +15 -0
  406. data/spec/views/open_conference_ware/proposals/_room_control.html.erb_spec.rb +23 -0
  407. data/spec/views/open_conference_ware/proposals/_transition_control.html.erb_spec.rb +14 -0
  408. data/spec/views/open_conference_ware/proposals/show.html.erb_spec.rb +128 -0
  409. data/spec/views/open_conference_ware/rooms/index.html.erb_spec.rb +19 -0
  410. data/spec/views/open_conference_ware/rooms/show.html.erb_spec.rb +15 -0
  411. data/spec/views/open_conference_ware/selector_votes/index.html.erb_spec.rb +41 -0
  412. data/spec/views/open_conference_ware/session_types/edit.html.erb_spec.rb +25 -0
  413. data/spec/views/open_conference_ware/session_types/index.html.erb_spec.rb +19 -0
  414. data/spec/views/open_conference_ware/session_types/new.html.erb_spec.rb +24 -0
  415. data/spec/views/open_conference_ware/session_types/show.html.erb_spec.rb +18 -0
  416. data/spec/views/open_conference_ware/tracks/edit.html.erb_spec.rb +25 -0
  417. data/spec/views/open_conference_ware/tracks/index.html.erb_spec.rb +56 -0
  418. data/spec/views/open_conference_ware/tracks/new.html.erb_spec.rb +26 -0
  419. data/spec/views/open_conference_ware/tracks/show.html.erb_spec.rb +23 -0
  420. data/spec/views/open_conference_ware/user_favorites/index.html.erb_spec.rb +29 -0
  421. data/spec/views/open_conference_ware/users/index_spec.rb +24 -0
  422. data/util/add_id3_tags_to_mp3.rb +57 -0
  423. data/util/schedule_demo.rb +36 -0
  424. data/util/seed_schedule.rb +61 -0
  425. data/util/sessions_to_lanyrd.rb +166 -0
  426. data/util/transfer_schedule_items.rb +25 -0
  427. data/util/update_proposal_status_from_voting_spreadsheet.rb +26 -0
  428. data/util/user_favorites_contention_report.rb +42 -0
  429. data/util/user_favorites_contention_report.sh +9 -0
  430. data/vendor/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  431. data/vendor/assets/fonts/glyphicons-halflings-regular.svg +228 -0
  432. data/vendor/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  433. data/vendor/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  434. data/vendor/assets/images/farbtastic/marker.png +0 -0
  435. data/vendor/assets/images/farbtastic/mask.png +0 -0
  436. data/vendor/assets/images/farbtastic/wheel.png +0 -0
  437. data/vendor/assets/images/idselector/aol.ico +0 -0
  438. data/vendor/assets/images/idselector/arrow.gif +0 -0
  439. data/vendor/assets/images/idselector/arrow_white_back.png +0 -0
  440. data/vendor/assets/images/idselector/arrow_white_forward.png +0 -0
  441. data/vendor/assets/images/idselector/blogger.ico +0 -0
  442. data/vendor/assets/images/idselector/claimid.ico +0 -0
  443. data/vendor/assets/images/idselector/flickr.ico +0 -0
  444. data/vendor/assets/images/idselector/google.ico +0 -0
  445. data/vendor/assets/images/idselector/lj.ico +0 -0
  446. data/vendor/assets/images/idselector/myopenid.ico +0 -0
  447. data/vendor/assets/images/idselector/openid.ico +0 -0
  448. data/vendor/assets/images/idselector/technorati.ico +0 -0
  449. data/vendor/assets/images/idselector/verisign.ico +0 -0
  450. data/vendor/assets/images/idselector/vidoop2.ico +0 -0
  451. data/vendor/assets/images/idselector/vox.ico +0 -0
  452. data/vendor/assets/images/idselector/yahoo.ico +0 -0
  453. data/vendor/assets/javascripts/audiojs/audio.min.js +23 -0
  454. data/vendor/assets/javascripts/audiojs/audiojs.swf +0 -0
  455. data/vendor/assets/javascripts/audiojs/player-graphics.gif +0 -0
  456. data/vendor/assets/javascripts/bootstrap.js +1999 -0
  457. data/vendor/assets/javascripts/farbtastic.js +329 -0
  458. data/vendor/assets/javascripts/html5shiv.js +8 -0
  459. data/vendor/assets/javascripts/idselector.js +538 -0
  460. data/vendor/assets/javascripts/idselector~original.js +532 -0
  461. data/vendor/assets/javascripts/jquery-migrate-1.2.1.js +521 -0
  462. data/vendor/assets/javascripts/jquery.localScroll.js +106 -0
  463. data/vendor/assets/javascripts/jquery.scrollTo.js +174 -0
  464. data/vendor/assets/javascripts/respond.min.js +6 -0
  465. data/vendor/assets/stylesheets/bootstrap-theme.css +384 -0
  466. data/vendor/assets/stylesheets/bootstrap.css +6805 -0
  467. data/vendor/assets/stylesheets/farbtastic.css.scss +38 -0
  468. data/vendor/assets/stylesheets/persona-buttons.css +229 -0
  469. data/vendor/assets/stylesheets/reset-fonts-grids.css +7 -0
  470. metadata +1174 -0
  471. 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,6 @@
1
+ module OpenConferenceWare
2
+ class Base < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ self.include_root_in_json = true
5
+ end
6
+ 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