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.
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