Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Public source code release of e-petitions

  • Loading branch information...
commit 99852d4414258621e9d51ad6eca2c3fba8c65ece 0 parents
@chrismdp chrismdp authored
Showing with 9,751 additions and 0 deletions.
  1. +19 −0 .gitignore
  2. +1 −0  .rspec
  3. +4 −0 Capfile
  4. +73 −0 Gemfile
  5. +289 −0 Gemfile.lock
  6. +10 −0 README
  7. +10 −0 Rakefile
  8. +9 −0 app/controllers/admin/admin_controller.rb
  9. +68 −0 app/controllers/admin/admin_users_controller.rb
  10. +121 −0 app/controllers/admin/petitions_controller.rb
  11. +24 −0 app/controllers/admin/profile_controller.rb
  12. +12 −0 app/controllers/admin/reports_controller.rb
  13. +37 −0 app/controllers/admin/searches_controller.rb
  14. +11 −0 app/controllers/admin/todolist_controller.rb
  15. +30 −0 app/controllers/admin/user_sessions_controller.rb
  16. +31 −0 app/controllers/application_controller.rb
  17. +21 −0 app/controllers/departments_controller.rb
  18. +21 −0 app/controllers/feedback_controller.rb
  19. +90 −0 app/controllers/petitions_controller.rb
  20. +14 −0 app/controllers/search_controller.rb
  21. +61 −0 app/controllers/signatures_controller.rb
  22. +11 −0 app/controllers/static_pages_controller.rb
  23. +54 −0 app/helpers/admin_form_builder.rb
  24. +2 −0  app/helpers/admin_form_helper.rb
  25. +25 −0 app/helpers/admin_helper.rb
  26. +29 −0 app/helpers/application_helper.rb
  27. +14 −0 app/helpers/captcha_helper.rb
  28. +24 −0 app/helpers/date_time_helper.rb
  29. +2 −0  app/helpers/feedback_helper.rb
  30. +247 −0 app/helpers/form_helper.rb
  31. +5 −0 app/helpers/link_helper.rb
  32. +26 −0 app/helpers/search_helper.rb
  33. +49 −0 app/jobs/email_threshold_response_job.rb
  34. +15 −0 app/mailers/admin_mailer.rb
  35. +12 −0 app/mailers/feedback_mailer.rb
  36. +71 −0 app/mailers/petition_mailer.rb
  37. +97 −0 app/models/admin_user.rb
  38. +5 −0 app/models/admin_user_session.rb
  39. +40 −0 app/models/captcha.rb
  40. +30 −0 app/models/department.rb
  41. +18 −0 app/models/department_assignment.rb
  42. +197 −0 app/models/petition.rb
  43. +102 −0 app/models/signature.rb
  44. +49 −0 app/models/system_setting.rb
  45. +27 −0 app/models/trending_petition.rb
  46. +27 −0 app/views/admin/admin_users/_form.html.erb
  47. +4 −0 app/views/admin/admin_users/edit.html.erb
  48. +21 −0 app/views/admin/admin_users/index.html.erb
  49. +4 −0 app/views/admin/admin_users/new.html.erb
  50. +4 −0 app/views/admin/petitions/_internal_response.html.erb
  51. +11 −0 app/views/admin/petitions/_petition.html.erb
  52. +33 −0 app/views/admin/petitions/_petition_details.html.erb
  53. +31 −0 app/views/admin/petitions/_published_petition_details.html.erb
  54. +28 −0 app/views/admin/petitions/_reject.html.erb
  55. +37 −0 app/views/admin/petitions/edit.html.erb
  56. +27 −0 app/views/admin/petitions/edit_internal_response.html.erb
  57. +24 −0 app/views/admin/petitions/edit_response.html.erb
  58. +34 −0 app/views/admin/petitions/index.html.erb
  59. +8 −0 app/views/admin/petitions/show.html.erb
  60. +13 −0 app/views/admin/petitions/threshold.html.erb
  61. +11 −0 app/views/admin/profile/edit.html.erb
  62. +74 −0 app/views/admin/reports/index.html.erb
  63. +6 −0 app/views/admin/searches/_form.html.erb
  64. +13 −0 app/views/admin/searches/new.html.erb
  65. +1 −0  app/views/admin/searches/petition_by_id.html.erb
  66. +31 −0 app/views/admin/searches/result.html.erb
  67. +56 −0 app/views/admin/shared/_admin_nav.html.erb
  68. +6 −0 app/views/admin/shared/_messages.html.erb
  69. +5 −0 app/views/admin/shared/_results_per_page.html.erb
  70. +7 −0 app/views/admin/signatures/_signature.html.erb
  71. +22 −0 app/views/admin/todolist/index.html.erb
  72. +15 −0 app/views/admin/user_sessions/new.html.erb
  73. +9 −0 app/views/admin_mailer/admin_email_reminder.html.erb
  74. +8 −0 app/views/admin_mailer/admin_email_reminder.text.erb
  75. +6 −0 app/views/admin_mailer/threshold_email_reminder.html.erb
  76. +6 −0 app/views/admin_mailer/threshold_email_reminder.text.erb
  77. +16 −0 app/views/api/petitions/index.json.rabl
  78. +3 −0  app/views/api/petitions/show.json.rabl
  79. +35 −0 app/views/departments/index.html.erb
  80. +34 −0 app/views/departments/info.html.erb
  81. +6 −0 app/views/departments/show.html.erb
  82. +99 −0 app/views/feedback/index.html.erb
  83. +14 −0 app/views/feedback/thanks.html.erb
  84. +12 −0 app/views/feedback_mailer/send_feedback.text.erb
  85. +32 −0 app/views/layouts/admin.html.erb
  86. +83 −0 app/views/layouts/application.html.erb
  87. +40 −0 app/views/layouts/default_mail.html.erb
  88. +7 −0 app/views/petition_mailer/double_signature_confirmation.html.erb
  89. +7 −0 app/views/petition_mailer/double_signature_confirmation.text.erb
  90. +7 −0 app/views/petition_mailer/email_already_confirmed_for_signature.html.erb
  91. +7 −0 app/views/petition_mailer/email_already_confirmed_for_signature.text.erb
  92. +16 −0 app/views/petition_mailer/email_confirmation_for_creator.html.erb
  93. +13 −0 app/views/petition_mailer/email_confirmation_for_creator.text.erb
  94. +14 −0 app/views/petition_mailer/email_confirmation_for_signer.html.erb
  95. +11 −0 app/views/petition_mailer/email_confirmation_for_signer.text.erb
  96. +13 −0 app/views/petition_mailer/no_signature_for_petition.html.erb
  97. +14 −0 app/views/petition_mailer/no_signature_for_petition.text.erb
  98. +11 −0 app/views/petition_mailer/notify_creator_that_petition_is_published.html.erb
  99. +11 −0 app/views/petition_mailer/notify_creator_that_petition_is_published.text.erb
  100. +19 −0 app/views/petition_mailer/notify_creator_that_petition_is_rejected.html.erb
  101. +19 −0 app/views/petition_mailer/notify_creator_that_petition_is_rejected.text.erb
  102. +11 −0 app/views/petition_mailer/notify_signer_of_threshold_response.html.erb
  103. +13 −0 app/views/petition_mailer/notify_signer_of_threshold_response.text.erb
  104. +13 −0 app/views/petition_mailer/one_pending_one_validated_signature.html.erb
  105. +13 −0 app/views/petition_mailer/one_pending_one_validated_signature.text.erb
  106. +16 −0 app/views/petition_mailer/special_resend_of_email_confirmation_for_signer.html.erb
  107. +11 −0 app/views/petition_mailer/special_resend_of_email_confirmation_for_signer.text.erb
  108. +13 −0 app/views/petition_mailer/two_pending_signatures.html.erb
  109. +13 −0 app/views/petition_mailer/two_pending_signatures.text.erb
  110. +7 −0 app/views/petitions/_pre_creation_search_form.html.erb
  111. +15 −0 app/views/petitions/_trending_petition.html.erb
  112. +8 −0 app/views/petitions/check.html.erb
  113. +28 −0 app/views/petitions/check_results.html.erb
  114. +6 −0 app/views/petitions/index.html.erb
  115. +204 −0 app/views/petitions/new.html.erb
  116. +14 −0 app/views/petitions/resend_confirmation_email.html.erb
  117. +97 −0 app/views/petitions/show.html.erb
  118. +14 −0 app/views/petitions/thank_you.html.erb
  119. +1 −0  app/views/search/_result_list_footer.html.erb
  120. +2 −0  app/views/search/_result_list_header.html.erb
  121. +18 −0 app/views/search/_result_tabs.html.erb
  122. +14 −0 app/views/search/_results.html.erb
  123. +43 −0 app/views/search/_results_table.html.erb
  124. +8 −0 app/views/search/search.html.erb
  125. +35 −0 app/views/shared/_captcha_widget.html.erb
  126. +6 −0 app/views/shared/_messages.html.erb
  127. +62 −0 app/views/signatures/_form.html.erb
  128. +6 −0 app/views/signatures/_notify_by_email.html.erb
  129. +10 −0 app/views/signatures/_terms_and_conditions.html.erb
  130. +109 −0 app/views/signatures/new.html.erb
  131. +12 −0 app/views/signatures/signed.html.erb
  132. +14 −0 app/views/signatures/thank_you.html.erb
  133. +16 −0 app/views/signatures/verify.html.erb
  134. +59 −0 app/views/static_pages/accessibility.html.erb
  135. +40 −0 app/views/static_pages/crown_copyright.html.erb
  136. +116 −0 app/views/static_pages/faq.html.erb
  137. +61 −0 app/views/static_pages/home.html.erb
  138. +113 −0 app/views/static_pages/how_it_works.html.erb
  139. +40 −0 app/views/static_pages/privacy_policy.html.erb
  140. +118 −0 app/views/static_pages/terms_and_conditions.html.erb
  141. +4 −0 config.ru
  142. +49 −0 config/application.rb
  143. +17 −0 config/application.yml
  144. +6 −0 config/boot.rb
  145. +8 −0 config/cucumber.yml
  146. +19 −0 config/database.yml.example
  147. +9 −0 config/database_jenkins.yml
  148. +10 −0 config/environment.rb
  149. +29 −0 config/environments/development.rb
  150. +29 −0 config/environments/development_with_caching.rb
  151. +56 −0 config/environments/preprod.rb
  152. +60 −0 config/environments/production.rb
  153. +55 −0 config/environments/staging.rb
  154. +35 −0 config/environments/test.rb
  155. +3 −0  config/evergreen.rb
  156. +7 −0 config/initializers/backtrace_silencers.rb
  157. +2 −0  config/initializers/constants.rb
  158. +2 −0  config/initializers/delayed_job_config.rb
  159. +10 −0 config/initializers/inflections.rb
  160. +5 −0 config/initializers/mime_types.rb
  161. +4 −0 config/initializers/newrelic.rb
  162. +8 −0 config/initializers/secret_token.rb
  163. +8 −0 config/initializers/session_store.rb
  164. +1 −0  config/initializers/time_formats.rb
  165. +5 −0 config/initializers/will_paginate.rb
  166. +5 −0 config/locales/en.yml
  167. +32 −0 config/rejection_reasons.json
  168. +75 −0 config/routes.rb
  169. +33 −0 config/schedule.rb
  170. +31 −0 config/sunspot.yml
  171. +150 −0 db/schema.rb
  172. +13 −0 db/seeds.rb
  173. +109 −0 db/seeds/departments.rb
  174. +9 −0 db/seeds/system_settings.rb
  175. +2 −0  doc/README_FOR_APP
  176. +98 −0 features/admin/admin_access.feature
  177. +108 −0 features/admin/admin_users_crud.feature
  178. +66 −0 features/admin/all_petitions.feature
  179. +15 −0 features/admin/department_assignment.feature
  180. +69 −0 features/admin/moderator_responds_to_petition.feature
  181. +27 −0 features/admin/petition_requires_response.feature
  182. +41 −0 features/admin/profile.feature
  183. +40 −0 features/admin/reports.feature
  184. +40 −0 features/admin/search_for_petition_by_id.feature
  185. +11 −0 features/admin/search_for_signatures_by_email.feature
  186. +21 −0 features/admin/takedown.feature
  187. +80 −0 features/admin/threshold.feature
  188. +43 −0 features/admin/todolist.feature
  189. +168 −0 features/charlie_creates_a_petition.feature
  190. +27 −0 features/sam_signs_a_petition_with_suzies_email_address.feature
  191. +50 −0 features/step_definitions/admin_search_steps.rb
  192. +15 −0 features/step_definitions/admin_user_steps.rb
  193. +38 −0 features/step_definitions/authentication_steps.rb
  194. +28 −0 features/step_definitions/capybara_ssl_fix.rb
  195. +16 −0 features/step_definitions/common_steps.rb
  196. +20 −0 features/step_definitions/department_assignment_steps.rb
  197. +43 −0 features/step_definitions/department_steps.rb
  198. +198 −0 features/step_definitions/email_steps.rb
  199. +3 −0  features/step_definitions/error_steps.rb
  200. +33 −0 features/step_definitions/feedback_steps.rb
  201. +101 −0 features/step_definitions/inspection_steps.rb
  202. +112 −0 features/step_definitions/moderation_steps.rb
  203. +12 −0 features/step_definitions/navigation_steps.rb
  204. +129 −0 features/step_definitions/petition_steps.rb
  205. +100 −0 features/step_definitions/pickle_steps.rb
  206. +14 −0 features/step_definitions/recaptcha_steps.rb
  207. +52 −0 features/step_definitions/reports_steps.rb
  208. +63 −0 features/step_definitions/resend_confirmation_steps.rb
  209. +34 −0 features/step_definitions/search_steps.rb
  210. +135 −0 features/step_definitions/signature_steps.rb
  211. +7 −0 features/step_definitions/ssl_steps.rb
  212. +10 −0 features/step_definitions/takedown_steps.rb
  213. +5 −0 features/step_definitions/threshold_steps.rb
  214. +9 −0 features/step_definitions/time_steps.rb
  215. +45 −0 features/step_definitions/transforms.rb
  216. +31 −0 features/step_definitions/trending_steps.rb
  217. +36 −0 features/step_definitions/validation_steps.rb
  218. +219 −0 features/step_definitions/web_steps.rb
  219. +4 −0 features/support/custom_env.rb
  220. +58 −0 features/support/env.rb
  221. +43 −0 features/support/hooks.rb
  222. +110 −0 features/support/paths.rb
  223. +24 −0 features/support/pickle.rb
  224. +52 −0 features/support/pickle_extensions.rb
  225. +42 −0 features/support/sections.rb
  226. +39 −0 features/support/selectors.rb
  227. +9 −0 features/support/selenium_browser.rb
  228. +53 −0 features/suzie_resends_confirmation_email.feature
  229. +79 −0 features/suzie_searches_by_department.feature
  230. +213 −0 features/suzie_searches_by_free_text.feature
  231. +25 −0 features/suzie_searches_from_any_page.feature
  232. +21 −0 features/suzie_sees_trending_petitions.feature
  233. +81 −0 features/suzie_signs_a_petition.feature
  234. +43 −0 features/suzie_views_a_petition.feature
  235. +12 −0 features/suzie_views_all_petitions.feature
  236. +9 −0 features/suzie_views_front_page_without_moderated_petitions.feature
  237. +53 −0 features/user_sees_static_stuff.feature
  238. +13 −0 features/user_sends_feedback.feature
  239. +10 −0 jenkins_build.sh
  240. +28 −0 lib/app_config.rb
  241. +27 −0 lib/audit_logger.rb
  242. +79 −0 lib/authentication.rb
  243. +64 −0 lib/email_reminder.rb
  244. +27 −0 lib/email_reminder_logger.rb
  245. +23 −0 lib/feedback.rb
  246. +52 −0 lib/petition_results.rb
  247. +32 −0 lib/rejection_reason.rb
  248. +96 −0 lib/search.rb
  249. +23 −0 lib/search_order.rb
  250. +29 −0 lib/search_results_setup.rb
  251. +55 −0 lib/signature_confirmer.rb
  252. +16 −0 lib/state.rb
  253. +22 −0 lib/sunspot_server_util.rb
  254. 0  lib/tasks/.gitkeep
  255. +29 −0 lib/tasks/ci.rake
  256. +53 −0 lib/tasks/cucumber.rake
  257. +61 −0 lib/tasks/epets.rake
  258. +7 −0 lib/tasks/spec_no_rails.rake
  259. +30 −0 public/404.html
  260. +26 −0 public/422.html
  261. +34 −0 public/500.html
  262. +1 −0  public/api/petitions.json
  263. +1 −0  public/api/petitions/38.json
  264. BIN  public/favicon.ico
  265. BIN  public/images/admin/delete.png
  266. BIN  public/images/admin/tick.gif
  267. BIN  public/images/backgrounds/gray_texture.gif
  268. BIN  public/images/backgrounds/how_it_works.gif
  269. BIN  public/images/backgrounds/tooltip_arrow.gif
  270. BIN  public/images/backgrounds/wizard_pagination.gif
  271. BIN  public/images/buttons/arrow_right.gif
  272. BIN  public/images/buttons/close.gif
  273. BIN  public/images/buttons/link_arrow.gif
  274. BIN  public/images/buttons/magnify.gif
  275. BIN  public/images/buttons/purple_white_arrow_left.gif
  276. BIN  public/images/buttons/tweet.png
  277. BIN  public/images/buttons/white_purple_arrow_left.gif
  278. BIN  public/images/buttons/white_purple_arrow_right.gif
  279. BIN  public/images/how_it_works/checked.jpg
  280. BIN  public/images/how_it_works/connector_1.gif
  281. BIN  public/images/how_it_works/connector_2.gif
  282. BIN  public/images/how_it_works/connector_2_mobile.gif
  283. BIN  public/images/how_it_works/connector_3.gif
  284. BIN  public/images/how_it_works/connector_3_mobile.gif
  285. BIN  public/images/how_it_works/connector_4.gif
  286. BIN  public/images/how_it_works/connector_4_mobile.gif
  287. BIN  public/images/how_it_works/connector_5.gif
  288. BIN  public/images/how_it_works/create_petition.jpg
  289. BIN  public/images/how_it_works/open.jpg
  290. BIN  public/images/how_it_works/search.jpg
  291. BIN  public/images/how_it_works/signature.jpg
  292. BIN  public/images/how_it_works/threshold.jpg
  293. BIN  public/images/icons/audio.jpg
  294. BIN  public/images/icons/close.png
  295. BIN  public/images/icons/facebook.png
  296. BIN  public/images/icons/google.png
  297. BIN  public/images/icons/info.gif
  298. BIN  public/images/icons/linkedin.png
  299. BIN  public/images/icons/new_window.gif
  300. BIN  public/images/icons/refresh.jpg
Sorry, we could not display the entire diff because too many files (402) changed.
19 .gitignore
@@ -0,0 +1,19 @@
+.bundle
+.project
+.rvmrc
+db/*.sqlite3
+log/*
+tmp/
+config/database.yml
+public/cucumber
+public/index.html
+public/javascripts/all.js
+solr/data
+vendor/bundle
+vendor/ruby
+vendor/plugins/query_reviewer
+coverage
+.DS_Store
+*/.DS_Store
+*/*/.DS_Store
+.idea
1  .rspec
@@ -0,0 +1 @@
+--color
4 Capfile
@@ -0,0 +1,4 @@
+load 'deploy' if respond_to?(:namespace) # cap2 differentiator
+Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
+
+load 'config/deploy' # remove this line to skip loading any of the default tasks
73 Gemfile
@@ -0,0 +1,73 @@
+source :rubygems
+
+gem 'rails', '3.0.12'
+
+# Bundle edge Rails instead:
+# gem 'rails', :git => 'git://github.com/rails/rails.git'
+
+gem 'rake', '0.8.7'
+gem 'mysql', '2.8.1'
+gem 'authlogic', '3.0.3'
+gem "will_paginate", "~> 3.0.pre2"
+gem 'sunspot_rails', '~> 1.2.1'
+gem 'tabnav', :git => 'git://github.com/unboxed/tabnav.git'
+gem 'bartt-ssl_requirement', :require => 'ssl_requirement'
+gem 'json'
+gem 'memcache-client'
+gem 'delayed_job', '2.1.4'
+gem 'whenever'
+gem "rabl"
+
+group :development, :test do
+ gem 'rspec-rails'
+ # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
+ # gem 'ruby-debug19', :require => 'ruby-debug'
+ gem 'ruby-debug'
+ gem 'method_info'
+ gem 'capistrano-ext'
+ gem 'evergreen', :require => 'evergreen/rails'
+ gem 'annotate'
+end
+
+group :development do
+ gem 'mongrel'
+ gem 'thin'
+end
+
+group :test do
+ gem 'nokogiri'
+ gem 'shoulda'
+ gem 'pickle'
+ gem 'cucumber-rails', '~> 0.3.2'
+ gem 'database_cleaner'
+ gem 'capybara', '~> 1.1.2'
+ gem 'selenium-webdriver', '~> 2.5.0'
+ gem 'factory_girl_rails'
+ gem 'be_valid_asset', :git => 'git://github.com/unboxed/be_valid_asset.git'
+ gem 'email_spec', :git => 'git://github.com/bmabey/email-spec.git'
+ gem 'chronic', :git => "git://github.com/mojombo/chronic.git"
+ gem 'timecop'
+ gem 'launchy'
+end
+
+# Use unicorn as the web server
+group :production do
+ gem 'unicorn'
+end
+
+# Deploy with Capistrano
+# gem 'capistrano'
+
+
+# Bundle the extra gems:
+# gem 'bj'
+# gem 'nokogiri'
+# gem 'sqlite3-ruby', :require => 'sqlite3'
+# gem 'aws-s3', :require => 'aws/s3'
+
+# Bundle gems for the local environment. Make sure to
+# put test-only gems in this group so their generators
+# and rake tasks are available in development mode:
+# group :development, :test do
+# gem 'webrat'
+# end
289 Gemfile.lock
@@ -0,0 +1,289 @@
+GIT
+ remote: git://github.com/bmabey/email-spec.git
+ revision: 42e64e6c102457088963d90b513757bf98ae6d08
+ specs:
+ email_spec (1.2.1)
+ launchy (~> 2.0)
+ mail (~> 2.2)
+ rspec (~> 2.0)
+
+GIT
+ remote: git://github.com/mojombo/chronic.git
+ revision: f5a247025ab02ced1fd5fbb8d779b34a1fe43331
+ specs:
+ chronic (0.6.7)
+
+GIT
+ remote: git://github.com/unboxed/be_valid_asset.git
+ revision: 36593aa75c7e1991825047cbba7cee11b58a8a9a
+ specs:
+ be_valid_asset (1.1.2)
+ rspec
+
+GIT
+ remote: git://github.com/unboxed/tabnav.git
+ revision: dab8c49b6b4f85d88abbf91ab63a8055113fd44c
+ specs:
+ tabnav (1.1.0)
+ actionpack (>= 2.3.8)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ abstract (1.0.0)
+ actionmailer (3.0.12)
+ actionpack (= 3.0.12)
+ mail (~> 2.2.19)
+ actionpack (3.0.12)
+ activemodel (= 3.0.12)
+ activesupport (= 3.0.12)
+ builder (~> 2.1.2)
+ erubis (~> 2.6.6)
+ i18n (~> 0.5.0)
+ rack (~> 1.2.5)
+ rack-mount (~> 0.6.14)
+ rack-test (~> 0.5.7)
+ tzinfo (~> 0.3.23)
+ activemodel (3.0.12)
+ activesupport (= 3.0.12)
+ builder (~> 2.1.2)
+ i18n (~> 0.5.0)
+ activerecord (3.0.12)
+ activemodel (= 3.0.12)
+ activesupport (= 3.0.12)
+ arel (~> 2.0.10)
+ tzinfo (~> 0.3.23)
+ activeresource (3.0.12)
+ activemodel (= 3.0.12)
+ activesupport (= 3.0.12)
+ activesupport (3.0.12)
+ addressable (2.2.6)
+ annotate (2.4.0)
+ arel (2.0.10)
+ authlogic (3.0.3)
+ activerecord (>= 3.0.7)
+ activerecord (>= 3.0.7)
+ bartt-ssl_requirement (1.4.1)
+ builder (2.1.2)
+ capistrano (2.9.0)
+ highline
+ net-scp (>= 1.0.0)
+ net-sftp (>= 2.0.0)
+ net-ssh (>= 2.0.14)
+ net-ssh-gateway (>= 1.1.0)
+ capistrano-ext (1.2.1)
+ capistrano (>= 1.0.0)
+ capybara (1.1.2)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ selenium-webdriver (~> 2.0)
+ xpath (~> 0.1.4)
+ cgi_multipart_eof_fix (2.5.0)
+ childprocess (0.3.1)
+ ffi (~> 1.0.6)
+ coffee-script (2.2.0)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.2.0)
+ columnize (0.3.6)
+ cucumber (1.1.9)
+ builder (>= 2.1.2)
+ diff-lcs (>= 1.1.2)
+ gherkin (~> 2.9.0)
+ json (>= 1.4.6)
+ term-ansicolor (>= 1.0.6)
+ cucumber-rails (0.3.2)
+ cucumber (>= 0.8.0)
+ daemons (1.1.8)
+ database_cleaner (0.7.1)
+ delayed_job (2.1.4)
+ activesupport (~> 3.0)
+ daemons
+ diff-lcs (1.1.3)
+ erubis (2.6.6)
+ abstract (>= 1.0.0)
+ escape (0.0.4)
+ eventmachine (0.12.10)
+ evergreen (1.0.0)
+ capybara (~> 1.0)
+ coffee-script
+ json_pure
+ launchy
+ sinatra (~> 1.1)
+ execjs (1.3.0)
+ multi_json (~> 1.0)
+ factory_girl (2.5.2)
+ activesupport (>= 2.3.9)
+ factory_girl_rails (1.6.0)
+ factory_girl (~> 2.5.0)
+ railties (>= 3.0.0)
+ fastthread (1.0.7)
+ ffi (1.0.11)
+ gem_plugin (0.2.3)
+ gherkin (2.9.3)
+ json (>= 1.4.6)
+ highline (1.6.11)
+ i18n (0.5.0)
+ json (1.6.6)
+ json_pure (1.6.6)
+ kgio (2.7.4)
+ launchy (2.0.5)
+ addressable (~> 2.2.6)
+ linecache (0.46)
+ rbx-require-relative (> 0.0.4)
+ mail (2.2.19)
+ activesupport (>= 2.3.6)
+ i18n (>= 0.4.0)
+ mime-types (~> 1.16)
+ treetop (~> 1.4.8)
+ memcache-client (1.8.5)
+ method_info (0.1.11)
+ mime-types (1.18)
+ mongrel (1.1.5)
+ cgi_multipart_eof_fix (>= 2.4)
+ daemons (>= 1.0.3)
+ fastthread (>= 1.0.1)
+ gem_plugin (>= 0.2.3)
+ multi_json (1.0.4)
+ mysql (2.8.1)
+ net-scp (1.0.4)
+ net-ssh (>= 1.99.1)
+ net-sftp (2.0.5)
+ net-ssh (>= 2.0.9)
+ net-ssh (2.3.0)
+ net-ssh-gateway (1.1.0)
+ net-ssh (>= 1.99.1)
+ nokogiri (1.5.2)
+ pickle (0.4.10)
+ cucumber (>= 0.8)
+ rake
+ polyglot (0.3.3)
+ pr_geohash (1.0.0)
+ rabl (0.6.12)
+ activesupport (>= 2.3.14)
+ multi_json (~> 1.0)
+ rack (1.2.5)
+ rack-mount (0.6.14)
+ rack (>= 1.0.0)
+ rack-test (0.5.7)
+ rack (>= 1.0)
+ rails (3.0.12)
+ actionmailer (= 3.0.12)
+ actionpack (= 3.0.12)
+ activerecord (= 3.0.12)
+ activeresource (= 3.0.12)
+ activesupport (= 3.0.12)
+ bundler (~> 1.0)
+ railties (= 3.0.12)
+ railties (3.0.12)
+ actionpack (= 3.0.12)
+ activesupport (= 3.0.12)
+ rake (>= 0.8.7)
+ rdoc (~> 3.4)
+ thor (~> 0.14.4)
+ raindrops (0.8.0)
+ rake (0.8.7)
+ rbx-require-relative (0.0.5)
+ rdoc (3.12)
+ json (~> 1.4)
+ rsolr (0.12.1)
+ builder (>= 2.1.2)
+ rspec (2.8.0)
+ rspec-core (~> 2.8.0)
+ rspec-expectations (~> 2.8.0)
+ rspec-mocks (~> 2.8.0)
+ rspec-core (2.8.0)
+ rspec-expectations (2.8.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.8.0)
+ rspec-rails (2.8.1)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ railties (>= 3.0)
+ rspec (~> 2.8.0)
+ ruby-debug (0.10.4)
+ columnize (>= 0.1)
+ ruby-debug-base (~> 0.10.4.0)
+ ruby-debug-base (0.10.4)
+ linecache (>= 0.3)
+ rubyzip (0.9.7)
+ selenium-webdriver (2.5.0)
+ childprocess (>= 0.2.1)
+ ffi (>= 1.0.7)
+ json_pure
+ rubyzip
+ shoulda (2.11.3)
+ sinatra (1.2.8)
+ rack (~> 1.1)
+ tilt (>= 1.2.2, < 2.0)
+ sunspot (1.2.1)
+ escape (= 0.0.4)
+ pr_geohash (~> 1.0)
+ rsolr (= 0.12.1)
+ sunspot_rails (1.2.1)
+ nokogiri
+ sunspot (= 1.2.1)
+ term-ansicolor (1.0.7)
+ thin (1.3.1)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ thor (0.14.6)
+ tilt (1.3.3)
+ timecop (0.3.5)
+ treetop (1.4.10)
+ polyglot
+ polyglot (>= 0.3.1)
+ tzinfo (0.3.33)
+ unicorn (4.3.0)
+ kgio (~> 2.6)
+ rack
+ raindrops (~> 0.7)
+ whenever (0.7.2)
+ activesupport (>= 2.3.4)
+ chronic (~> 0.6.3)
+ will_paginate (3.0.3)
+ xpath (0.1.4)
+ nokogiri (~> 1.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ annotate
+ authlogic (= 3.0.3)
+ bartt-ssl_requirement
+ be_valid_asset!
+ capistrano-ext
+ capybara (~> 1.1.2)
+ chronic!
+ cucumber-rails (~> 0.3.2)
+ database_cleaner
+ delayed_job (= 2.1.4)
+ email_spec!
+ evergreen
+ factory_girl_rails
+ json
+ launchy
+ memcache-client
+ method_info
+ mongrel
+ mysql (= 2.8.1)
+ nokogiri
+ pickle
+ rabl
+ rails (= 3.0.12)
+ rake (= 0.8.7)
+ rspec-rails
+ ruby-debug
+ selenium-webdriver (~> 2.5.0)
+ shoulda
+ sunspot_rails (~> 1.2.1)
+ tabnav!
+ thin
+ timecop
+ unicorn
+ whenever
+ will_paginate (~> 3.0.pre2)
10 README
@@ -0,0 +1,10 @@
+ePets
+
+For setting up a sysadmin user, run 'rake epets:add_sysadmin_user'
+
+To start a solr instance, run 'rake sunspot:solr:start'
+To index the models, run 'rake sunspot:reindex'
+
+To get the selenium tests running, make sure you're running firefox 7 as your firefox browser or alternatively, have firefox 7 in one of the following locations:
+/Applications/Firefox7.app/Contents/MacOS/firefox-bin or
+/Applications/Firefox 7.app/Contents/MacOS/firefox-bin
10 Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require File.expand_path('../config/application', __FILE__)
+require 'rake'
+
+Epets::Application.load_tasks
+
+task(:default).clear_prerequisites
+task :default => [:spec_no_rails, :spec, :'spec:javascripts', :'cucumber']
9 app/controllers/admin/admin_controller.rb
@@ -0,0 +1,9 @@
+class Admin::AdminController < ApplicationController
+ include Authentication
+ # makes all actions ssl protected
+ ssl_exceptions
+
+ before_filter :require_admin_and_check_for_password_change
+ layout 'admin'
+
+end
68 app/controllers/admin/admin_users_controller.rb
@@ -0,0 +1,68 @@
+class Admin::AdminUsersController < Admin::AdminController
+ before_filter :require_sysadmin
+ before_filter :assign_departments, :only => [:new, :edit]
+
+ def index
+ @users = AdminUser.by_name.paginate(:page => params[:page], :per_page => 20)
+ end
+
+ def new
+ @user = AdminUser.new
+ end
+
+ def create
+ @user = AdminUser.create(params[:admin_user])
+ update_departments(@user)
+ if @user.save
+ flash[:notice] = "User was successfully created"
+ redirect_to admin_admin_users_path
+ else
+ assign_departments
+ render :action => 'new'
+ end
+ end
+
+ def edit
+ @user = AdminUser.find(params[:id])
+ end
+
+ def update
+ @user = AdminUser.find(params[:id])
+
+ update_departments(@user)
+ if @user.update_attributes(params[:admin_user])
+ flash[:notice] = "User was successfully updated"
+ redirect_to admin_admin_users_path
+ else
+ assign_departments
+ render :action => 'edit'
+ end
+ end
+
+ def destroy
+ @user = AdminUser.find(params[:id])
+
+ # only destroy if user is not the logged in user and there are at least 2 users
+ if @user == current_user
+ flash[:error] = "You are not allowed to delete yourself!"
+ elsif AdminUser.count < 2
+ flash[:error] = "There needs to be at least 1 admin user"
+ else
+ @user.destroy
+ end
+ redirect_to admin_admin_users_path
+ end
+
+ protected
+
+ def update_departments(user)
+ department_ids = params[:department_ids].values
+ # loop through backwards so deleting has no effect on subsequent elements
+ user.departments.reverse.each do |department|
+ user.departments.delete(department) unless department_ids.include?(department.id.to_s)
+ end
+ department_ids.map { |department_id| Department.find_by_id(department_id) }.compact.each do |department|
+ user.departments << department unless user.departments.include?(department)
+ end
+ end
+end
121 app/controllers/admin/petitions_controller.rb
@@ -0,0 +1,121 @@
+class Admin::PetitionsController < Admin::AdminController
+ before_filter :assign_departments, :only => [:edit, :update]
+
+ respond_to :html
+
+ def index
+ if current_user.is_a_sysadmin? or current_user.is_a_threshold?
+ @petitions = Petition
+ else
+ @petitions = Petition.for_departments(current_user.departments)
+ end
+ @petitions = @petitions.moderated.order(:signature_count)
+ @petitions = @petitions.for_state(params[:state]) unless params[:state].blank?
+ @petitions = @petitions.paginate(:page => params[:page], :per_page => params[:per_page] || 20)
+ end
+
+ def show
+ @petition = Petition.find(params[:id])
+ end
+
+ def edit
+ @petition = Petition.for_state(Petition::VALIDATED_STATE).find(params[:id])
+ end
+
+ def update
+ @petition = Petition.for_state(Petition::VALIDATED_STATE).find(params[:id])
+ user_action = params['commit']
+ case user_action
+ when 'Publish this petition'
+ publish
+ when 'Re-assign'
+ reassign
+ when 'Reject'
+ reject
+ else
+ raise "Don't know how to do this action"
+ end
+
+ respond_with @petition, :location => admin_root_path
+ end
+
+ def threshold
+ @petitions = Petition.threshold.order(:signature_count).paginate(:page => params[:page], :per_page => params[:per_page] || 20)
+ end
+
+ def edit_response
+ @petition = Petition.find(params[:id])
+ end
+
+ def update_response
+ @petition = Petition.find(params[:id])
+ @petition.internal_response = params[:petition][:internal_response]
+ @petition.response_required = params[:petition][:response_required]
+ @petition.response = params[:petition][:response]
+ @petition.email_signees = params[:petition][:email_signees] == '1'
+ if @petition.save
+ # if email signees has been selected then set up a delayed job to email out to all signees who opted in
+ if @petition.email_signees
+ # run the job at some random point beween midnight and 4 am
+ requested_at = Time.zone.now
+ @petition.update_attribute(:email_requested_at, requested_at)
+ Delayed::Job.enqueue EmailThresholdResponseJob.new(@petition.id, requested_at, Petition, PetitionMailer), :run_at => 1.day.from_now.at_midnight + rand(240).minutes + rand(60).seconds
+ end
+
+ redirect_to threshold_admin_petitions_path
+ else
+ render :edit_response
+ end
+ end
+
+ def take_down
+ @petition = Petition.find(params[:id])
+ reject
+ respond_with @petition, :location => admin_petitions_path
+ end
+
+ def edit_internal_response
+ @petition = Petition.find(params[:id])
+ end
+
+ def update_internal_response
+ @petition = Petition.find(params[:id])
+ @petition.internal_response = params[:petition][:internal_response]
+ @petition.response_required = params[:petition][:response_required]
+ @petition.save!
+ redirect_to admin_petitions_path
+ end
+
+ protected
+
+ def publish
+ @petition.state = Petition::OPEN_STATE
+ @petition.open_at = Time.zone.now
+ @petition.closed_at = @petition.duration.to_i.months.from_now
+ @petition.save!
+ PetitionMailer.notify_creator_that_petition_is_published(@petition.creator_signature).deliver
+ end
+
+ def reassign
+ department = Department.find(params[:petition][:department_id])
+ @petition.reassign!(department)
+ end
+
+ def reject
+ @petition.rejection_code = params[:petition][:rejection_code]
+ @petition.rejection_text = params[:petition][:rejection_text]
+
+ # if a petition is rejected for a reason that means it should be hidden, then set the state accordingly
+ reason = RejectionReason.find_by_code(@petition.rejection_code)
+ if reason and ! reason.published
+ @petition.state = Petition::HIDDEN_STATE
+ else
+ @petition.state = Petition::REJECTED_STATE
+ end
+
+ # send rejection email
+ if @petition.save
+ PetitionMailer.notify_creator_that_petition_is_rejected(@petition.creator_signature).deliver
+ end
+ end
+end
24 app/controllers/admin/profile_controller.rb
@@ -0,0 +1,24 @@
+class Admin::ProfileController < Admin::AdminController
+ skip_before_filter :require_admin_and_check_for_password_change
+ before_filter :require_admin
+
+ def edit
+
+ end
+
+ def update
+ # reset attributes that could be forcing a user to change their password
+ current_user.password_changed_at = Time.zone.now
+ current_user.force_password_reset = false
+
+ if ! current_user.valid_password?(params[:current_password])
+ current_user.errors.add(:current_password, "is incorrect")
+ elsif params[:current_password] == params[:admin_user][:password]
+ current_user.errors.add(:password, "is the same as the current password")
+ elsif params[:admin_user][:password].present? and current_user.update_attributes(params[:admin_user])
+ flash[:notice] = "Password was successfully updated"
+ redirect_to admin_root_path and return
+ end
+ render :edit
+ end
+end
12 app/controllers/admin/reports_controller.rb
@@ -0,0 +1,12 @@
+class Admin::ReportsController < Admin::AdminController
+
+ def index
+ @counts = Petition.counts_by_state
+ @departments = Department.by_petition_count
+
+ departments_for_trending = current_user.can_see_all_trending_petitions? ? Department.all : current_user.departments
+ @number_of_days_to_trend = params[:number_of_days_to_trend].present? ? params[:number_of_days_to_trend].to_i : 1
+ @trending_petitions = Petition.for_departments(departments_for_trending).trending(@number_of_days_to_trend)
+ end
+
+end
37 app/controllers/admin/searches_controller.rb
@@ -0,0 +1,37 @@
+class Admin::SearchesController < Admin::AdminController
+
+ def new
+ @query = ""
+ end
+
+ def result
+ @query = params[:search][:query]
+ if (/^\d+$/ =~ @query)
+ find_by_id(@query)
+ else
+ @signatures = Signature.for_email(@query).paginate(:page => params[:page], :per_page => 20)
+ end
+ end
+
+ private
+ def find_by_id(query)
+ begin
+ petition = Petition.find(query.to_i)
+ redirect_to path_for_petition_state(petition)
+ rescue ActiveRecord::RecordNotFound
+ flash[:error] = "Cannot find petition with id: #{query}"
+ redirect_to new_admin_search_path
+ end
+ end
+
+ def path_for_petition_state(petition)
+ return admin_petition_path(petition) unless petition.editable_by?(current_user)
+ return edit_admin_petition_path(petition) if petition.awaiting_moderation?
+
+ if (petition.response_editable_by?(current_user))
+ edit_response_admin_petition_path(petition)
+ else
+ edit_internal_response_admin_petition_path(petition)
+ end
+ end
+end
11 app/controllers/admin/todolist_controller.rb
@@ -0,0 +1,11 @@
+class Admin::TodolistController < Admin::AdminController
+
+ def index
+ if current_user.is_a_sysadmin? or current_user.is_a_threshold?
+ scope = Petition
+ else
+ scope = Petition.for_departments(current_user.departments)
+ end
+ @petitions = scope.for_state(Petition::VALIDATED_STATE).order(:created_at).paginate(:page => params[:page], :per_page => params[:per_page] || 20)
+ end
+end
30 app/controllers/admin/user_sessions_controller.rb
@@ -0,0 +1,30 @@
+class Admin::UserSessionsController < Admin::AdminController
+ skip_before_filter :require_admin_and_check_for_password_change
+
+ def new
+ @user_session = AdminUserSession.new
+ end
+
+ def create
+ @user_session = AdminUserSession.new(params[:admin_user_session])
+ if @user_session.save
+ redirect_to_target_or_default
+
+ # if failed logins are above the specified level, then authlogic disables account
+ # so we need to display appropriate error message
+ elsif @user_session.errors[:base].size > 0
+ flash.now[:error] = @user_session.errors[:base][0]
+ render :new
+ else
+ flash.now[:error] = "Invalid email/password combination"
+ render :new
+ end
+ end
+
+ def destroy
+ current_session.destroy if logged_in?
+ flash[:notice] = "You have been logged out."
+ redirect_to admin_login_path
+ end
+end
+
31 app/controllers/application_controller.rb
@@ -0,0 +1,31 @@
+class ApplicationController < ActionController::Base
+ include ::SslRequirement
+ protect_from_forgery
+ helper :all
+
+ protected
+
+ def assign_departments
+ @departments = Department.by_name
+ end
+
+ def sanitise_page_param(param = :page)
+ params[param] = params[param].to_i
+ params[param] = 1 if params[param] < 1
+ end
+
+ def self.caches_action_with_params(*actions)
+ options = actions.extract_options!
+ options[:cache_path] = Proc.new do |c|
+ p = {}
+ p[:page] = c.params[:page].to_i unless c.params[:page].blank?
+ p[:order] = c.params[:order] unless c.params[:order].blank?
+ p[:sort] = c.params[:sort] unless c.params[:sort].blank?
+ p[:state] = c.params[:state] unless c.params[:state].blank?
+ p
+ end
+ options[:expires_in] ||= DEFAULT_CACHE_EXPIRY
+ actions << options
+ self.caches_action(*actions)
+ end
+end
21 app/controllers/departments_controller.rb
@@ -0,0 +1,21 @@
+class DepartmentsController < ApplicationController
+ before_filter :sanitise_page_param, :only => :show
+ caches_action :index, :info, :expires_in => DEFAULT_CACHE_EXPIRY
+
+ respond_to :html
+
+ include SearchResultsSetup
+
+ def index
+ respond_with @departments = Department.all
+ end
+
+ def show
+ @department = Department.find(params[:id])
+ results_for(@department.petitions)
+ end
+
+ def info
+ respond_with @departments = Department.all
+ end
+end
21 app/controllers/feedback_controller.rb
@@ -0,0 +1,21 @@
+class FeedbackController < ApplicationController
+ ssl_required :index, :create, :thanks
+
+ respond_to :html
+ def index
+ respond_with @feedback = Feedback.new
+ end
+
+ def create
+ @feedback = Feedback.new(params[:feedback])
+ if @feedback.valid?
+ FeedbackMailer.send_feedback(@feedback).deliver
+ redirect_to thanks_feedback_path
+ else
+ render 'index'
+ end
+ end
+
+ def thanks
+ end
+end
90 app/controllers/petitions_controller.rb
@@ -0,0 +1,90 @@
+class PetitionsController < ApplicationController
+ before_filter :assign_departments, :only => [:new, :create]
+ before_filter :sanitise_page_param
+ caches_action_with_params :index
+
+ caches_page :show
+
+ ssl_required :new, :create
+ ssl_allowed :thank_you
+
+ respond_to :html
+
+ include SearchResultsSetup
+
+ def index
+ results_for(Petition)
+ end
+
+ def new
+ @petition = Petition.new(:title => params[:title])
+ @petition.build_creator_signature(:country => 'United Kingdom')
+ @start_on_section = 0;
+ respond_with @petition
+ end
+
+ def show
+ respond_with @petition = Petition.visible.find(params[:id])
+ end
+
+ def create
+ params[:petition][:creator_signature_attributes][:humanity] = Captcha.verify(params[:captcha_response_field], params[:captcha_string])
+
+ @petition = Petition.new(params[:petition])
+ @petition.creator_signature.email.strip!
+ if @petition.creator_signature
+ @petition.creator_signature.ip_address = request.remote_ip
+ @petition.creator_signature.notify_by_email = true
+ end
+
+ @petition.title.strip!
+ if @petition.save
+ send_email_to_verify_petition_creator(@petition)
+ redirect_to thank_you_petition_path(@petition, :secure => true)
+ else
+ if @petition.errors[:title].any? ||
+ @petition.errors[:department].any? ||
+ @petition.errors[:description].any?
+ @start_on_section = 0
+ elsif @petition.creator_signature.errors[:name].any? ||
+ @petition.creator_signature.errors[:email].any? ||
+ @petition.creator_signature.errors[:uk_citizenship].any? ||
+ @petition.creator_signature.errors[:address].any? ||
+ @petition.creator_signature.errors[:town].any? ||
+ @petition.creator_signature.errors[:postcode].any? ||
+ @petition.creator_signature.errors[:country].any?
+ @start_on_section = 1
+ elsif @petition.creator_signature.errors[:humanity].any?
+ @start_on_section = 2
+ else
+ @start_on_section = 0
+ end
+ render :new
+ end
+ end
+
+ def check
+ end
+
+ def check_results
+ @petition_search = PetitionResults.new(
+ :search_term => params[:search],
+ :state => params[:state],
+ :per_page => 10,
+ :page_number => params[:page],
+ :sort => params[:sort],
+ :order => params[:order]
+ )
+ end
+
+ def resend_confirmation_email
+ @petition = Petition.visible.find(params[:id])
+ SignatureConfirmer.new(@petition, params[:confirmation_email], PetitionMailer, Authlogic::Regex.email).confirm!
+ end
+
+ protected
+
+ def send_email_to_verify_petition_creator(petition)
+ PetitionMailer.email_confirmation_for_creator(petition.creator_signature).deliver
+ end
+end
14 app/controllers/search_controller.rb
@@ -0,0 +1,14 @@
+class SearchController < ApplicationController
+ respond_to :html
+
+ def search
+ @petition_search = PetitionResults.new(
+ :search_term => params[:q],
+ :state => params[:state],
+ :per_page => 20,
+ :page_number => params[:page],
+ :sort => params[:sort],
+ :order => params[:order]
+ )
+ end
+end
61 app/controllers/signatures_controller.rb
@@ -0,0 +1,61 @@
+class SignaturesController < ApplicationController
+ before_filter :retrieve_petition, :only => [:new, :create, :thank_you, :signed]
+ ssl_required :new, :create
+ ssl_allowed :thank_you, :verify
+ include ActionView::Helpers::NumberHelper
+
+ respond_to :html
+
+ def new
+ @signature = Signature.new(:petition => @petition, :country => "United Kingdom")
+ end
+
+ def create
+ params[:signature][:humanity] = Captcha.verify(params[:captcha_response_field], params[:captcha_string])
+
+ @signature = Signature.new(params[:signature])
+ @signature.email.strip!
+ @signature.petition = @petition
+ if (@signature.save)
+ send_email_to_petition_signer(@signature)
+ end
+ respond_with @signature, :location => thank_you_petition_signature_path(@petition)
+ end
+
+ def verify
+ @signature = Signature.find(params[:id])
+
+ if @signature.perishable_token == params[:token]
+ @signature.perishable_token = nil
+ @signature.state = Signature::VALIDATED_STATE
+ @signature.save(:validate => false)
+
+ # if signature is that of the petition's creator, mark the petition as validated
+ if @signature.petition.creator_signature == @signature
+ @signature.petition.state = Petition::VALIDATED_STATE
+ @signature.petition.save!
+
+ # else signature is from an ordinary signee so let's redirect to petition's page
+ else
+ redirect_to signed_petition_signature_path(@signature.petition) and return
+ end
+ else
+ # We've found the signature, but it's already been verified.
+ if @signature.state == Signature::VALIDATED_STATE
+ flash[:notice] = "Thank you. Your signature has already been added to the <span class='nowrap'>e-petition</span>.".html_safe
+ redirect_to signed_petition_signature_path(@signature.petition) and return
+ else
+ raise ActiveRecord::RecordNotFound
+ end
+ end
+ end
+
+ private
+ def retrieve_petition
+ @petition = Petition.visible.find(params[:petition_id])
+ end
+
+ def send_email_to_petition_signer(signature)
+ PetitionMailer.email_confirmation_for_signer(signature).deliver
+ end
+end
11 app/controllers/static_pages_controller.rb
@@ -0,0 +1,11 @@
+class StaticPagesController < ApplicationController
+ caches_page :accessibility, :how_it_works, :terms_and_conditions, :privacy_policy, :crown_copyright, :faq
+ caches_action :home, :expires_in => 5.minutes
+
+ def home
+ all_trending_petitions = TrendingPetition.order("signatures_in_last_hour desc").limit(12)
+ @trending_petitions = all_trending_petitions[0..5]
+ @additional_petitions = all_trending_petitions[6..11]
+ end
+
+end
54 app/helpers/admin_form_builder.rb
@@ -0,0 +1,54 @@
+class AdminFormBuilder < ActionView::Helpers::FormBuilder
+
+ [:text_field, :text_area, :password_field].each do |field_type|
+ class_eval <<-end_src, __FILE__, __LINE__
+ # Keep an alias to the original so we can refer to them in specialised versions of the standard helpers
+ alias :original_#{field_type} :#{field_type}
+ def #{field_type}(field, options = {})
+ options = options.dup
+ wrapper_class = add_css_class(options.delete(:wrapper_class), 'form_field')
+ field_suffix = options.delete(:field_suffix)
+ body = ActiveSupport::SafeBuffer.new
+ body << label(field, options.delete(:label), :class => options.delete(:label_class))
+ body << @template.mandatory_field().html_safe if options.delete(:is_mandatory)
+ body << "<br/>\n".html_safe << super
+ body << ' ' << field_suffix if field_suffix
+ @template.content_tag(:p, body, :class => wrapper_class)
+ end
+ end_src
+ end
+
+ alias :original_check_box :check_box
+ def check_box(field, options = {}, checked_value = "1", unchecked_value = "0")
+ options = options.dup
+ label = label(field, options.delete(:label), :class => options.delete(:label_class))
+ label += @template.mandatory_field() if options.delete(:is_mandatory)
+ wrapper_class = add_css_class(options.delete(:wrapper_class), 'form_field')
+ @template.content_tag(:p, super + label, :class => wrapper_class)
+ end
+
+ def label(field, text = nil, options = {})
+ text ||= object.class.human_attribute_name(field) unless object.nil?
+ super
+ end
+
+ def datetime_text_field(field, options = {})
+ text_field(field, options.reverse_merge(:field_suffix => '(yyyy-mm-dd hh:mm:ss)'))
+ end
+
+ def image_file_field(field, options = {})
+ options = options.dup
+ if object.send(field).file?
+ options[:preview_content] = @template.image_tag( object.send(field).url(:thumbnail), :class => "thumbnail" )
+ end
+ file_field(field, options)
+ end
+
+ private
+
+ def add_css_class(classes, new_class)
+ classes ||= ''
+ classes += " #{new_class}"
+ classes.strip
+ end
+end
2  app/helpers/admin_form_helper.rb
@@ -0,0 +1,2 @@
+module AdminFormHelper
+end
25 app/helpers/admin_helper.rb
@@ -0,0 +1,25 @@
+module AdminHelper
+
+ def save_button(resource)
+ submit_tag 'Save'
+ end
+
+ def mandatory_field()
+ return '<span class="mandatory">&nbsp;*</span>'
+ end
+
+ def cms_delete_link(model, options = {})
+ options[:model_name] ||= model.name
+ options[:url] ||= resource_path(model)
+ link_to image_tag('admin/delete.png', :size => "16x16", :alt => "Delete"), options[:url],
+ :confirm => "WARNING: This action cannot be undone.\nAre you sure you want to delete #{h options[:model_name]}?",
+ :method => :delete
+ end
+
+ def setup_admin_user(admin_user)
+ 4.times do
+ admin_user.departments.build
+ end
+ admin_user
+ end
+end
29 app/helpers/application_helper.rb
@@ -0,0 +1,29 @@
+module ApplicationHelper
+ def increment(amount = 1)
+ @counter ||= 0
+ @counter += amount
+ end
+
+ def new_window_link_to(label, path, options = {})
+ link_to raw(label + ' <span class="new_window_icon">This link opens in a new window</span>'), path, options.merge(:target => '_blank')
+ end
+
+ def info_link_to(path, options = {})
+ options[:class] = ("info_link " + options[:class]).strip
+ link_to "Click for more information", path, {:title => 'Click for more information'}.merge(options)
+ end
+
+ def http_prefix
+ request.ssl? ? "https://" : "http://"
+ end
+
+ RESULT_LIST_OPTIONS = {
+ "open" => "Listed below are any e-petitions that can be signed.",
+ "closed" => "Listed below are any e-petitions that have ended. These can no longer be signed.",
+ "rejected" => "Listed below are the e-petitions that failed to meet the terms and conditions.",
+ }
+
+ def results_list_label(state)
+ RESULT_LIST_OPTIONS[state]
+ end
+end
14 app/helpers/captcha_helper.rb
@@ -0,0 +1,14 @@
+module CaptchaHelper
+
+ def captcha_image(random_string)
+ image_tag "https://image.captchas.net/?client=#{Captcha::USERNAME}&random=#{random_string}&color=781D7E&alphabet=#{Captcha::ALPHABET}",
+ :id => 'captcha_image'
+ end
+
+ def captcha_audio(random_string)
+ link_to 'Play audio challenge',
+ "https://audio.captchas.net/?client=#{Captcha::USERNAME}&random=#{random_string}&alphabet=#{Captcha::ALPHABET}",
+ :id => 'captcha_audio', :target => '_blank'
+ end
+
+end
24 app/helpers/date_time_helper.rb
@@ -0,0 +1,24 @@
+module DateTimeHelper
+
+ def date_time_format(date_time)
+ date_time.strftime("%d-%m-%Y %H:%M") if date_time
+ end
+
+ def date_format(date_time)
+ date_time.to_s(:dotted_short_date) if date_time
+ end
+
+ def date_format_admin(date_time)
+ date_time.strftime("%d-%m-%Y") if date_time
+ end
+
+ def local_date_time_format(date_time)
+ return unless date_time
+ date_time.in_time_zone.strftime("%d/%m/%Y %H:%M")
+ end
+
+ def last_updated_at_time(date_time)
+ return unless date_time
+ date_time.in_time_zone.strftime("%H:%M %Z")
+ end
+end
2  app/helpers/feedback_helper.rb
@@ -0,0 +1,2 @@
+module FeedbackHelper
+end
247 app/helpers/form_helper.rb
@@ -0,0 +1,247 @@
+module FormHelper
+ def form_row opts={}, &block
+ css_classes = ['row']
+ css_classes.push opts[:class] if opts[:class]
+ css_classes.push 'invalid_row' if opts[:for] && opts[:for][0].errors[opts[:for][1]].any?
+ content_tag :div, capture(&block), :class => css_classes.join(' ')
+ end
+
+ def petition_durations
+ [["3 months", "3"], ["6 months", "6"], ["9 months", "9"], ["12 months", "12"]]
+ end
+
+ def countries_for_select
+ [
+ 'Afghanistan',
+ 'Albania',
+ 'Algeria',
+ 'American Samoa',
+ 'Andorra',
+ 'Angola',
+ 'Anguilla (British Overseas Territory)',
+ 'Antigua and Barbuda',
+ 'Argentina',
+ 'Armenia',
+ 'Aruba/Dutch Caribbean',
+ 'Ascension Island (British Overseas Territory)',
+ 'Australia',
+ 'Austria',
+ 'Azerbaijan',
+ 'Bahamas',
+ 'Bahrain',
+ 'Bangladesh',
+ 'Barbados',
+ 'Belarus',
+ 'Belgium',
+ 'Belize',
+ 'Benin',
+ 'Bermuda (British Overseas Territory)',
+ 'Bhutan',
+ 'Bolivia',
+ 'Bosnia and Herzegovina',
+ 'Botswana',
+ 'Brazil',
+ 'British Antarctic Territory',
+ 'British Indian Ocean Territory',
+ 'British Virgin Islands (British Overseas Territory)',
+ 'Brunei',
+ 'Bulgaria',
+ 'Burkina Faso',
+ 'Burma',
+ 'Burundi',
+ 'Cambodia',
+ 'Cameroon',
+ 'Canada',
+ 'Cape Verde',
+ 'Cayman Islands (British Overseas Territory)',
+ 'Central African Republic',
+ 'Chad',
+ 'Chile',
+ 'China',
+ 'Colombia',
+ 'Comoros',
+ 'Congo',
+ 'Congo (Democratic Republic)',
+ 'Costa Rica',
+ 'Croatia',
+ 'Cuba',
+ 'Cyprus',
+ 'Czech Republic',
+ 'Denmark',
+ 'Djibouti',
+ 'Dominica, Commonwealth of',
+ 'Dominican Republic',
+ 'East Timor (Timor-Leste)',
+ 'Ecuador',
+ 'Egypt',
+ 'El Salvador',
+ 'Equatorial Guinea',
+ 'Eritrea',
+ 'Estonia',
+ 'Ethiopia',
+ 'Falkland Islands (British Overseas Territory)',
+ 'Fiji',
+ 'Finland',
+ 'France',
+ 'French Guiana',
+ 'French Polynesia',
+ 'Gabon',
+ 'Gambia',
+ 'Georgia',
+ 'Germany',
+ 'Ghana',
+ 'Gibraltar',
+ 'Greece',
+ 'Grenada',
+ 'Guadeloupe',
+ 'Guatemala',
+ 'Guinea',
+ 'Guinea-Bissau',
+ 'Guyana',
+ 'Haiti',
+ 'Honduras',
+ 'Hong Kong',
+ 'Hungary',
+ 'Iceland',
+ 'India',
+ 'Indonesia',
+ 'Iran',
+ 'Iraq',
+ 'Ireland',
+ 'Israel and the Occupied Palestinian Territories',
+ 'Italy',
+ 'Ivory Coast (Cote d''Ivoire)',
+ 'Jamaica',
+ 'Japan',
+ 'Jordan',
+ 'Kazakhstan',
+ 'Kenya',
+ 'Kiribati',
+ 'South Korea',
+ 'North Korea',
+ 'Kosovo',
+ 'Kuwait',
+ 'Kyrgyzstan',
+ 'Laos',
+ 'Latvia',
+ 'Lebanon',
+ 'Lesotho',
+ 'Liberia',
+ 'Libya',
+ 'Liechtenstein',
+ 'Lithuania',
+ 'Luxembourg',
+ 'Macao',
+ 'Macedonia',
+ 'Madagascar',
+ 'Malawi',
+ 'Malaysia',
+ 'Maldives',
+ 'Mali',
+ 'Malta',
+ 'Marshall Islands',
+ 'Martinique',
+ 'Mauritania',
+ 'Mauritius',
+ 'Mayotte',
+ 'Mexico',
+ 'Micronesia',
+ 'Moldova',
+ 'Monaco',
+ 'Mongolia',
+ 'Montenegro',
+ 'Montserrat (British Overseas Territory)',
+ 'Morocco',
+ 'Mozambique',
+ 'Namibia',
+ 'Nauru',
+ 'Nepal',
+ 'Netherlands',
+ 'New Caledonia',
+ 'New Zealand',
+ 'Nicaragua',
+ 'Niger',
+ 'Nigeria',
+ 'Norway',
+ 'Oman',
+ 'Pakistan',
+ 'Palau',
+ 'Panama',
+ 'Papua New Guinea',
+ 'Paraguay',
+ 'Peru',
+ 'Philippines',
+ 'Pitcairn',
+ 'Poland',
+ 'Portugal',
+ 'Qatar',
+ 'Romania',
+ 'Russian Federation',
+ 'Rwanda',
+ 'Réunion',
+ 'Samoa',
+ 'Saudi Arabia',
+ 'Senegal',
+ 'Serbia',
+ 'Seychelles',
+ 'Sierra Leone',
+ 'Singapore',
+ 'Slovakia',
+ 'Slovenia',
+ 'Solomon Islands',
+ 'Somalia',
+ 'South Africa',
+ 'South Georgia & South Sandwich Islands',
+ 'Spain',
+ 'Sri Lanka',
+ 'St Helena',
+ 'St Kitts and Nevis',
+ 'St Lucia',
+ 'St Pierre & Miquelon',
+ 'St Vincent and the Grenadines',
+ 'Sudan',
+ 'Suriname',
+ 'Swaziland',
+ 'Sweden',
+ 'Switzerland',
+ 'Syria',
+ 'São Tomé and Principe',
+ 'Taiwan',
+ 'Tajikistan',
+ 'Tanzania',
+ 'Thailand',
+ 'Togo',
+ 'Tonga',
+ 'Trinidad and Tobago',
+ 'Tristan da Cunha',
+ 'Tunisia',
+ 'Turkey',
+ 'Turkmenistan',
+ 'Turks & Caicos Islands (British Overseas Territory)',
+ 'Tuvalu',
+ 'Uganda',
+ 'Ukraine',
+ 'United Arab Emirates',
+ 'United Kingdom',
+ 'United States',
+ 'Uruguay',
+ 'Uzbekistan',
+ 'Vanuatu',
+ 'Venezuela',
+ 'Vietnam',
+ 'Wallis & Futuna',
+ 'Western Sahara',
+ 'Yemen',
+ 'Zambia',
+ 'Zimbabwe'
+ ]
+ end
+
+ def error_messages_for_field(object, field_name, opts={})
+ unless object.errors[field_name].empty?
+ return_string = "<div class=\"errors#{' server_only_validation' if opts[:server_only_validation]}\">"
+ return_string += object.errors[field_name].join " "
+ raw(return_string + '</div>')
+ end
+ end
+end
5 app/helpers/link_helper.rb
@@ -0,0 +1,5 @@
+module LinkHelper
+ def linkify(url)
+ link_to url, url
+ end
+end
26 app/helpers/search_helper.rb
@@ -0,0 +1,26 @@
+module SearchHelper
+ def search_count_for(state)
+ count = @petition_search.state_counts[state]
+ "<span class='count'>(#{number_with_delimiter(count)})</span>"
+ end
+
+ def sort_link_tag link_text, field_name, options={}
+ default_order = options[:default_order] || 'asc'
+
+ if params[:sort] == field_name.to_s
+ link_display = (params[:order] == default_order) ? 'normal' : 'inverse'
+ next_order = (params[:order] == 'asc') ? 'desc' : 'asc'
+ is_active = true
+ else
+ link_display = 'normal'
+ next_order = default_order
+ is_active = false
+ end
+
+ link_to link_text,
+ url_for(params.merge(:sort => field_name,
+ :order => next_order)),
+ :class => "#{ 'active_' if is_active}search_#{link_display}",
+ :title => options[:title]
+ end
+end
49 app/jobs/email_threshold_response_job.rb
@@ -0,0 +1,49 @@
+class PleaseRetryEmailJob < Exception
+end
+
+class EmailThresholdResponseJob < Struct.new(:petition_id, :email_requested_at, :petition_model, :mailer)
+ def perform
+ petition = petition_model.find(petition_id)
+ logger(petition_id).info("Starting job for petition '#{petition.title}' with email requested at : #{petition.email_requested_at}")
+
+ if petition.email_requested_at.to_i != email_requested_at.to_i
+ return
+ end
+
+ i = 1
+ petition.need_emailing.find_each do |signature|
+ begin
+ mailer.notify_signer_of_threshold_response(petition, signature).deliver
+ logger(petition_id).info("Email #{i} to #{signature.email} sent")
+ signature.update_attribute(:last_emailed_at, petition.email_requested_at)
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Net::SMTPError => e
+ # try this one again later
+ logger(petition_id).info("#{e.class.name} while sending to: #{signature.email}")
+ end
+ i = i + 1
+ end
+
+ logger(petition_id).info("Finished job for petition '#{petition.title}'")
+ if (petition.need_emailing.count > 0)
+ logger(petition_id).info("Raising error to force a retry of email send of '#{petition.title}'")
+ raise PleaseRetryEmailJob
+ end
+
+ rescue Exception => e
+ # re-raise error so job is re-tried
+ logger(petition_id).error("#{e.class.name} while processing EmailThresholdResponseJob (petition id #{petition_id}): #{e.message}", e.backtrace) unless e.is_a?(PleaseRetryEmailJob)
+ raise e
+ end
+
+ def logger(petition_id)
+ unless @logger
+ logfilename = "threshold_response_for_petition_id_#{petition_id}.log"
+ @logger = AuditLogger.new(Rails.root.join('log', logfilename), 'Email threshold response error')
+ end
+ @logger
+ end
+
+ def failure
+ logger(petition_id).error("EmailThresholdResponseJob has failed for petition '#{petition.title}'. Please see log file threshold_response_for_petition_id_#{petition_id}.log")
+ end
+end
15 app/mailers/admin_mailer.rb
@@ -0,0 +1,15 @@
+class AdminMailer < ActionMailer::Base
+ default :from => AppConfig.email_from
+
+ def admin_email_reminder(admin_user, petitions, new_petitions_count)
+ @admin_user = admin_user
+ @petitions = petitions
+ @new_petitions_count = new_petitions_count
+ mail(:subject => "e-Petitions alert", :to => admin_user.email)
+ end
+
+ def threshold_email_reminder(admin_users, petitions)
+ @petitions = petitions
+ mail(:subject => "e-Petitions alert", :to => admin_users.map(&:email))
+ end
+end
12 app/mailers/feedback_mailer.rb
@@ -0,0 +1,12 @@
+class FeedbackMailer < ActionMailer::Base
+ TO = "petitions@example.com"
+ default :from => AppConfig.email_from
+ layout 'default_mail'
+
+ def send_feedback(feedback)
+ @feedback = feedback
+ mail :to => TO,
+ :subject => "e-petitions: Feedback received",
+ 'Reply-To' => feedback.email
+ end
+end
71 app/mailers/petition_mailer.rb
@@ -0,0 +1,71 @@
+class PetitionMailer < ActionMailer::Base
+ helper :link
+
+ default :from => AppConfig.email_from
+ include ActionView::Helpers::NumberHelper
+ layout 'default_mail'
+
+ def email_confirmation_for_creator(signature)
+ @signature = signature
+ mail(:subject => "HM Government e-petitions: Email address confirmation", :to => @signature.email)
+ end
+
+ def email_confirmation_for_signer(signature)
+ @signature = signature
+ mail(:subject => "HM Government e-petitions: Email address confirmation", :to => @signature.email)
+ end
+
+ def special_resend_of_email_confirmation_for_signer(signature)
+ @signature = signature
+ mail(:subject => "HM Government e-petitions: Email address confirmation", :to => @signature.email)
+ end
+
+ def notify_creator_that_petition_is_published(signature)
+ @signature = signature
+ mail(:subject => "HM Government e-petitions: Your e-petition has been published", :to => @signature.email)
+ end
+
+ def notify_creator_that_petition_is_rejected(signature)
+ @signature = signature
+ mail(:subject => "HM Government e-petitions: Your e-petition has been rejected", :to => @signature.email)
+ end
+
+ def notify_signer_of_threshold_response(petition, signature)
+ @petition = petition
+ @signature = signature
+ mail(:subject => "HM Government e-petitions: The petition '#{petition.title}' has reached #{number_with_delimiter(petition.signature_count)} signatures", :to => @signature.email)
+ end
+
+ def no_signature_for_petition(petition, email)
+ @petition = petition
+ mail :subject => "HM Government e-petitions: a confirmation email has been requested",
+ :to => email
+ end
+
+ def email_already_confirmed_for_signature(signature)
+ @signature = signature
+ mail :subject => "HM Government e-petitions: Signature already confirmed",
+ :to => @signature.email
+ end
+
+ def two_pending_signatures(signature_one, signature_two)
+ @signature_one = signature_one
+ @signature_two = signature_two
+ mail :subject => "HM Government e-petitions: Signature confirmations",
+ :to => @signature_one.email
+ end
+
+ def one_pending_one_validated_signature(pending_signature, validated_signature)
+ @pending_signature = pending_signature
+ @validated_signature = validated_signature
+ mail :subject => "HM Government e-petitions: Signature confirmation",
+ :to => @pending_signature.email
+ end
+
+ def double_signature_confirmation(signatures)
+ @signature_one = signatures.first
+ @signature_two = signatures.second
+ mail :subject => "HM Government e-petitions: Signatures already confirmed",
+ :to => @signature_one.email
+ end
+end
97 app/models/admin_user.rb
@@ -0,0 +1,97 @@
+# == Schema Information
+#
+# Table name: admin_users
+#
+# id :integer(4) not null, primary key
+# email :string(255) not null
+# persistence_token :string(255)
+# crypted_password :string(255)
+# password_salt :string(255)
+# login_count :integer(4) default(0)
+# failed_login_count :integer(4) default(0)
+# current_login_at :datetime
+# last_login_at :datetime
+# current_login_ip :string(255)
+# last_login_ip :string(255)
+# first_name :string(255)
+# last_name :string(255)
+# role :string(10) not null
+# force_password_reset :boolean(1) default(TRUE)
+# password_changed_at :datetime
+# created_at :datetime
+# updated_at :datetime
+#
+
+class AdminUser < ActiveRecord::Base
+ DISABLED_LOGIN_COUNT = 5
+ ADMIN_ROLE = 'admin'
+ SYSADMIN_ROLE = 'sysadmin'
+ THRESHOLD_ROLE = 'threshold'
+
+ acts_as_authentic do |config|
+ config.merge_validates_length_of_password_field_options :minimum => 8
+ config.ignore_blank_passwords = true
+
+ # Add conditions to the default validations to tidy up output.
+ config.merge_validates_format_of_email_field_options :unless => Proc.new { |user| user.email.blank? }
+ config.merge_validates_length_of_email_field_options :unless => Proc.new { |user| user.email.blank? }
+ config.merge_validates_length_of_password_field_options :unless => Proc.new { |user| user.password.blank? }
+ config.merge_validates_confirmation_of_password_field_options :unless => Proc.new { |user| user.password.blank? }
+ end
+
+ # = Relationships =
+ has_and_belongs_to_many :departments
+
+ # = Validations =
+ validates_presence_of :password, :on => :create
+ validates_presence_of :email, :first_name, :last_name
+ # password must have at least one digit, one alphabetical lower and upcase case character and one special character
+ # see http://www.zorched.net/2009/05/08/password-strength-validation-with-regular-expressions/
+ validates_format_of :password, :with => /^.*(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\W_]).*$/,
+ :message => 'must contain at least one digit, a lower and upper case letter and a special character',
+ :allow_blank => true
+ ROLES = [ADMIN_ROLE, SYSADMIN_ROLE, THRESHOLD_ROLE]
+ validates_inclusion_of :role, :in => ROLES, :message => "'%{value}' is invalid"
+
+ # = Finders =
+ scope :by_name, :order => 'last_name, first_name'
+ scope :by_role, lambda { |role| { :conditions => ['role = ?', role] }}
+
+ # = Methods =
+
+ def name
+ "#{last_name}, #{first_name}"
+ end
+
+ def is_a_sysadmin?
+ self.role == 'sysadmin'
+ end
+
+ def is_a_threshold?
+ self.role == 'threshold'
+ end
+
+ def has_to_change_password?
+ self.force_password_reset or (self.password_changed_at and self.password_changed_at < 9.months.ago)
+ end
+
+ def can_take_petitions_down?
+ is_a_sysadmin? || is_a_threshold?
+ end
+
+ def can_edit_responses?
+ is_a_sysadmin? || is_a_threshold?
+ end
+
+ def can_see_all_trending_petitions?
+ is_a_sysadmin? || is_a_threshold?
+ end
+
+ def account_disabled
+ self.failed_login_count >= DISABLED_LOGIN_COUNT
+ end
+
+ def account_disabled=(flag)
+ self.failed_login_count = (flag == "0" or !flag) ? 0 : DISABLED_LOGIN_COUNT
+ end
+end
5 app/models/admin_user_session.rb
@@ -0,0 +1,5 @@
+class AdminUserSession < Authlogic::Session::Base
+ allow_http_basic_auth false
+ consecutive_failed_logins_limit AdminUser::DISABLED_LOGIN_COUNT
+
+end
40 app/models/captcha.rb
@@ -0,0 +1,40 @@
+require 'digest/md5'
+
+class Captcha
+ USERNAME = 'xxxxx'
+ SECRET = 'xxxxx'
+ ALPHABET = 'abcdefghijklmnopqrstuvwxyz1234567890'
+ CHARACTER_COUNT = 6
+
+ def self.verify(user_inputted_text, captcha_random_string)
+ return true if skip_captcha_verification?
+ user_inputted_text == get_captcha_text(captcha_random_string)
+ end
+
+ def self.get_captcha_text random_string
+ if bad_character_count?
+ raise "Character count of #{CHARACTER_COUNT} is outside the range of 1-16"
+ end
+
+ input = "#{SECRET}#{random_string}"
+ if custom_alphabet_or_character_count?
+ input << ":#{ALPHABET}:#{CHARACTER_COUNT}"
+ end
+
+ bytes = Digest::MD5.hexdigest(input).slice(0..(2*CHARACTER_COUNT - 1)).scan(/../)
+ bytes.map { |byte| ALPHABET[byte.hex % ALPHABET.size].chr }.to_s
+ end
+
+ private
+ def self.skip_captcha_verification?
+ AppConfig.has_setting?(:skip_recaptcha_verify) && AppConfig.skip_recaptcha_verify > 0
+ end
+
+ def self.bad_character_count?
+ CHARACTER_COUNT < 1 || CHARACTER_COUNT > 16
+ end
+
+ def self.custom_alphabet_or_character_count?
+ ALPHABET != 'abcdefghijklmnopqrstuvwxyz' || CHARACTER_COUNT != 6
+ end
+end
30 app/models/department.rb
@@ -0,0 +1,30 @@
+# == Schema Information
+#
+# Table name: departments
+#
+# id :integer(4) not null, primary key
+# name :string(255) not null
+# description :text
+# created_at :datetime
+# updated_at :datetime
+# website_url :string(255)
+#
+
+class Department < ActiveRecord::Base
+
+ # = Relationships =
+ has_many :petitions
+
+ # = Validations =
+ validates_presence_of :name
+ validates_uniqueness_of :name, :case_sensitive => false
+
+ # = Finders =
+ scope :by_name, :order => 'name'
+ scope :by_petition_count, joins(:petitions).order("count('petitions.id') DESC").group('departments.id')
+
+ # = Methods =
+ def count_petitions_for_state(state)
+ petitions.for_state(state).count
+ end
+end
18 app/models/department_assignment.rb
@@ -0,0 +1,18 @@
+# == Schema Information
+#
+# Table name: department_assignments
+#
+# id :integer(4) not null, primary key
+# petition_id :integer(4)
+# department_id :integer(4)
+# assigned_on :datetime
+# created_at :datetime
+# updated_at :datetime
+#
+
+class DepartmentAssignment < Acti