From d1db46ffaf8308d2ea1a9718741648d64ec3c1ea Mon Sep 17 00:00:00 2001 From: Godwin Date: Sun, 9 Apr 2017 11:37:16 -0700 Subject: [PATCH] 2017 refactor --- .gitignore | 4 - Gemfile | 133 +- Gemfile.lock | 633 +++++ Guardfile | 25 + README.md | 5 + Rakefile | 31 + app/assets/images/test-poster.png | Bin 0 -> 77534 bytes app/assets/javascripts/userfield.js | 1 + app/assets/stylesheets/_application.scss | 23 +- .../stylesheets/bumbleberry-settings.json | 194 +- app/assets/stylesheets/user-mailer.scss | 398 +-- ...roller.rb => administration_controller.rb} | 102 +- app/controllers/application_controller.rb | 302 +-- .../conference_administration_controller.rb | 146 +- app/controllers/conferences_controller.rb | 71 +- app/controllers/oauths_controller.rb | 101 - app/controllers/workshops_controller.rb | 8 +- app/helpers/admin_helper.rb | 163 ++ app/helpers/application_helper.rb | 2219 +---------------- app/helpers/bike_bike_form_helper.rb | 407 --- app/helpers/form_helper.rb | 729 ++++++ app/helpers/geocoder_helper.rb | 100 + app/helpers/i18n_helper.rb | 74 + app/helpers/page_helper.rb | 88 + app/helpers/registration_helper.rb | 30 +- app/helpers/table_helper.rb | 366 +++ app/helpers/widgets_helper.rb | 288 +++ app/mailers/user_mailer.rb | 4 +- app/models/authentication.rb | 3 - app/models/city.rb | 166 -- app/models/city_cache.rb | 21 - app/models/comment.rb | 37 - app/models/conference.rb | 160 -- app/models/conference_admin.rb | 2 - app/models/conference_administrator.rb | 4 - app/models/conference_host_organization.rb | 4 - app/models/conference_registration.rb | 74 - .../conference_registration_form_field.rb | 4 - .../conference_registration_response.rb | 5 - app/models/conference_type.rb | 7 - app/models/email_confirmation.rb | 40 - app/models/event.rb | 20 - app/models/event_location.rb | 25 - app/models/event_type.rb | 2 - app/models/location.rb | 21 - app/models/locations_organization.rb | 6 - app/models/organization.rb | 77 - app/models/organization_status.rb | 2 - app/models/registration_form_field.rb | 75 - app/models/user.rb | 68 - app/models/user_organization_relationship.rb | 11 - app/models/version.rb | 2 - app/models/workshop.rb | 180 -- app/models/workshop_facilitator.rb | 2 - app/models/workshop_interest.rb | 4 - app/models/workshop_presentation_style.rb | 2 - app/models/workshop_requested_resource.rb | 2 - app/models/workshop_resource.rb | 2 - app/models/workshop_stream.rb | 2 - app/uploaders/avatar_uploader.rb | 104 - app/uploaders/cover_uploader.rb | 54 - app/uploaders/logo_uploader.rb | 57 - app/uploaders/poster_uploader.rb | 69 - app/views/admin/new.html.haml | 2 +- app/views/application/500.html.haml | 3 +- app/views/application/_contact.html.haml | 2 +- app/views/application/_login.html.haml | 2 +- .../application/_login_confirm.html.haml | 2 +- .../_login_confirmation_sent.html.haml | 6 +- .../application/_not_a_translator.html.haml | 2 +- .../application/_translator_login.html.haml | 2 +- app/views/application/contact.html.haml | 16 +- app/views/application/home.html.haml | 3 +- .../application/permission_denied.html.haml | 16 +- app/views/application/update_user.html.haml | 2 +- app/views/application/user_settings.html.haml | 2 +- .../_administrators.html.haml | 10 +- .../_broadcast.html.haml | 6 +- .../_dates.html.haml | 2 +- .../_description.html.haml | 2 +- .../_events.html.haml | 4 +- .../_locations.html.haml | 6 +- .../_meals.html.haml | 4 +- .../_payment_message.html.haml | 2 +- .../_paypal.html.haml | 2 +- .../_poster.html.haml | 2 +- .../_providers.html.haml | 2 +- .../_publish_schedule.html.haml | 4 +- .../_registration_status.html.haml | 2 +- .../_registrations.html.haml | 2 +- .../_schedule.html.haml | 167 +- .../_suggested_amounts.html.haml | 2 +- .../_workshop_times.html.haml | 4 +- .../conferences/_administration.html.haml | 6 - app/views/conferences/_conference.html.haml | 29 +- .../conferences/_confirm_email.html.haml | 20 +- app/views/conferences/_contact_info.html.haml | 4 +- app/views/conferences/_done.html.haml | 2 +- .../conferences/_email_confirm.html.haml | 10 +- app/views/conferences/_hosting.html.haml | 4 +- app/views/conferences/_payment.html.haml | 9 +- .../conferences/_paypal_confirm.html.haml | 8 +- app/views/conferences/_policy.html.haml | 10 +- app/views/conferences/_questions.html.haml | 2 +- app/views/conferences/_register.html.haml | 2 +- .../_registration_register.html.haml | 45 - app/views/conferences/_workshops.html.haml | 36 +- app/views/conferences/broadcast.html.haml | 14 +- app/views/conferences/edit.html.haml | 44 +- app/views/conferences/hosts.html.haml | 13 - app/views/conferences/list.html.haml | 2 +- app/views/conferences/register.html.haml | 2 +- app/views/conferences/registration.html.haml | 14 - app/views/conferences/view.html.haml | 2 +- app/views/layouts/application.html.haml | 2 +- app/views/layouts/user_mailer.html.haml | 50 +- app/views/schedule/edit.html.haml | 8 +- app/views/shared/_footer.html.haml | 7 - app/views/shared/_navbar.html.haml | 4 +- .../shared/_translation_control.html.haml | 52 +- app/views/user_mailer/contact.html.haml | 8 +- .../user_mailer/contact_details.html.haml | 54 +- .../user_mailer/email_confirmation.html.haml | 3 +- .../user_mailer/workshop_comment.html.haml | 4 +- .../workshop_facilitator_request.html.haml | 6 +- ...hop_facilitator_request_approved.html.haml | 4 +- ...kshop_facilitator_request_denied.html.haml | 4 +- .../user_mailer/workshop_translated.html.haml | 4 +- .../user_mailer/workshop_translated.text.haml | 25 - app/views/workshops/_show.html.haml | 14 +- .../workshops/_workshop_previews.html.haml | 2 +- app/views/workshops/delete.html.haml | 16 +- app/views/workshops/edit.html.haml | 6 - app/views/workshops/facilitate.html.haml | 28 +- .../facilitate_request_sent.html.haml | 18 +- app/views/workshops/index.html.haml | 9 +- app/views/workshops/new.html.haml | 4 +- config/application.rb | 41 +- config/environments/development.rb | 111 +- config/environments/test.rb | 96 +- config/locales/en.yml | 1262 +++------- config/locales/fr.yml | 2 + config/routes.rb | 138 +- config/unicorn.rb | 18 +- db/migrate/20131102214949_sorcery_core.rb | 16 - .../20131102214950_sorcery_remember_me.rb | 15 - .../20131102214951_sorcery_reset_password.rb | 17 - .../20131102214952_sorcery_user_activation.rb | 17 - ...02214953_sorcery_brute_force_protection.rb | 13 - db/migrate/20131102214954_sorcery_external.rb | 14 - db/migrate/20140208035124_create_locations.rb | 12 - .../20140208231633_add_country_to_location.rb | 5 - ...0140208231738_add_territory_to_location.rb | 5 - .../20140208231802_add_city_to_location.rb | 5 - .../20140208231828_add_street_to_location.rb | 5 - ...40208231913_add_postal_code_to_location.rb | 5 - ...0208232052_remove_address_from_location.rb | 5 - .../20140209001432_add_index_to_location.rb | 5 - .../20140209005651_create_conference_types.rb | 10 - ...40209005813_add_slug_to_conference_type.rb | 5 - ...327_create_workshop_presentation_styles.rb | 11 - ...0140209012344_create_workshop_resources.rb | 11 - .../20140209012408_create_workshop_streams.rb | 11 - ...0209015644_create_organization_statuses.rb | 11 - .../20140209021801_create_translations.rb | 13 - db/migrate/20140212022115_create_versions.rb | 14 - .../20140212054019_add_value_to_versions.rb | 5 - .../20140217225420_add_avatar_to_users.rb | 5 - .../20140219020355_create_organizations.rb | 21 - ...8_remove_location_id_from_organizations.rb | 5 - ...222202640_create_organization_locations.rb | 8 - ...primary_key_for_locations_organizations.rb | 5 - ..._create_user_organization_relationships.rb | 10 - ...140224034434_add_cover_to_organizations.rb | 5 - ...dd_id_to_organization_user_relationship.rb | 5 - .../20140301202157_create_conferences.rb | 21 - ...23_create_conference_host_organizations.rb | 11 - ...20140302212924_create_conference_admins.rb | 10 - ...302225518_add_text_fields_to_conference.rb | 7 - ...3010316_create_registration_form_fields.rb | 14 - ...eate_conference_registraton_form_fields.rb | 11 - ...3010926_create_conference_registrations.rb | 11 - ...reate_conference_registration_responses.rb | 11 - ..._to_conference_registration_form_fields.rb | 5 - ...rom_conference_registration_form_fields.rb | 5 - ..._to_conference_registration_form_fields.rb | 5 - db/migrate/20140314025647_create_workshops.rb | 18 - ...0315175914_create_workshop_facilitators.rb | 11 - ...222_create_workshop_requested_resources.rb | 11 - .../20140315183121_create_event_types.rb | 10 - db/migrate/20140315183241_create_events.rb | 16 - .../20140524050931_add_about_me_to_users.rb | 5 - .../20140524052048_add_role_to_users.rb | 5 - ...d_cover_attribution_id_to_organizations.rb | 5 - ...cover_attribution_name_to_organizations.rb | 5 - ..._cover_attribution_src_to_organizations.rb | 5 - ...140525165547_add_phone_to_organizations.rb | 5 - ...organization_status_id_to_organizations.rb | 5 - ...15_add_cover_attribution_to_conferences.rb | 7 - ...er_attribution_user_id_to_organizations.rb | 5 - ...over_attribution_user_id_to_conferences.rb | 5 - ...is_confirmed_to_conference_registraions.rb | 5 - ..._participant_to_conference_registraions.rb | 5 - ...is_volunteer_to_conference_registraions.rb | 5 - .../20140714012739_add_firstname_to_users.rb | 5 - .../20140714012811_add_lastname_to_users.rb | 5 - .../20140714013645_add_sessions_table.rb | 12 - ...ation_token_to_conference_registrations.rb | 5 - ...52_add_data_to_conference_registrations.rb | 5 - ...9_add_email_to_conference_registrations.rb | 5 - ...is_complete_to_conference_registrations.rb | 5 - ...d_completed_to_conference_registrations.rb | 5 - ...mation_token_to_conference_registration.rb | 5 - ...payment_info_to_conference_registration.rb | 5 - ...723182548_add_order_to_workshop_streams.rb | 5 - ...d_order_to_workshop_presentation_styles.rb | 5 - ...on_fees_paid_to_conference_registration.rb | 5 - ...20150314031359_add_locale_to_conference.rb | 5 - ...0150314031361_create_translation_tables.rb | 26 - ...225815_add_email_address_to_conferences.rb | 5 - ...add_paypal_email_address_to_conferences.rb | 5 - ...150412203317_create_email_confirmations.rb | 12 - ...150412203357_add_is_translator_to_users.rb | 5 - ...47_add_city_to_conference_registrations.rb | 10 - ...04032547_add_paypal_info_to_conferences.rb | 7 - ...0150807002150_add_info_to_registrations.rb | 7 - ...150809011815_make_username_not_nullable.rb | 5 - ...453_make_username_actually_not_nullable.rb | 5 - .../20150811234700_create_delayed_jobs.rb | 22 - ...150819042431_add_languages_to_workshops.rb | 9 - .../20150821005002_add_notes_to_workshops.rb | 5 - ...0150912010105_create_workshop_interests.rb | 10 - .../20150914230507_add_locale_to_workshops.rb | 5 - .../20150920233402_create_event_locations.rb | 14 - ...3710_add_event_location_id_to_workshops.rb | 5 - ...0233755_add_event_location_id_to_events.rb | 5 - .../20150925000217_add_type_to_events.rb | 5 - ...0927010559_add_day_parts_to_conferences.rb | 5 - ...60304021005_add_status_to_organizations.rb | 5 - ..._add_registration_status_to_conferences.rb | 9 - ...ighest_step_to_conference_registrations.rb | 5 - ...s_completed_to_conference_registrations.rb | 5 - ...849_add_needs_facilitators_to_workshops.rb | 5 - .../20160529225253_add_languages_to_users.rb | 5 - ...e_format_in_dynamic_translation_records.rb | 5 - ...ide_housing_to_conference_registrations.rb | 5 - ...ousing_data_to_conference_registrations.rb | 5 - .../20160604221432_add_locale_to_users.rb | 5 - ...0621020123_add_space_to_event_locations.rb | 5 - ...20160621051912_add_meals_to_conferences.rb | 5 - .../20160622011811_add_locale_to_events.rb | 5 - ...3219_add_workshop_blocks_to_conferences.rb | 5 - .../20160703044620_add_block_to_workshops.rb | 5 - ...160707034050_add_is_subscribed_to_users.rb | 5 - db/migrate/20160708042302_create_comments.rb | 11 - .../20160708042511_add_user_id_to_comments.rb | 5 - ...00814_add_payment_message_to_conference.rb | 5 - ...00940_add_payment_amounts_to_conference.rb | 5 - .../20161006021205_add_fb_id_to_user.rb | 5 - .../20161008210246_add_type_to_conference.rb | 5 - db/migrate/20161009004744_create_cities.rb | 14 - .../20161009005122_create_city_matches.rb | 10 - .../20161009194821_add_year_to_conference.rb | 5 - ...0161009200252_add_city_id_to_conference.rb | 5 - ...61129010755_add_is_public_to_conference.rb | 9 - ...129014652_add_is_featured_to_conference.rb | 9 - .../20161129035601_rename_conference_type.rb | 9 - .../20161130042227_rename_city_matches.rb | 39 - .../20161201011655_add_place_id_to_city.rb | 7 - .../20161201015559_add_city_id_to_location.rb | 5 - ...201021349_add_spanish_city_translations.rb | 4 - .../20161201043930_set_conference_types.rb | 20 - ...021236_create_conference_administrators.rb | 10 - ...17_change_conference_datetimes_to_dates.rb | 6 - .../20161211065022_replace_missing_locales.rb | 19 - ...add_city_id_to_conference_registrations.rb | 5 - ...7_add_provider_conditions_to_conference.rb | 5 - db/schema.rb | 26 +- features/conferences.feature | 113 + features/contact_us.feature | 40 + features/landing_page.feature | 61 +- features/registration.feature | 606 +++++ features/schedule.feature | 72 + features/sign_in.feature | 86 + features/static_pages.feature | 33 +- features/step_definitions/conferences.rb | 102 + features/step_definitions/emails.rb | 54 + features/step_definitions/interface_steps.rb | 626 ++--- features/step_definitions/navigation.rb | 70 + features/step_definitions/registration.rb | 93 + features/step_definitions/users.rb | 93 + features/step_definitions/workshops.rb | 107 + .../assets/images/posters/Brooklyn2025.png | Bin 0 -> 77534 bytes .../assets/images/posters/NewOrleans2013.png | Bin 0 -> 1212897 bytes .../assets/images/posters/Northwest2025.png | Bin 0 -> 132821 bytes features/support/env.rb | 185 +- features/support/factory_girl.rb | 164 +- features/support/helper.rb | 194 ++ features/support/location_cache.yml | 479 +++- features/support/paths.rb | 53 +- features/support/state.rb | 112 + features/workshops.feature | 323 +++ 302 files changed, 7159 insertions(+), 8141 deletions(-) create mode 100644 Gemfile.lock create mode 100644 app/assets/images/test-poster.png rename app/controllers/{admin_controller.rb => administration_controller.rb} (94%) delete mode 100644 app/controllers/oauths_controller.rb create mode 100644 app/helpers/admin_helper.rb delete mode 100644 app/helpers/bike_bike_form_helper.rb create mode 100644 app/helpers/form_helper.rb create mode 100644 app/helpers/geocoder_helper.rb create mode 100644 app/helpers/i18n_helper.rb create mode 100644 app/helpers/page_helper.rb create mode 100644 app/helpers/table_helper.rb create mode 100644 app/helpers/widgets_helper.rb delete mode 100644 app/models/authentication.rb delete mode 100644 app/models/city.rb delete mode 100644 app/models/city_cache.rb delete mode 100644 app/models/comment.rb delete mode 100644 app/models/conference.rb delete mode 100644 app/models/conference_admin.rb delete mode 100644 app/models/conference_administrator.rb delete mode 100644 app/models/conference_host_organization.rb delete mode 100644 app/models/conference_registration.rb delete mode 100644 app/models/conference_registration_form_field.rb delete mode 100644 app/models/conference_registration_response.rb delete mode 100644 app/models/conference_type.rb delete mode 100644 app/models/email_confirmation.rb delete mode 100644 app/models/event.rb delete mode 100644 app/models/event_location.rb delete mode 100644 app/models/event_type.rb delete mode 100644 app/models/location.rb delete mode 100644 app/models/locations_organization.rb delete mode 100644 app/models/organization.rb delete mode 100644 app/models/organization_status.rb delete mode 100644 app/models/registration_form_field.rb delete mode 100644 app/models/user.rb delete mode 100644 app/models/user_organization_relationship.rb delete mode 100644 app/models/version.rb delete mode 100644 app/models/workshop.rb delete mode 100644 app/models/workshop_facilitator.rb delete mode 100644 app/models/workshop_interest.rb delete mode 100644 app/models/workshop_presentation_style.rb delete mode 100644 app/models/workshop_requested_resource.rb delete mode 100644 app/models/workshop_resource.rb delete mode 100644 app/models/workshop_stream.rb delete mode 100644 app/uploaders/avatar_uploader.rb delete mode 100644 app/uploaders/cover_uploader.rb delete mode 100644 app/uploaders/logo_uploader.rb delete mode 100644 app/uploaders/poster_uploader.rb delete mode 100644 app/views/conferences/_administration.html.haml delete mode 100644 app/views/conferences/_registration_register.html.haml delete mode 100644 app/views/conferences/hosts.html.haml delete mode 100644 app/views/conferences/registration.html.haml delete mode 100644 app/views/user_mailer/workshop_translated.text.haml delete mode 100644 app/views/workshops/edit.html.haml create mode 100644 config/locales/fr.yml delete mode 100644 db/migrate/20131102214949_sorcery_core.rb delete mode 100644 db/migrate/20131102214950_sorcery_remember_me.rb delete mode 100644 db/migrate/20131102214951_sorcery_reset_password.rb delete mode 100644 db/migrate/20131102214952_sorcery_user_activation.rb delete mode 100644 db/migrate/20131102214953_sorcery_brute_force_protection.rb delete mode 100644 db/migrate/20131102214954_sorcery_external.rb delete mode 100644 db/migrate/20140208035124_create_locations.rb delete mode 100644 db/migrate/20140208231633_add_country_to_location.rb delete mode 100644 db/migrate/20140208231738_add_territory_to_location.rb delete mode 100644 db/migrate/20140208231802_add_city_to_location.rb delete mode 100644 db/migrate/20140208231828_add_street_to_location.rb delete mode 100644 db/migrate/20140208231913_add_postal_code_to_location.rb delete mode 100644 db/migrate/20140208232052_remove_address_from_location.rb delete mode 100644 db/migrate/20140209001432_add_index_to_location.rb delete mode 100644 db/migrate/20140209005651_create_conference_types.rb delete mode 100644 db/migrate/20140209005813_add_slug_to_conference_type.rb delete mode 100644 db/migrate/20140209012327_create_workshop_presentation_styles.rb delete mode 100644 db/migrate/20140209012344_create_workshop_resources.rb delete mode 100644 db/migrate/20140209012408_create_workshop_streams.rb delete mode 100644 db/migrate/20140209015644_create_organization_statuses.rb delete mode 100644 db/migrate/20140209021801_create_translations.rb delete mode 100644 db/migrate/20140212022115_create_versions.rb delete mode 100644 db/migrate/20140212054019_add_value_to_versions.rb delete mode 100644 db/migrate/20140217225420_add_avatar_to_users.rb delete mode 100644 db/migrate/20140219020355_create_organizations.rb delete mode 100644 db/migrate/20140222202258_remove_location_id_from_organizations.rb delete mode 100644 db/migrate/20140222202640_create_organization_locations.rb delete mode 100644 db/migrate/20140222212830_create_primary_key_for_locations_organizations.rb delete mode 100644 db/migrate/20140223004805_create_user_organization_relationships.rb delete mode 100644 db/migrate/20140224034434_add_cover_to_organizations.rb delete mode 100644 db/migrate/20140227012532_add_id_to_organization_user_relationship.rb delete mode 100644 db/migrate/20140301202157_create_conferences.rb delete mode 100644 db/migrate/20140302212723_create_conference_host_organizations.rb delete mode 100644 db/migrate/20140302212924_create_conference_admins.rb delete mode 100644 db/migrate/20140302225518_add_text_fields_to_conference.rb delete mode 100644 db/migrate/20140303010316_create_registration_form_fields.rb delete mode 100644 db/migrate/20140303010547_create_conference_registraton_form_fields.rb delete mode 100644 db/migrate/20140303010926_create_conference_registrations.rb delete mode 100644 db/migrate/20140303011229_create_conference_registration_responses.rb delete mode 100644 db/migrate/20140307035356_rename_conference_registraton_form_fields_to_conference_registration_form_fields.rb delete mode 100644 db/migrate/20140308173154_remove_order_from_conference_registration_form_fields.rb delete mode 100644 db/migrate/20140308173325_add_position_to_conference_registration_form_fields.rb delete mode 100644 db/migrate/20140314025647_create_workshops.rb delete mode 100644 db/migrate/20140315175914_create_workshop_facilitators.rb delete mode 100644 db/migrate/20140315181222_create_workshop_requested_resources.rb delete mode 100644 db/migrate/20140315183121_create_event_types.rb delete mode 100644 db/migrate/20140315183241_create_events.rb delete mode 100644 db/migrate/20140524050931_add_about_me_to_users.rb delete mode 100644 db/migrate/20140524052048_add_role_to_users.rb delete mode 100644 db/migrate/20140524202858_add_cover_attribution_id_to_organizations.rb delete mode 100644 db/migrate/20140524202938_add_cover_attribution_name_to_organizations.rb delete mode 100644 db/migrate/20140524203006_add_cover_attribution_src_to_organizations.rb delete mode 100644 db/migrate/20140525165547_add_phone_to_organizations.rb delete mode 100644 db/migrate/20140525174815_add_organization_status_id_to_organizations.rb delete mode 100644 db/migrate/20140525212115_add_cover_attribution_to_conferences.rb delete mode 100644 db/migrate/20140712190647_add_cover_attribution_user_id_to_organizations.rb delete mode 100644 db/migrate/20140712190815_add_cover_attribution_user_id_to_conferences.rb delete mode 100644 db/migrate/20140713213158_add_is_confirmed_to_conference_registraions.rb delete mode 100644 db/migrate/20140713213502_add_is_participant_to_conference_registraions.rb delete mode 100644 db/migrate/20140713213534_add_is_volunteer_to_conference_registraions.rb delete mode 100644 db/migrate/20140714012739_add_firstname_to_users.rb delete mode 100644 db/migrate/20140714012811_add_lastname_to_users.rb delete mode 100644 db/migrate/20140714013645_add_sessions_table.rb delete mode 100644 db/migrate/20140716001857_add_confirmation_token_to_conference_registrations.rb delete mode 100644 db/migrate/20140716002152_add_data_to_conference_registrations.rb delete mode 100644 db/migrate/20140719172749_add_email_to_conference_registrations.rb delete mode 100644 db/migrate/20140719185914_add_is_complete_to_conference_registrations.rb delete mode 100644 db/migrate/20140719204949_add_completed_to_conference_registrations.rb delete mode 100644 db/migrate/20140723174550_add_payment_confirmation_token_to_conference_registration.rb delete mode 100644 db/migrate/20140723174648_add_payment_info_to_conference_registration.rb delete mode 100644 db/migrate/20140723182548_add_order_to_workshop_streams.rb delete mode 100644 db/migrate/20140723183557_add_order_to_workshop_presentation_styles.rb delete mode 100644 db/migrate/20140725001300_add_registration_fees_paid_to_conference_registration.rb delete mode 100644 db/migrate/20150314031359_add_locale_to_conference.rb delete mode 100644 db/migrate/20150314031361_create_translation_tables.rb delete mode 100644 db/migrate/20150315225815_add_email_address_to_conferences.rb delete mode 100644 db/migrate/20150315225844_add_paypal_email_address_to_conferences.rb delete mode 100644 db/migrate/20150412203317_create_email_confirmations.rb delete mode 100644 db/migrate/20150412203357_add_is_translator_to_users.rb delete mode 100644 db/migrate/20150802234647_add_city_to_conference_registrations.rb delete mode 100644 db/migrate/20150804032547_add_paypal_info_to_conferences.rb delete mode 100644 db/migrate/20150807002150_add_info_to_registrations.rb delete mode 100644 db/migrate/20150809011815_make_username_not_nullable.rb delete mode 100644 db/migrate/20150809012453_make_username_actually_not_nullable.rb delete mode 100644 db/migrate/20150811234700_create_delayed_jobs.rb delete mode 100644 db/migrate/20150819042431_add_languages_to_workshops.rb delete mode 100644 db/migrate/20150821005002_add_notes_to_workshops.rb delete mode 100644 db/migrate/20150912010105_create_workshop_interests.rb delete mode 100644 db/migrate/20150914230507_add_locale_to_workshops.rb delete mode 100644 db/migrate/20150920233402_create_event_locations.rb delete mode 100644 db/migrate/20150920233710_add_event_location_id_to_workshops.rb delete mode 100644 db/migrate/20150920233755_add_event_location_id_to_events.rb delete mode 100644 db/migrate/20150925000217_add_type_to_events.rb delete mode 100644 db/migrate/20150927010559_add_day_parts_to_conferences.rb delete mode 100644 db/migrate/20160304021005_add_status_to_organizations.rb delete mode 100644 db/migrate/20160515210708_add_registration_status_to_conferences.rb delete mode 100644 db/migrate/20160521230653_add_highest_step_to_conference_registrations.rb delete mode 100644 db/migrate/20160522022034_add_steps_completed_to_conference_registrations.rb delete mode 100644 db/migrate/20160526044849_add_needs_facilitators_to_workshops.rb delete mode 100644 db/migrate/20160529225253_add_languages_to_users.rb delete mode 100644 db/migrate/20160530175805_change_date_format_in_dynamic_translation_records.rb delete mode 100644 db/migrate/20160604221121_add_can_provide_housing_to_conference_registrations.rb delete mode 100644 db/migrate/20160604221248_add_housing_data_to_conference_registrations.rb delete mode 100644 db/migrate/20160604221432_add_locale_to_users.rb delete mode 100644 db/migrate/20160621020123_add_space_to_event_locations.rb delete mode 100644 db/migrate/20160621051912_add_meals_to_conferences.rb delete mode 100644 db/migrate/20160622011811_add_locale_to_events.rb delete mode 100644 db/migrate/20160630233219_add_workshop_blocks_to_conferences.rb delete mode 100644 db/migrate/20160703044620_add_block_to_workshops.rb delete mode 100644 db/migrate/20160707034050_add_is_subscribed_to_users.rb delete mode 100644 db/migrate/20160708042302_create_comments.rb delete mode 100644 db/migrate/20160708042511_add_user_id_to_comments.rb delete mode 100644 db/migrate/20160814000814_add_payment_message_to_conference.rb delete mode 100644 db/migrate/20160814000940_add_payment_amounts_to_conference.rb delete mode 100644 db/migrate/20161006021205_add_fb_id_to_user.rb delete mode 100644 db/migrate/20161008210246_add_type_to_conference.rb delete mode 100644 db/migrate/20161009004744_create_cities.rb delete mode 100644 db/migrate/20161009005122_create_city_matches.rb delete mode 100644 db/migrate/20161009194821_add_year_to_conference.rb delete mode 100644 db/migrate/20161009200252_add_city_id_to_conference.rb delete mode 100644 db/migrate/20161129010755_add_is_public_to_conference.rb delete mode 100644 db/migrate/20161129014652_add_is_featured_to_conference.rb delete mode 100644 db/migrate/20161129035601_rename_conference_type.rb delete mode 100644 db/migrate/20161130042227_rename_city_matches.rb delete mode 100644 db/migrate/20161201011655_add_place_id_to_city.rb delete mode 100644 db/migrate/20161201015559_add_city_id_to_location.rb delete mode 100644 db/migrate/20161201021349_add_spanish_city_translations.rb delete mode 100644 db/migrate/20161201043930_set_conference_types.rb delete mode 100644 db/migrate/20161207021236_create_conference_administrators.rb delete mode 100644 db/migrate/20161210212817_change_conference_datetimes_to_dates.rb delete mode 100644 db/migrate/20161211065022_replace_missing_locales.rb delete mode 100644 db/migrate/20170110061048_add_city_id_to_conference_registrations.rb delete mode 100644 db/migrate/20170111172147_add_provider_conditions_to_conference.rb create mode 100644 features/conferences.feature create mode 100644 features/contact_us.feature create mode 100644 features/registration.feature create mode 100644 features/schedule.feature create mode 100644 features/sign_in.feature create mode 100644 features/step_definitions/conferences.rb create mode 100644 features/step_definitions/emails.rb create mode 100644 features/step_definitions/navigation.rb create mode 100644 features/step_definitions/registration.rb create mode 100644 features/step_definitions/users.rb create mode 100644 features/step_definitions/workshops.rb create mode 100644 features/support/assets/images/posters/Brooklyn2025.png create mode 100644 features/support/assets/images/posters/NewOrleans2013.png create mode 100644 features/support/assets/images/posters/Northwest2025.png create mode 100644 features/support/helper.rb create mode 100644 features/support/state.rb create mode 100644 features/workshops.feature diff --git a/.gitignore b/.gitignore index cc90ddb..61d62ed 100644 --- a/.gitignore +++ b/.gitignore @@ -47,10 +47,6 @@ rerun.txt pickle-email-*.html .project -# Ignore Gemfile.lock so that we always get the most up to date gems -# We'll be testing in 'preview' to avoid clashes -Gemfile.lock - # Ignore Redis' dataset snapshot dump.rdb diff --git a/Gemfile b/Gemfile index 851267c..aa555a9 100644 --- a/Gemfile +++ b/Gemfile @@ -10,93 +10,106 @@ gem 'rack-mini-profiler' gem 'haml' gem 'nokogiri', '~> 1.6.8.rc2' +gem 'tzinfo-data' +gem 'sass' +gem 'sass-rails' +gem 'uglifier', '>= 1.3.0' +# replace this once these changes are merged in sorcery +gem 'sorcery', git: 'https://github.com/tg90nor/sorcery.git', branch: 'make-facebook-provider-use-json-token-parser' +gem 'carrierwave' +gem 'carrierwave-imageoptimizer' +gem 'mini_magick' +gem 'activerecord-session_store' +gem 'premailer-rails' +gem 'sidekiq' +gem 'letter_opener' +gem 'launchy' + if Dir.exists?('../lingua_franca') - gem 'lingua_franca', :path => '../lingua_franca' + gem 'lingua_franca', path: '../lingua_franca' else - gem 'lingua_franca', :git => 'git://github.com/lingua-franca/lingua_franca.git' + gem 'lingua_franca', git: 'https://github.com/lingua-franca/lingua_franca.git', :branch => '2017' end -gem 'tzinfo-data' -gem 'sass' -gem 'sass-rails' +if Dir.exists?('../marmara') + gem 'marmara', path: '../marmara' +else + gem 'marmara', git: 'https://github.com/lingua-franca/marmara.git' +end -if Dir.exists?('../bumbleberry') - gem 'bumbleberry', :path => "../bumbleberry" +if Dir.exists?('../bikecollectives_core') + gem 'bikecollectives_core', path: '../bikecollectives_core' else - gem 'bumbleberry', :git => 'git://github.com/bumbleberry/bumbleberry.git' + gem 'bikecollectives_core', git: 'https://github.com/bikebike/bikecollectives_core.git' end -if Dir.exists?('../paypal-express') - gem 'paypal-express', :path => "../paypal-express" +if Dir.exists?('../bumbleberry') + gem 'bumbleberry', path: '../bumbleberry' else - gem 'paypal-express', :git => 'git://github.com/bikebike/paypal-express.git' + gem 'bumbleberry', git: 'https://github.com/bumbleberry/bumbleberry.git' end -gem 'uglifier', '>= 1.3.0' -gem 'sorcery', '>= 0.8.1' -gem 'oauth2', '~> 0.8.0' -gem 'carrierwave' -gem 'carrierwave-imageoptimizer' -gem 'mini_magick' + +# gem 'paper_trail', '~> 3.0.5' + +# Bike!Bike! specific stuff +gem 'paypal-express', git: 'https://github.com/ianfleeton/paypal-express' gem 'geocoder' -gem 'paper_trail', '~> 3.0.5' gem 'sitemap_generator' -gem 'activerecord-session_store' gem 'sass-json-vars' -gem 'premailer-rails' gem 'redcarpet' -gem 'sidekiq' -gem 'letter_opener' -gem 'launchy' -gem 'to_spreadsheet', :git => 'git://github.com/glebm/to_spreadsheet.git' - -group :test do - gem 'rspec' - gem 'rspec-rails' -end +gem 'to_spreadsheet', git: 'https://github.com/glebm/to_spreadsheet.git' group :development do - gem 'better_errors' - gem 'binding_of_caller' - gem 'meta_request' - - gem 'capistrano', '~> 3.1' - gem 'capistrano-rails', '~> 1.1' - gem 'capistrano-faster-assets', '~> 1.0' - - gem 'eventmachine', :github => 'krzcho/eventmachine', :branch => 'master' - gem 'thin'# , :github => 'krzcho/thin', :branch => 'master' + gem 'better_errors' + gem 'binding_of_caller' + gem 'meta_request' + + gem 'capistrano', '~> 3.1' + gem 'capistrano-rails', '~> 1.1' + gem 'capistrano-faster-assets', '~> 1.0' + + gem 'eventmachine', git: 'https://github.com/krzcho/eventmachine', :branch => 'master' + gem 'thin'# , :github => 'krzcho/thin', :branch => 'master' + gem 'rubocop', require: false + gem 'haml-lint', require: false end group :test do - gem 'gherkin3', '>= 3.1.0' - gem 'cucumber' - gem 'cucumber-core' - gem 'cucumber-rails' - - gem 'poltergeist' - gem 'guard-rspec' - gem 'factory_girl_rails' - gem 'coveralls', require: false - gem 'selenium-webdriver' - gem 'simplecov', require: false - gem 'webmock', require: false - gem 'database_cleaner' - gem 'mocha' + gem 'rspec' + gem 'rspec-rails' + gem 'gherkin3', '>= 3.1.0' + gem 'cucumber' + gem 'cucumber-core' + gem 'cucumber-rails', require: false + gem 'guard-cucumber' + + gem 'poltergeist' + gem 'capybara-email' + # gem 'capybara-webkit' + gem 'guard-rspec' + gem 'factory_girl_rails' + gem 'coveralls', require: false + gem 'selenium-webdriver' + gem 'simplecov', require: false + gem 'webmock', require: false + gem 'database_cleaner' + gem 'mocha' end group :staging, :production, :preview do - gem 'rails_12factor' + gem 'rails_12factor' end group :production, :preview do - gem 'unicorn' - gem 'daemon-spawn' - gem 'daemons' + gem 'unicorn', require: false + gem 'daemon-spawn' + gem 'daemons' end platforms 'mswin', 'mingw' do - group :test do - gem 'wdm', '>= 0.1.0' - end + group :test do + gem 'wdm', '>= 0.1.0' + gem 'win32console', require: false + end end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..e52b085 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,633 @@ +GIT + remote: https://github.com/glebm/to_spreadsheet.git + revision: 4c08455646dd18de51cc1ec05717fbb240c78a68 + specs: + to_spreadsheet (1.0.6) + axlsx + chronic + nokogiri + rails + responders + +GIT + remote: https://github.com/ianfleeton/paypal-express + revision: 629749621de4c65dd6651649f98410315520fb3d + specs: + paypal-express (0.8.1) + activesupport (>= 2.3) + attr_required (>= 0.0.5) + rest-client + +GIT + remote: https://github.com/krzcho/eventmachine + revision: 651a35ee9df9826e048c3b3721e2c6b415c5a328 + branch: master + specs: + eventmachine (1.2.1) + +GIT + remote: https://github.com/tg90nor/sorcery.git + revision: 79b69a87ce168c47fab76921874aa7e8cb727002 + branch: make-facebook-provider-use-json-token-parser + specs: + sorcery (0.10.3) + bcrypt (~> 3.1) + oauth (~> 0.4, >= 0.4.4) + oauth2 (~> 1.0, >= 0.8.0) + +PATH + remote: ../bikecollectives_core + specs: + bikecollectives_core (0.1.0) + activerecord-session_store + carrierwave + carrierwave-imageoptimizer + haml + launchy + letter_opener + mini_magick + pg + premailer-rails + rails (~> 4.2.0) + redcarpet + sass + sass-json-vars + sass-rails + sidekiq + uglifier (>= 1.3.0) + +PATH + remote: ../bumbleberry + specs: + bumbleberry (0.0.1) + blockspring + cairo + railties + rsvg2 + sass-json-vars + sass-rails + +PATH + remote: ../lingua_franca + specs: + lingua_franca (0.0.1) + diffy + forgery + http_accept_language + i18n + rails (~> 4.2.0.rc2) + rails-i18n + rubyzip + +PATH + remote: ../marmara + specs: + marmara (1.0.2) + css_parser (>= 1.5.0.pre) + +GEM + remote: http://rubygems.org/ + specs: + actionmailer (4.2.0) + actionpack (= 4.2.0) + actionview (= 4.2.0) + activejob (= 4.2.0) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.0) + actionview (= 4.2.0) + activesupport (= 4.2.0) + rack (~> 1.6.0) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + actionview (4.2.0) + activesupport (= 4.2.0) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + activejob (4.2.0) + activesupport (= 4.2.0) + globalid (>= 0.3.0) + activemodel (4.2.0) + activesupport (= 4.2.0) + builder (~> 3.1) + activerecord (4.2.0) + activemodel (= 4.2.0) + activesupport (= 4.2.0) + arel (~> 6.0) + activerecord-session_store (1.0.0) + actionpack (>= 4.0, < 5.1) + activerecord (>= 4.0, < 5.1) + multi_json (~> 1.11, >= 1.11.2) + rack (>= 1.5.2, < 3) + railties (>= 4.0, < 5.1) + activesupport (4.2.0) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + airbrussh (1.1.2) + sshkit (>= 1.6.1, != 1.7.0) + arel (6.0.4) + ast (2.3.0) + attr_required (1.0.1) + axlsx (2.0.1) + htmlentities (~> 4.3.1) + nokogiri (>= 1.4.1) + rubyzip (~> 1.0.0) + bcrypt (3.1.11-x64-mingw32) + bcrypt (3.1.11-x86-mingw32) + better_errors (2.1.1) + coderay (>= 1.0.0) + erubis (>= 2.6.6) + rack (>= 0.9.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + blockspring (0.1.4) + rest-client (> 1.6.7) + builder (3.2.3) + cairo (1.15.5-x64-mingw32) + pkg-config (>= 1.1.5) + cairo (1.15.5-x86-mingw32) + pkg-config (>= 1.1.5) + callsite (0.0.11) + capistrano (3.8.0) + airbrussh (>= 1.0.0) + i18n + rake (>= 10.0.0) + sshkit (>= 1.9.0) + capistrano-bundler (1.2.0) + capistrano (~> 3.1) + sshkit (~> 1.2) + capistrano-faster-assets (1.0.2) + capistrano (>= 3.1) + capistrano-rails (1.2.3) + capistrano (~> 3.1) + capistrano-bundler (~> 1.1) + capybara (2.13.0) + addressable + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + capybara-email (2.5.0) + capybara (~> 2.4) + mail + carrierwave (1.0.0) + activemodel (>= 4.0.0) + activesupport (>= 4.0.0) + mime-types (>= 1.16) + carrierwave-imageoptimizer (1.4.0) + carrierwave (>= 0.8, < 2.0) + image_optimizer (~> 1.6) + childprocess (0.6.3) + ffi (~> 1.0, >= 1.0.11) + chronic (0.10.2) + cliver (0.3.2) + coderay (1.1.1) + concurrent-ruby (1.0.5) + connection_pool (2.2.1) + coveralls (0.8.20) + json (>= 1.8, < 3) + simplecov (~> 0.14.1) + term-ansicolor (~> 1.3) + thor (~> 0.19.4) + tins (~> 1.6) + crack (0.4.3) + safe_yaml (~> 1.0.0) + css_parser (1.5.0.pre2) + addressable + cucumber (2.4.0) + builder (>= 2.1.2) + cucumber-core (~> 1.5.0) + cucumber-wire (~> 0.0.1) + diff-lcs (>= 1.1.3) + gherkin (~> 4.0) + multi_json (>= 1.7.5, < 2.0) + multi_test (>= 0.1.2) + cucumber-core (1.5.0) + gherkin (~> 4.0) + cucumber-rails (1.4.5) + capybara (>= 1.1.2, < 3) + cucumber (>= 1.3.8, < 4) + mime-types (>= 1.16, < 4) + nokogiri (~> 1.5) + railties (>= 3, < 5.1) + cucumber-wire (0.0.1) + daemon-spawn (0.4.2) + daemons (1.2.4) + database_cleaner (1.5.3) + debug_inspector (0.0.2) + diff-lcs (1.3) + diffy (3.2.0) + docile (1.1.5) + domain_name (0.5.20170404) + unf (>= 0.0.5, < 1.0.0) + erubis (2.7.0) + execjs (2.7.0) + factory_girl (4.8.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.8.0) + factory_girl (~> 4.8.0) + railties (>= 3.0.0) + faraday (0.11.0) + multipart-post (>= 1.2, < 3) + ffi (1.9.18-x64-mingw32) + ffi (1.9.18-x86-mingw32) + forgery (0.6.0) + formatador (0.2.5) + gdk_pixbuf2 (3.1.1-x64-mingw32) + gio2 (= 3.1.1) + gdk_pixbuf2 (3.1.1-x86-mingw32) + gio2 (= 3.1.1) + geocoder (1.4.3) + gherkin (4.1.1) + gherkin3 (3.1.2) + gio2 (3.1.1-x64-mingw32) + glib2 (= 3.1.1) + gobject-introspection (= 3.1.1) + gio2 (3.1.1-x86-mingw32) + glib2 (= 3.1.1) + gobject-introspection (= 3.1.1) + git-version-bump (0.15.1) + glib2 (3.1.1-x64-mingw32) + cairo (>= 1.12.8) + pkg-config + glib2 (3.1.1-x86-mingw32) + cairo (>= 1.12.8) + pkg-config + globalid (0.3.7) + activesupport (>= 4.1.0) + gobject-introspection (3.1.1-x64-mingw32) + glib2 (= 3.1.1) + gobject-introspection (3.1.1-x86-mingw32) + glib2 (= 3.1.1) + guard (2.14.1) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (~> 1.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-cucumber (2.1.2) + cucumber (~> 2.0) + guard-compat (~> 1.0) + nenv (~> 0.1) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) + haml (4.0.7) + tilt + haml-lint (0.999.999) + haml_lint + haml_lint (0.24.0) + haml (>= 4.0, < 5.1) + rainbow + rake (>= 10, < 13) + rubocop (>= 0.47.0) + sysexits (~> 1.1) + hashdiff (0.3.2) + htmlentities (4.3.4) + http-cookie (1.0.3) + domain_name (~> 0.5) + http_accept_language (2.1.0) + i18n (0.8.1) + image_optimizer (1.7.0) + json (1.8.6) + jwt (1.5.6) + kgio (2.11.0) + launchy (2.4.3) + addressable (~> 2.3) + letter_opener (1.4.1) + launchy (~> 2.2) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.0.3) + nokogiri (>= 1.5.9) + lumberjack (1.0.11) + mail (2.6.4) + mime-types (>= 1.16, < 4) + meta_request (0.4.0) + callsite (~> 0.0, >= 0.0.11) + rack-contrib (~> 1.1) + railties (>= 3.0.0, < 5.1.0) + metaclass (0.0.4) + method_source (0.8.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_magick (4.7.0) + mini_portile2 (2.1.0) + minitest (5.10.1) + mocha (1.2.1) + metaclass (~> 0.0.1) + multi_json (1.12.1) + multi_test (0.1.2) + multi_xml (0.6.0) + multipart-post (2.0.0) + nenv (0.3.0) + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-ssh (4.1.0) + netrc (0.11.0) + nokogiri (1.6.8.1-x64-mingw32) + mini_portile2 (~> 2.1.0) + nokogiri (1.6.8.1-x86-mingw32) + mini_portile2 (~> 2.1.0) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) + oauth (0.5.1) + oauth2 (1.3.1) + faraday (>= 0.8, < 0.12) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + pango (3.1.1-x64-mingw32) + cairo (>= 1.14.0) + glib2 (= 3.1.1) + pango (3.1.1-x86-mingw32) + cairo (>= 1.14.0) + glib2 (= 3.1.1) + parser (2.4.0.0) + ast (~> 2.2) + pg (0.20.0-x64-mingw32) + pg (0.20.0-x86-mingw32) + pkg-config (1.1.7) + poltergeist (1.14.0) + capybara (~> 2.1) + cliver (~> 0.3.1) + websocket-driver (>= 0.2.0) + powerpack (0.1.1) + premailer (1.10.2) + addressable + css_parser (>= 1.4.10) + htmlentities (>= 4.0.0) + premailer-rails (1.9.5) + actionmailer (>= 3, < 6) + premailer (~> 1.7, >= 1.7.9) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + public_suffix (2.0.5) + rack (1.6.5) + rack-contrib (1.4.0) + git-version-bump (~> 0.15) + rack (~> 1.4) + rack-mini-profiler (0.10.2) + rack (>= 1.2.0) + rack-protection (1.5.3) + rack + rack-test (0.6.3) + rack (>= 1.0) + rails (4.2.0) + actionmailer (= 4.2.0) + actionpack (= 4.2.0) + actionview (= 4.2.0) + activejob (= 4.2.0) + activemodel (= 4.2.0) + activerecord (= 4.2.0) + activesupport (= 4.2.0) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.0) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.8) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + rails-i18n (4.0.9) + i18n (~> 0.7) + railties (~> 4.0) + rails_12factor (0.0.3) + rails_serve_static_assets + rails_stdout_logging + rails_serve_static_assets (0.0.5) + rails_stdout_logging (0.0.5) + railties (4.2.0) + actionpack (= 4.2.0) + activesupport (= 4.2.0) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rainbow (2.2.1) + raindrops (0.18.0) + rake (11.1.2) + rb-fsevent (0.9.8) + rb-inotify (0.9.8) + ffi (>= 0.5.0) + redcarpet (3.4.0) + redis (3.3.3) + responders (2.3.0) + railties (>= 4.2.0, < 5.1) + rest-client (2.0.1-x64-mingw32) + ffi (~> 1.9) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + rest-client (2.0.1-x86-mingw32) + ffi (~> 1.9) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-rails (3.5.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + rsvg2 (3.1.1-x64-mingw32) + cairo (>= 1.12.8) + gdk_pixbuf2 (= 3.1.1) + pango (>= 3.1.1) + rsvg2 (3.1.1-x86-mingw32) + cairo (>= 1.12.8) + gdk_pixbuf2 (= 3.1.1) + pango (>= 3.1.1) + rubocop (0.48.1) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.8.1) + ruby_dep (1.3.1) + rubyzip (1.0.0) + safe_yaml (1.0.4) + sass (3.4.23) + sass-json-vars (0.3.3) + sass (>= 3.1) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.3.0) + childprocess (~> 0.5) + rubyzip (~> 1.0) + websocket (~> 1.0) + shellany (0.0.1) + sidekiq (4.2.10) + concurrent-ruby (~> 1.0) + connection_pool (~> 2.2, >= 2.2.0) + rack-protection (>= 1.5.0) + redis (~> 3.2, >= 3.2.1) + simplecov (0.14.1) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + sitemap_generator (5.3.1) + builder (~> 3.0) + slop (3.6.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sshkit (1.13.1) + net-scp (>= 1.1.2) + net-ssh (>= 2.8.0) + sysexits (1.2.0) + term-ansicolor (1.5.0) + tins (~> 1.0) + thin (1.7.0) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.7) + tins (1.13.2) + tzinfo (1.2.3) + thread_safe (~> 0.1) + tzinfo-data (1.2017.2) + tzinfo (>= 1.0.0) + uglifier (3.1.13) + execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.2-x64-mingw32) + unf_ext (0.0.7.2-x86-mingw32) + unicode-display_width (1.1.3) + unicorn (5.3.0) + kgio (~> 2.6) + raindrops (~> 0.7) + wdm (0.1.1) + webmock (2.3.2) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff + websocket (1.2.4) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + win32console (1.3.2-x86-mingw32) + xpath (2.0.0) + nokogiri (~> 1.3) + +PLATFORMS + x64-mingw32 + x86-mingw32 + +DEPENDENCIES + activerecord-session_store + better_errors + bikecollectives_core! + binding_of_caller + bumbleberry! + capistrano (~> 3.1) + capistrano-faster-assets (~> 1.0) + capistrano-rails (~> 1.1) + capybara-email + carrierwave + carrierwave-imageoptimizer + coveralls + cucumber + cucumber-core + cucumber-rails + daemon-spawn + daemons + database_cleaner + eventmachine! + factory_girl_rails + geocoder + gherkin3 (>= 3.1.0) + guard-cucumber + guard-rspec + haml + haml-lint + launchy + letter_opener + lingua_franca! + marmara! + meta_request + mini_magick + mocha + nokogiri (~> 1.6.8.rc2) + paypal-express! + pg + poltergeist + premailer-rails + rack-mini-profiler + rails (= 4.2.0) + rails_12factor + rake (= 11.1.2) + redcarpet + rspec + rspec-rails + rubocop + ruby_dep (= 1.3.1) + sass + sass-json-vars + sass-rails + selenium-webdriver + sidekiq + simplecov + sitemap_generator + sorcery! + thin + to_spreadsheet! + tzinfo-data + uglifier (>= 1.3.0) + unicorn + wdm (>= 0.1.0) + webmock + win32console + +BUNDLED WITH + 1.14.4 diff --git a/Guardfile b/Guardfile index e6e7c78..e5db626 100644 --- a/Guardfile +++ b/Guardfile @@ -22,3 +22,28 @@ guard :rspec do watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } end + +cucumber_options = { + # Below are examples overriding defaults + + # cmd: 'bin/cucumber', + # cmd_additional_args: '--profile guard', + + # all_after_pass: false, + # all_on_start: false, + # keep_failed: false, + # feature_sets: ['features/frontend', 'features/experimental'], + + # run_all: { cmd_additional_args: '--profile guard_all' }, + # focus_on: { 'wip' }, # @wip + # notification: false +} + +guard "cucumber", cucumber_options do + watch(%r{^features/.+\.feature$}) + watch(%r{^features/support/.+$}) { "features" } + + watch(%r{^features/step_definitions/(.+)_steps\.rb$}) do |m| + Dir[File.join("**/#{m[1]}.feature")][0] || "features" + end +end diff --git a/README.md b/README.md index 10a69b3..7ce286e 100644 --- a/README.md +++ b/README.md @@ -150,3 +150,8 @@ On hold until our design team determines a director for our identity. ## Testing Practices ## Our focus will be on integration testing using Capybara. While testing the app records all translations that it finds, whether or not they exist, and which pages that they were found on. + +## Attribution ## + +### Icons ### + diff --git a/Rakefile b/Rakefile index b9a026c..ffec68e 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,8 @@ # 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 'rubygems' +require 'cucumber' +require 'cucumber/rake/task' require File.expand_path('../config/application', __FILE__) @@ -61,3 +64,31 @@ task update_cities_fr: :environment do c.save! end end + +task :i18n do + LinguaFranca.test LinguaFranca::TestModes::RECORD do + Rake::Task[:cucumber].execute + end +end + +task :css do + ENV['CSS_TEST'] = '1' + Rake::Task[:cucumber].execute + ENV['CSS_TEST'] = nil +end + +task :a11y do + ENV['TEST_A11Y'] = '1' + Rake::Task[:cucumber].execute + ENV['TEST_A11Y'] = nil +end + +task "cucumber:debug" do + ENV['TEST_DEBUG'] = '1' + Rake::Task[:cucumber].execute + ENV['TEST_DEBUG'] = nil +end + +Cucumber::Rake::Task.new(:cucumber) do |t| + t.cucumber_opts = "features --format pretty" +end diff --git a/app/assets/images/test-poster.png b/app/assets/images/test-poster.png new file mode 100644 index 0000000000000000000000000000000000000000..29d20c1b7c42ef63f57bb45a48bc2f5b6e97d6b5 GIT binary patch literal 77534 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 literal 0 HcmV?d00001 diff --git a/app/assets/javascripts/userfield.js b/app/assets/javascripts/userfield.js index 3ee54d8..593e15a 100644 --- a/app/assets/javascripts/userfield.js +++ b/app/assets/javascripts/userfield.js @@ -21,6 +21,7 @@ var field = fields[i]; var input = field.getElementsByTagName('input')[0]; var name = field.getElementsByClassName('user-name')[0]; + } }, false); })(); diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index fb851df..d4121c4 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -1710,17 +1710,17 @@ ul.warnings { a.policy { background-color: $colour-5; - @include _(order, 1); + // @include _(order, 1); } a.about { background-color: $colour-3; - @include _(order, 1); + // @include _(order, 1); } a.conferences { background-color: $colour-2; - @include _(order, 1); + // @include _(order, 1); } form, .register { @@ -1728,7 +1728,8 @@ ul.warnings { } a, form { - width: 33%; + width: 33.333%; + float: left; @include _(flex, 1); } @@ -2948,12 +2949,11 @@ a.logo { .conference-banner { text-align: center; padding: 0; - margin: 0 -1em 2em; - width: auto; + margin: 0; .title { font-size: 5vw; - margin: 1em auto 2em; + margin: 1em auto 0; h1, h2 { margin: 0; @@ -2971,6 +2971,7 @@ a.logo { img { max-width: 100%; + margin-top: 2em; } } @@ -3311,8 +3312,11 @@ body.error-locale-not-available { body { #primary-content { @include _(transition, 'filter 250ms ease-in-out, -webkit-filter 250ms ease-in-out'); - display: -webkit-flex; - flex-direction: column; + + @if capable_of(flexbox, true) { + display: -webkit-flex; + flex-direction: column; + } min-height: 100vh; overflow: hidden; } @@ -4617,6 +4621,7 @@ html[data-ontop] { .nav a { &[class] { width: auto; + float: none; overflow: visible; margin-left: 0.725em; padding: 0.25em 0.5em; diff --git a/app/assets/stylesheets/bumbleberry-settings.json b/app/assets/stylesheets/bumbleberry-settings.json index c0bafee..b8d42ed 100644 --- a/app/assets/stylesheets/bumbleberry-settings.json +++ b/app/assets/stylesheets/bumbleberry-settings.json @@ -1,103 +1,105 @@ { - "stylesheets": ["application", "editor"], - "precompile": { - "test": { - "chrome": ["51"] - }, - "development": { - "and_chr": ["55"], - "chrome": ["55"], - "edge": ["13"], - "firefox": ["50"], - "ie": ["11"], - "ios_saf": ["8", "9"] - } + "stylesheets": ["application", "editor"], + "precompile": { + "test": { + "safari": ["5"], + "chrome": ["55"] }, - "background-color": "#FFFEFE", - "breakpoint-unit": "px", - "unresponsive-width": 1000, - "row-width": 1000, - "total-columns": 12, - "platforms": { - "mobile": { - "range": [0,700] - }, - "desktop": { - "range": [0,0] - } + "development": { + "safari": ["5"], + "and_chr": ["56"], + "chrome": ["56"], + "edge": ["13"], + "firefox": ["50"], + "ie": ["11"], + "ios_saf": ["8", "9"] + } + }, + "background-color": "#FFFEFE", + "breakpoint-unit": "px", + "unresponsive-width": 1000, + "row-width": 1000, + "total-columns": 12, + "platforms": { + "mobile": { + "range": [0,700] + }, + "desktop": { + "range": [0,0] + } + }, + "breakpoints": { + "small": { + "range": [0], + "grid": "y", + "offset": "n", + "reset-order": "n" }, - "breakpoints": { - "small": { - "range": [0], - "grid": "y", - "offset": "n", - "reset-order": "n" - }, - "medium": { - "range": [680], - "grid": "y", - "offset": "n", - "reset-order": "n" - }, - "large": { - "range": [1024], - "grid": "y", - "offset": "n", - "reset-order": "n" - }, - "small-only": { - "range": [0,319], - "grid": "n", - "offset": "n", - "reset-order": "n" - }, - "medium-only": { - "range": [320,1023], - "grid": "n", - "offset": "n", - "reset-order": "n" - } + "medium": { + "range": [680], + "grid": "y", + "offset": "n", + "reset-order": "n" }, - "grid-push": [1, 2], - "grid-pull": [1, 2], - "font-loading-method": "http2", - "fonts": { - "primary": { - "name": "Sanchez Light", - "location": "Sanchez", - "svg_id": "wf", - "ttf_type": "ttf", - "fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"] - }, - "secondary": { - "name": "AlteHaasGroteskBold", - "location": "AlteHaasGroteskBold", - "svg_id": "alte_haas_groteskbold", - "ttf_type": "ttf", - "fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"] - }, - "monospace": { - "name": "NotCourierSans", - "location": "NotCourierSans", - "svg_id": "notcouriersans", - "ttf_type": "ttf", - "fallback": ["Courier New", "monospace"], - "auto_load": "n" - }, - "monospace-bold": { - "name": "NotCourierSans", - "location": "NotCourierSans-Bold", - "svg_id": "notcouriersansbold", - "ttf_type": "ttf", - "fallback": ["Courier New", "monospace"], - "auto_load": "n", - "weight": "bold" - } + "large": { + "range": [1024], + "grid": "y", + "offset": "n", + "reset-order": "n" }, - "sprites": { - "icons": { - "bb-icon-logo": [0, 0, "182px", "149px"], - "bb-icon-logo-text": ["182px", 0, "136px", "149px"] - } + "small-only": { + "range": [0,319], + "grid": "n", + "offset": "n", + "reset-order": "n" + }, + "medium-only": { + "range": [320,1023], + "grid": "n", + "offset": "n", + "reset-order": "n" + } + }, + "grid-push": [1, 2], + "grid-pull": [1, 2], + "font-loading-method": "http2", + "fonts": { + "primary": { + "name": "Sanchez Light", + "location": "Sanchez", + "svg_id": "wf", + "ttf_type": "ttf", + "fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"] + }, + "secondary": { + "name": "AlteHaasGroteskBold", + "location": "AlteHaasGroteskBold", + "svg_id": "alte_haas_groteskbold", + "ttf_type": "ttf", + "fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"] + }, + "monospace": { + "name": "NotCourierSans", + "location": "NotCourierSans", + "svg_id": "notcouriersans", + "ttf_type": "ttf", + "fallback": ["Courier New", "monospace"], + "auto_load": "n" + }, + "monospace-bold": { + "name": "NotCourierSans", + "location": "NotCourierSans-Bold", + "svg_id": "notcouriersansbold", + "ttf_type": "ttf", + "fallback": ["Courier New", "monospace"], + "auto_load": "n", + "weight": "bold" + } + }, + "sprites": { + "icons": { + "bb-icon-logo": [0, 0, "182px", "149px"], + "bb-icon-logo-text": ["182px", 0, "136px", "149px"] } + } } diff --git a/app/assets/stylesheets/user-mailer.scss b/app/assets/stylesheets/user-mailer.scss index af6e781..b312e63 100644 --- a/app/assets/stylesheets/user-mailer.scss +++ b/app/assets/stylesheets/user-mailer.scss @@ -1,268 +1,268 @@ @import "settings"; body { - width: 100% !important; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - margin: 1em; - padding: 0; + width: 100% !important; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + margin: 1em; + padding: 0; } .ExternalClass { - width: 100%; - line-height: 100%; + width: 100%; + line-height: 100%; - p, span, font, td, div { - line-height: 100%; - } + p, span, font, td, div { + line-height: 100%; + } } #backgroundTable { - margin: 0; - padding: 0; - width: 100% !important; - max-width: 100% !important; - line-height: 100% !important; + margin: 0; + padding: 0; + width: 100% !important; + max-width: 100% !important; + line-height: 100% !important; } img { - outline: none; - text-decoration: none; - -ms-interpolation-mode: bicubic; + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; - a & { - border: none; - } + a & { + border: none; + } } .image_fix { - display: block; - max-width: 100%; + display: block; + max-width: 100%; } a { - color: $colour-1; - - #outlook & { - padding: 0; - } - - &:link { color: $colour-1; } - &:visited { color: $colour-1; } - &:hover { color: $colour-5; } - - @media only screen and (max-device-width: 480px) { - &[href^="tel"], &[href^="sms"] { - text-decoration: none; - color: $black; - pointer-events: none; - cursor: default; - } - - .mobile_link &[href^="tel"], .mobile_link &[href^="sms"] { - text-decoration: default; - color: $colour-1 !important; - pointer-events: auto; - cursor: default; - } - } + color: $colour-1; + + #outlook & { + padding: 0; + } + + &:link { color: $colour-1; } + &:visited { color: $colour-1; } + &:hover { color: $colour-5; } + + @media only screen and (max-device-width: 480px) { + &[href^="tel"], &[href^="sms"] { + text-decoration: none; + color: $black; + pointer-events: none; + cursor: default; + } + + .mobile_link &[href^="tel"], .mobile_link &[href^="sms"] { + text-decoration: default; + color: $colour-1 !important; + pointer-events: auto; + cursor: default; + } + } } p { - color: $black !important; - font-size: 1.5em; + color: $black !important; + font-size: 1.5em; } p, blockquote { - margin: 1em; - line-height: 1.3333em; + margin: 1em; + line-height: 1.3333em; } blockquote { - font-size: 1em; + font-size: 1em; } h1 { - font-size: 2.5em; - line-height: 1.25em; - padding: 1em 0; - - &:first-child { - padding-top: 0; - } + font-size: 2.5em; + line-height: 1.25em; + padding: 1em 0; + + &:first-child { + padding-top: 0; + } } h2 { - font-size: 1.8em; - line-height: 1.25em; - padding-bottom: 1em; + font-size: 1.8em; + line-height: 1.25em; + padding-bottom: 1em; } blockquote { - font-style: italic; - margin-bottom: 2em; - color: #666 !important; - border: 0.1em solid #CCC; - padding: 1em; - border-style: none none solid solid; - background-color: #F8F8F8; + font-style: italic; + margin-bottom: 2em; + color: #666 !important; + border: 0.1em solid #CCC; + padding: 1em; + border-style: none none solid solid; + background-color: #F8F8F8; } h1, h2, h3, h4, h5, h6 { - color: $black !important; - - a { - color: $colour-1 !important; - - &:active { - color: $colour-4 !important; - } - - &:visited { - color: $colour-2 !important; - } - } + color: $black !important; + + a { + color: $colour-1 !important; + + &:active { + color: $colour-4 !important; + } + + &:visited { + color: $colour-2 !important; + } + } } table { - border-collapse: collapse; - max-width: 512px; - min-width: 280px; - mso-table-lspace: 0pt; - mso-table-rspace: 0pt; - - td { - border-collapse: collapse; - } - - th { - text-align: left; - } + border-collapse: collapse; + max-width: 512px; + min-width: 280px; + mso-table-lspace: 0pt; + mso-table-rspace: 0pt; + + td { + border-collapse: collapse; + } + + th { + text-align: left; + } } table#bb_full_width, table#ecxbb_full_width { - &, table { - max-width: none; - width: 100%; - } + &, table { + max-width: none; + width: 100%; + } } .error-report { - width: 100%; - max-width: 100%; - border: 0.15em solid #CCC; - background-color: #EEE; - font-size: 1.25em; - margin-bottom: 2em; - - th { - background-color: #CCC; - } - - td, th { - padding: 0.25em 0.5em; - - &:last-child { - border-left: 0.15em solid #CCC; - min-width: 40em; - } - } - - td:last-child { - font-family: monospace; - word-break: break-word; - background-color: #FFF; - font-size: 0.75em; - } + width: 100%; + max-width: 100%; + border: 0.15em solid #CCC; + background-color: #EEE; + font-size: 1.25em; + margin-bottom: 2em; + + th { + background-color: #CCC; + } + + td, th { + padding: 0.25em 0.5em; + + &:last-child { + border-left: 0.15em solid #CCC; + min-width: 40em; + } + } + + td:last-child { + font-family: monospace; + word-break: break-word; + background-color: #FFF; + font-size: 0.75em; + } } code { - color: #C33; - font-size: 0.9em; + color: #C33; + font-size: 0.9em; } pre { - font-size: 1.5em; - padding: 1em; - background-color: #333; - color: antiquewhite; - word-break: break-word; + font-size: 1.5em; + padding: 1em; + background-color: #333; + color: antiquewhite; + word-break: break-word; } .diff, .ecxdiff { - margin: 1em 0 5em 1em; - overflow: auto; - - ul { - list-style: none; - padding: 0; - margin: 0; - position: relative; - } - - li { - width: 40%; - display: inline-block; - padding: 0.5em; - margin: 0; - border-bottom: 1px solid #DDD; - //white-space: nowrap; - - &:nth-child(odd) { - margin-right: -3px; - float: left; - clear: left; - } - - ins { - color: #080;//lighten($colour-3, 33%); - } - - del { - color: #800; - } - } + margin: 1em 0 5em 1em; + overflow: auto; + + ul { + list-style: none; + padding: 0; + margin: 0; + position: relative; + } + + li { + width: 40%; + display: inline-block; + padding: 0.5em; + margin: 0; + border-bottom: 1px solid #DDD; + //white-space: nowrap; + + &:nth-child(odd) { + margin-right: -3px; + float: left; + clear: left; + } + + ins { + color: #080;//lighten($colour-3, 33%); + } + + del { + color: #800; + } + } } // Buttons h3 { - b { - padding: 10px 20px; - line-height: 50px; - - a, a:visited { - color: #FFF !important; - background-color: $colour-5; - text-decoration: none !important; - border-radius: 4px; - padding: 10px 15px; - margin-left: 20px; - border-bottom: 3px solid darken($colour-5, 10%); - -webkit-box-shadow: 0 0.5em 1.5em -0.75em #000; - box-shadow: 0 0.5em 1.5em -0.75em #000; - cursor: pointer !important; - } - - a:hover { - background-color: darken($colour-5, 10%); - } - } + b { + padding: 10px 20px; + line-height: 50px; + + a, a:visited { + color: #FFF !important; + background-color: $colour-5; + text-decoration: none !important; + border-radius: 4px; + padding: 10px 15px; + margin-left: 20px; + border-bottom: 3px solid darken($colour-5, 10%); + -webkit-box-shadow: 0 0.5em 1.5em -0.75em #000; + box-shadow: 0 0.5em 1.5em -0.75em #000; + cursor: pointer !important; + } + + a:hover { + background-color: darken($colour-5, 10%); + } + } } @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { - a[href^="tel"], a[href^="sms"] { - text-decoration: none; - color: $colour-1; - pointer-events: none; - cursor: default; - - .mobile_link & { - text-decoration: default; - color: $colour-1 !important; - pointer-events: auto; - cursor: default; - } - } + a[href^="tel"], a[href^="sms"] { + text-decoration: none; + color: $colour-1; + pointer-events: none; + cursor: default; + + .mobile_link & { + text-decoration: default; + color: $colour-1 !important; + pointer-events: auto; + cursor: default; + } + } } diff --git a/app/controllers/admin_controller.rb b/app/controllers/administration_controller.rb similarity index 94% rename from app/controllers/admin_controller.rb rename to app/controllers/administration_controller.rb index 1c03f0d..4c5d980 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/administration_controller.rb @@ -1,51 +1,51 @@ -require 'geocoder/calculations' - -class AdminController < ApplicationController - def new - return do_404 unless logged_in? && current_user.administrator? - @this_conference = Conference.new - @main_title = @page_title = 'articles.conferences.headings.new' - end - - def edit - return do_404 unless logged_in? && current_user.administrator? - @this_conference = Conference.find_by!(slug: params[:slug]) - @page_title = 'articles.conferences.headings.edit' - @main_title_vars = { vars: { title: @this_conference.title } } - render 'new' - end - - def save - conference = params[:id].present? ? Conference.find_by!(id: params[:id]) : Conference.new - - if params[:button] == 'save' - city = City.search(params[:city]) - conference.city_id = city.id - conference.conferencetype = params[:type] - conference.year = params[:year].to_i - conference.is_public = params[:is_public].present? - conference.is_featured = params[:is_featured].present? - conference.make_slug(true) - conference.save! - elsif params[:button] == 'delete' - conference.destroy - return redirect_to conferences_url - end - - redirect_to conference_url(conference.slug) - end - - rescue_from ActiveRecord::PremissionDenied do |exception| - if logged_in? - redirect_to :register - else - @register_template = :confirm_email - @page_title = "articles.conference_registration.headings.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" - render :register - end - end - - rescue_from ActiveRecord::RecordNotFound do |exception| - do_404 - end -end +require 'geocoder/calculations' + +class AdministrationController < ApplicationController + def new + return do_404 unless logged_in? && current_user.administrator? + @this_conference = Conference.new + @main_title = @page_title = 'articles.conferences.headings.new' + end + + def edit + return do_404 unless logged_in? && current_user.administrator? + @this_conference = Conference.find_by!(slug: params[:slug]) + @page_title = 'articles.conferences.headings.edit' + @main_title_vars = { vars: { title: @this_conference.title } } + render 'new' + end + + def save + conference = params[:id].present? ? Conference.find_by!(id: params[:id]) : Conference.new + + if params[:button] == 'save' + city = City.search(params[:city]) + conference.city_id = city.id + conference.conferencetype = params[:type] + conference.year = params[:year].to_i + conference.is_public = params[:is_public].present? + conference.is_featured = params[:is_featured].present? + conference.make_slug(true) + conference.save! + elsif params[:button] == 'delete' + conference.destroy + return redirect_to conferences_url + end + + redirect_to conference_url(conference.slug) + end + + rescue_from ActiveRecord::PremissionDenied do |exception| + if logged_in? + redirect_to :register + else + @register_template = :confirm_email + @page_title = "articles.conference_registration.headings.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" + render :register + end + end + + rescue_from ActiveRecord::RecordNotFound do |exception| + do_404 + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7f6039b..ae7c2e6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,11 +1,4 @@ -module ActiveRecord - class PremissionDenied < RuntimeError - end -end - -class ApplicationController < LinguaFrancaApplicationController - # Prevent CSRF attacks by raising an exception. - # For APIs, you may want to use :null_session instead. +class ApplicationController < BaseController protect_from_forgery with: :exception, :except => [:do_confirm, :js_error, :admin_update] before_filter :capture_page_info @@ -16,6 +9,7 @@ class ApplicationController < LinguaFrancaApplicationController @@test_location def capture_page_info + # capture request info in case an error occurs if request.method == "GET" && (params[:controller] != 'application' || params[:action] != 'contact') session[:last_request] request_info = { @@ -27,14 +21,11 @@ class ApplicationController < LinguaFrancaApplicationController 'env' => Hash.new } } - request.env.each do | key, value | + request.env.each do |key, value| request_info['request']['env'][key.to_s] = value.to_s end session['request_info'] = request_info end - # set the translator to the current user if we're logged in - I18n.config.translator = current_user - I18n.config.callback = self # get the current conferences and set them globally status_hierarchy = { @@ -57,11 +48,9 @@ class ApplicationController < LinguaFrancaApplicationController @_inline_scripts ||= [] @_inline_scripts << Rails.application.assets.find_asset('main.js').to_s - ActionMailer::Base.default_url_options = {:host => "#{request.protocol}#{request.host_with_port}"} - - if request.post? && params[:action] == 'do_confirm' - halt_redirection! - end + ActionMailer::Base.default_url_options = { + host: "#{request.protocol}#{request.host_with_port}" + } @alt_lang_urls = {} I18n.backend.enabled_locales.each do |locale| @@ -72,17 +61,16 @@ class ApplicationController < LinguaFrancaApplicationController # give each environment a different icon and theme colour so that we can easily see where we are. See https://css-tricks.com/give-development-domain-different-favicon-production @favicon = Rails.env.development? || Rails.env.preview? ? "favicon-#{Rails.env.to_s}.ico" : 'favicon.ico' @theme_colour = Rails.env.preview? ? '#EF57B4' : (Rails.env.development? ? '#D89E59' : '#00ADEF') - - # call the base method to detect the language - super end def home - @workshops = Workshop.where(:conference_id => @conference.id) + if @conference.present? && @conference.id.present? + @workshops = Workshop.where(conference_id: @conference.id) - if @conference.workshop_schedule_published - @event_dlg = true - get_scheule_data(false) + if @conference.workshop_schedule_published + @event_dlg = true + get_scheule_data(false) + end end end @@ -90,14 +78,6 @@ class ApplicationController < LinguaFrancaApplicationController @is_policy_page = true end - def robots - render :text => File.read("config/robots-#{Rails.env.production? ? 'live' : 'dev'}.txt"), :content_type => 'text/plain' - end - - def humans - render :text => File.read("config/humans.txt"), :content_type => 'text/plain' - end - def self.set_host(host) @@test_host = host end @@ -110,35 +90,6 @@ class ApplicationController < LinguaFrancaApplicationController @@test_location end - def do_404 - error_404(status: 404) - end - - def error_404(args = {}) - params[:_original_action] = params[:action] - params[:action] = 'error-404' - @page_title = 'page_titles.404.Page_Not_Found' - @main_title = 'error.404.title' - render 'application/404', args - end - - def do_403(template = nil) - @template = template - @page_title ||= 'page_titles.403.Access_Denied' - @main_title ||= @page_title - params[:_original_action] = params[:action] - params[:action] = 'error-403' - render 'application/permission_denied', status: 403 - end - - def error_500(exception = nil) - @page_title = 'page_titles.500.An_Error_Occurred' - @main_title = 'error.500.title' - params[:_original_action] = params[:action] - params[:action] = 'error-500' - render 'application/500', status: 500 - end - def js_error # send and email if this is production report = "A JavaScript error has occurred on #{params[:location]}" @@ -150,7 +101,7 @@ class ApplicationController < LinguaFrancaApplicationController begin # log the error - logger.info "Javascript exception: #{params[:message]}" + logger.info "A JavaScript error has occurred on #{params[:location]}:#{params[:lineNumber]}: #{params[:message]}" UserMailer.send_mail(:error_report) do [ @@ -163,7 +114,7 @@ class ApplicationController < LinguaFrancaApplicationController current_user, Time.now.strftime("%d/%m/%Y %H:%M") ] - end# if Rails.env.preview? || Rails.env.production? + end if Rails.env.preview? || Rails.env.production? rescue Exception => exception2 logger.info exception2.to_s logger.info exception2.backtrace.join("\n") @@ -171,31 +122,27 @@ class ApplicationController < LinguaFrancaApplicationController render json: {} end - rescue_from ActiveRecord::RecordNotFound do |exception| - do_404 - end - - rescue_from ActiveRecord::PremissionDenied do |exception| - do_403 - end + def confirmation_sent(user) + template = 'login_confirmation_sent' + @page_title ||= 'page_titles.403.Please_Check_Email' - rescue_from AbstractController::ActionNotFound do |exception| - @banner_image = 'grafitti.jpg' - - if current_user - @page_title = nil#'page_titles.Please_Login' - do_403 'not_a_translator' - #return - else - @page_title = 'page_titles.403.Please_Login' - do_403 'translator_login' + if (request.present? && request.referrer.present? && conference = /^\/conferences\/(\w+)\/register\/?$/.match(request.referrer.gsub(/^https?:\/\/.*?\//, '/'))) + @this_conference = Conference.find_by!(slug: conference[1]) + @banner_image = @this_conference.cover_url + template = 'conferences/email_confirm' end + + do_403 template end def locale_not_enabled!(locale = nil) locale_not_available!(locale) end + def locale_not_available + locale_not_available!(params[:locale]) + end + def locale_not_available!(locale = nil) set_default_locale params[:_original_action] = params[:action] @@ -206,11 +153,13 @@ class ApplicationController < LinguaFrancaApplicationController render 'application/locale_not_available', status: 404 end - rescue_from StandardError do |exception| - handle_exception exception + unless Rails.env.test? + rescue_from StandardError do |exception| + handle_exception exception - # show the error page - error_500 exception + # show the error page + error_500 exception + end end def handle_exception(exception) @@ -246,37 +195,6 @@ class ApplicationController < LinguaFrancaApplicationController end end - def generate_confirmation(user, url, expiry = nil) - if user.is_a? String - user = User.find_user(user) - - # if the user doesn't exist, just show them a 403 - do_403 unless user.present? - end - expiry ||= (Time.now + 12.hours) - session[:confirm_uid] = user.id - - unless user.locale.present? - user.locale = I18n.locale - user.save - end - - # send the confirmation email and make sure it get sent as quickly as possible - UserMailer.send_mail :email_confirmation do - EmailConfirmation.create(user_id: user.id, expiry: expiry, url: url) - end - end - - def user_settings - @conferences = Array.new - if logged_in? - Conference.all.each do | conference | - @conferences << conference if conference.host? current_user - end - end - @main_title = @page_title = 'page_titles.user_settings.Your_Account' - end - def contact @main_title = @page_title = 'page_titles.contact.Contact_Us' end @@ -286,8 +204,8 @@ class ApplicationController < LinguaFrancaApplicationController if params[:reason] == 'conference' - @conference.organizations.each do | org | - org.users.each do | user | + @conference.organizations.each do |org| + org.users.each do |user| email_list << user.named_email end end @@ -322,31 +240,8 @@ class ApplicationController < LinguaFrancaApplicationController render 'contact' end - def update_user_settings - return do_403 unless logged_in? - current_user.firstname = params[:name] - current_user.lastname = nil - current_user.languages = (params[:languages] || { I18n.locale.to_s => true }).keys - current_user.is_subscribed = params[:email_subscribe].present? - current_user.save - redirect_to settings_path - end - - def do_confirm(settings = nil) - settings ||= {:template => 'login_confirmation_sent'} + def confirm_user if params[:email] - # see if we've already sent the confirmation email and are just confirming - # the email address - if params[:token] - user = User.find_user(params[:email]) - confirm(user) - return - end - user = User.get(params[:email]) - - # generate the confirmation, send the email and show the 403 - referrer = params[:dest] || (request.present? && request.referer.present? ? request.referer.gsub(/^.*?\/\/.*?\//, '/') : settings_path) - generate_confirmation(params[:email], referrer) template = 'login_confirmation_sent' @page_title ||= 'page_titles.403.Please_Check_Email' @@ -361,75 +256,44 @@ class ApplicationController < LinguaFrancaApplicationController @banner_image ||= 'grafitti.jpg' @page_title ||= 'page_titles.403.Please_Login' - do_403 (template || 'translator_login') + do_403 template else do_404 end end - def confirm(uid = nil) - @confirmation = EmailConfirmation.find_by_token(params[:token]) - - unless @confirmation.present? - @token_not_found = true - return do_404 - end - - confirm_user = nil - if uid.is_a?(User) - confirm_user = uid - uid = confirm_user.id - end - # check to see if we were given a user id to confirm against - # if we were, make sure it was the same one - if (uid ||= (params[:uid] || session[:confirm_uid])) - if uid == @confirmation.user_id - session[:uid] = nil - confirm_user ||= User.find uid - auto_login(confirm_user) - else - @confirmation.delete - end - - redirect_to (@confirmation.url || '/') - return - end - - @banner_image = 'grafitti.jpg' - @page_title = 'page_titles.403.Please_Confirm_Email' - do_403 'login_confirm' + def error_404(args = {}) + params[:_original_action] = params[:action] + params[:action] = 'error-404' + @page_title = 'page_titles.404.Page_Not_Found' + @main_title = 'error.404.title' + super(args) end - def translator_request + def do_403(template = nil) @banner_image = 'grafitti.jpg' - @page_title = 'page_titles.403.Translator_Request_Sent' - do_403 'translator_request_sent' - end - - def user_logout - logout() - redirect_to (params[:url] || '/') - end + + unless current_user + @page_title = 'page_titles.403.Please_Login' + end - def find_user - user = User.find_user(params[:e]) + @template = template + @page_title ||= 'page_titles.403.Access_Denied' + @main_title ||= @page_title + params[:_original_action] = params[:action] + params[:action] = 'error-403' - if user.present? - return render json: { - name: user.name, - email: user.email, - exists: true - } - end - render json: { - name: I18n.t('user.not_found'), - email: nil, - exists: false - } + super(template) end - def login_user(u) - auto_login(u) + def error_500(exception = nil) + @page_title = 'page_titles.500.An_Error_Occurred' + @main_title = 'error.500.title' + params[:_original_action] = params[:action] + params[:action] = 'error-500' + @exception = exception + + super(exception) end def on_translation_change(object, data, locale, translator_id) @@ -541,8 +405,8 @@ class ApplicationController < LinguaFrancaApplicationController @schedule = {} day_1 = conference.start_date.wday - @workshop_blocks.each_with_index do | info, block | - info['days'].each do | block_day | + @workshop_blocks.each_with_index do |info, block| + info['days'].each do |block_day| day_diff = block_day.to_i - day_1 day_diff += 7 if day_diff < 0 day = (conference.start_date + day_diff.days).to_date @@ -555,9 +419,10 @@ class ApplicationController < LinguaFrancaApplicationController end end - @workshops.each do | workshop | + @workshops.each do |workshop| if workshop.block.present? block = @workshop_blocks[workshop.block['block'].to_i] + day_diff = workshop.block['day'].to_i - day_1 day_diff += 7 if day_diff < 0 day = (conference.start_date + day_diff.days).to_date @@ -569,7 +434,7 @@ class ApplicationController < LinguaFrancaApplicationController end end - @meals.each do | time, meal | + @meals.each do |time, meal| day = meal['day'].to_date time = meal['time'].to_f @schedule[day] ||= {} @@ -580,7 +445,7 @@ class ApplicationController < LinguaFrancaApplicationController @schedule[day][:times][time][:item] = meal end - @events.each do | event | + @events.each do |event| if event.present? && event.start_time.present? && event.end_time.present? day = event.start_time.midnight.to_date time = event.start_time.hour.to_f + (event.start_time.min / 60.0) @@ -594,13 +459,13 @@ class ApplicationController < LinguaFrancaApplicationController end @schedule = @schedule.sort.to_h - @schedule.each do | day, data | + @schedule.each do |day, data| @schedule[day][:times] = data[:times].sort.to_h end - @schedule.each do | day, data | + @schedule.each do |day, data| last_event = nil - data[:times].each do | time, time_data | + data[:times].each do |time, time_data| if last_event.present? @schedule[day][:times][last_event][:next_event] = time end @@ -609,8 +474,8 @@ class ApplicationController < LinguaFrancaApplicationController @schedule[day][:num_locations] = (data[:locations] || []).size end - @schedule.deep_dup.each do | day, data | - data[:times].each do | time, time_data | + @schedule.deep_dup.each do |day, data| + data[:times].each do |time, time_data| if time_data[:next_event].present? || time_data[:length] > 0.5 span = 0.5 length = time_data[:next_event].present? ? time_data[:next_event] - time : time_data[:length] @@ -627,7 +492,7 @@ class ApplicationController < LinguaFrancaApplicationController @schedule = @schedule.sort.to_h - @schedule.each do | day, data | + @schedule.each do |day, data| @schedule[day][:times] = data[:times].sort.to_h @schedule[day][:locations] ||= {} @@ -635,19 +500,19 @@ class ApplicationController < LinguaFrancaApplicationController @schedule[day][:locations][0] = :add if do_analyze || @schedule[day][:locations].empty? if do_analyze - data[:times].each do | time, time_data | + data[:times].each do |time, time_data| if time_data[:type] == :workshop && time_data[:item].present? && time_data[:item][:workshops].present? ids = time_data[:item][:workshops].keys - (0..ids.length).each do | i | + (0..ids.length).each do |i| if time_data[:item][:workshops][ids[i]].present? workshop_i = time_data[:item][:workshops][ids[i]][:workshop] conflicts = {} - (i+1..ids.length).each do | j | + (i+1..ids.length).each do |j| workshop_j = time_data[:item][:workshops][ids[j]].present? ? time_data[:item][:workshops][ids[j]][:workshop] : nil if workshop_i.present? && workshop_j.present? - workshop_i.active_facilitators.each do | facilitator_i | - workshop_j.active_facilitators.each do | facilitator_j | + workshop_i.active_facilitators.each do |facilitator_i| + workshop_j.active_facilitators.each do |facilitator_j| if facilitator_i.id == facilitator_j.id @schedule[day][:times][time][:status] ||= {} @schedule[day][:times][time][:item][:workshops][ids[j]][:status][:errors] << { @@ -669,7 +534,7 @@ class ApplicationController < LinguaFrancaApplicationController needs = JSON.parse(workshop_i.needs || '[]').map &:to_sym amenities = JSON.parse(location.amenities || '[]').map &:to_sym - needs.each do | need | + needs.each do |need| @schedule[day][:times][time][:item][:workshops][ids[i]][:status][:errors] << { name: :need_not_available, need: need, @@ -685,7 +550,7 @@ class ApplicationController < LinguaFrancaApplicationController # collect common interested users interests = [] - (0..ids.length).each do | j | + (0..ids.length).each do |j| workshop_j = time_data[:item][:workshops][ids[j]].present? ? time_data[:item][:workshops][ids[j]][:workshop] : nil if workshop_i.present? && workshop_j.present? && workshop_i.id != workshop_j.id interests = interests | workshop_j.interested.map { | u | u.user_id } @@ -740,4 +605,9 @@ class ApplicationController < LinguaFrancaApplicationController def html_value(value) return value.present? && ActionView::Base.full_sanitizer.sanitize(value).strip.present? ? value : nil end + + # send the confirmation email and make sure it get sent as quickly as possible + def send_confirmation(confirmation) + UserMailer.send_mail(:email_confirmation) { confirmation } + end end diff --git a/app/controllers/conference_administration_controller.rb b/app/controllers/conference_administration_controller.rb index e64d547..2fef5c8 100644 --- a/app/controllers/conference_administration_controller.rb +++ b/app/controllers/conference_administration_controller.rb @@ -62,7 +62,10 @@ class ConferenceAdministrationController < ApplicationController set_flash_messages # redirect to the step unless the method handled redirection itself - unless self.send(method_name) + case self.send(method_name) + when true + administration_step(@admin_step) + when false redirect_to administration_step_path(@this_conference.slug, @admin_step) end end @@ -100,6 +103,9 @@ class ConferenceAdministrationController < ApplicationController end def administrate_broadcast + if @this_conference.start_date.blank? || @this_conference.end_date.blank? + @warning_message = :no_date_warning + end end def administrate_broadcast_sent @@ -120,6 +126,9 @@ class ConferenceAdministrationController < ApplicationController end def administrate_registration_status + if @this_conference.start_date.blank? || @this_conference.end_date.blank? + @warning_message = :no_date_warning + end end def administrate_organizations @@ -142,7 +151,7 @@ class ConferenceAdministrationController < ApplicationController }, data: [], } - @organizations.each do | org | + @organizations.each do |org| if org.present? address = org.locations.first @excel_data[:data] << { @@ -158,18 +167,23 @@ class ConferenceAdministrationController < ApplicationController } end end - return respond_to do | format | + return respond_to do |format| format.xlsx { render xlsx: :stats, filename: "organizations" } end end end def administrate_registrations + if @this_conference.start_date.blank? || @this_conference.end_date.blank? + @warning_message = :no_date_warning + return + end + get_stats(!request.format.xlsx?) if request.format.xlsx? logger.info "Generating stats.xls" - return respond_to do | format | + return respond_to do |format| format.xlsx { render xlsx: :stats, filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } end end @@ -180,7 +194,7 @@ class ConferenceAdministrationController < ApplicationController @donation_count = 0 @donations = 0 @food = { meat: 0, vegan: 0, vegetarian: 0, all: 0 } - @registrations.each do | r | + @registrations.each do |r| if view_context.registration_status(r) == :registered @completed_registrations += 1 @@ -200,11 +214,16 @@ class ConferenceAdministrationController < ApplicationController end def administrate_stats + if @this_conference.start_date.blank? || @this_conference.end_date.blank? + @warning_message = :no_date_warning + return + end + get_stats(!request.format.xlsx?) if request.format.xlsx? logger.info "Generating stats.xls" - return respond_to do | format | + return respond_to do |format| format.xlsx { render xlsx: :stats, filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } end else @@ -214,7 +233,7 @@ class ConferenceAdministrationController < ApplicationController @donation_count = 0 @donations = 0 @food = { meat: 0, vegan: 0, vegetarian: 0, all: 0 } - @registrations.each do | r | + @registrations.each do |r| if view_context.registration_status(r) == :registered @completed_registrations += 1 @@ -256,7 +275,7 @@ class ConferenceAdministrationController < ApplicationController }, data: [], } - @hosts.each do | id, host | + @hosts.each do |id, host| data = (host.housing_data || {}) host_data = { name: host.user.name, @@ -264,7 +283,7 @@ class ConferenceAdministrationController < ApplicationController email: host.user.email, phone: data['phone'], availability: data['availability'].present? && data['availability'][1].present? ? view_context.date_span(data['availability'][0].to_date, data['availability'][1].to_date) : '', - considerations: ((data['considerations'] || []).map { | consideration | view_context._"articles.conference_registration.host.considerations.#{consideration}" }).join(', '), + considerations: ((data['considerations'] || []).map { |consideration| view_context._"articles.conference_registration.host.considerations.#{consideration}" }).join(', '), empty: '', guests: { columns: [:name, :area, :email, :arrival_departure, :allergies, :food, :companion, :city], @@ -285,8 +304,8 @@ class ConferenceAdministrationController < ApplicationController } } - @housing_data[id][:guests].each do | space, space_data | - space_data.each do | guest_id, guest_data | + @housing_data[id][:guests].each do |space, space_data| + space_data.each do |guest_id, guest_data| guest = guest_data[:guest] if guest.present? companion = view_context.companion(guest) @@ -307,7 +326,7 @@ class ConferenceAdministrationController < ApplicationController @excel_data[:data] << host_data end - return respond_to do | format | + return respond_to do |format| format.xlsx { render xlsx: :stats, filename: "housing" } end end @@ -439,7 +458,7 @@ class ConferenceAdministrationController < ApplicationController }, data: [] } - User.AVAILABLE_LANGUAGES.each do | l | + User.AVAILABLE_LANGUAGES.each do |l| @excel_data[:keys]["language_#{l}".to_sym] = "languages.#{l.to_s}" end ConferenceRegistration.all_spaces.each do |s| @@ -449,7 +468,7 @@ class ConferenceAdministrationController < ApplicationController ConferenceRegistration.all_considerations.each do |c| @excel_data[:keys][c] = "articles.conference_registration.host.considerations.#{c.to_s}" end - @registrations.each do | r | + @registrations.each do |r| user = r.user_id ? User.where(id: r.user_id).first : nil if user.present? companion = view_context.companion(r) @@ -509,7 +528,7 @@ class ConferenceAdministrationController < ApplicationController last_day: availability[1].present? ? view_context.date(availability[1].to_date, :span_same_year_date_1) : '' } } - User.AVAILABLE_LANGUAGES.each do | l | + User.AVAILABLE_LANGUAGES.each do |l| can_speak = ((user.languages || []).include? l.to_s) data["language_#{l}".to_sym] = (can_speak ? (view_context._'articles.conference_registration.questions.bike.yes') : '') data[:raw_values]["language_#{l}".to_sym] = can_speak @@ -554,12 +573,12 @@ class ConferenceAdministrationController < ApplicationController first_day: view_context.conference_days_options_list(:before), last_day: view_context.conference_days_options_list(:after) } - User.AVAILABLE_LANGUAGES.each do | l | + User.AVAILABLE_LANGUAGES.each do |l| @column_options["language_#{l}".to_sym] = [ [(view_context._"articles.conference_registration.questions.bike.yes"), true] ] end - ConferenceRegistration.all_considerations.each do | c | + ConferenceRegistration.all_considerations.each do |c| @column_options[c.to_sym] = [ [(view_context._"articles.conference_registration.questions.bike.yes"), true] ] @@ -570,7 +589,7 @@ class ConferenceAdministrationController < ApplicationController def get_housing_data @hosts = {} @guests = {} - ConferenceRegistration.where(:conference_id => @this_conference.id).each do | registration | + ConferenceRegistration.where(:conference_id => @this_conference.id).each do |registration| if registration.can_provide_housing @hosts[registration.id] = registration elsif registration.housing.present? && registration.housing != 'none' @@ -584,11 +603,11 @@ class ConferenceAdministrationController < ApplicationController @housing_data = {} @hosts_affected_by_guests = {} - @hosts.each do | id, host | + @hosts.each do |id, host| @hosts[id].housing_data ||= {} @housing_data[id] = { guests: {}, space: {} } @hosts[id].housing_data['space'] ||= {} - @hosts[id].housing_data['space'].each do | s, size | + @hosts[id].housing_data['space'].each do |s, size| size = (size || 0).to_i @housing_data[id][:guests][s.to_sym] = {} @housing_data[id][:space][s.to_sym] = size @@ -597,7 +616,7 @@ class ConferenceAdministrationController < ApplicationController @guests_housed = 0 - @guests.each do | guest_id, guest | + @guests.each do |guest_id, guest| data = guest.housing_data || {} @hosts_affected_by_guests[guest_id] ||= [] @@ -632,7 +651,7 @@ class ConferenceAdministrationController < ApplicationController end companions = data['companions'] || [] - companions.each do | companion | + companions.each do |companion| user = User.find_user(companion) if user.present? reg = ConferenceRegistration.find_by( @@ -659,10 +678,10 @@ class ConferenceAdministrationController < ApplicationController end end - @hosts.each do | id, host | + @hosts.each do |id, host| host_data = host.housing_data - @hosts[id].housing_data['space'].each do | space, size | + @hosts[id].housing_data['space'].each do |space, size| # make sure the host isn't overbooked space = space.to_sym space_available = (size || 0).to_i @@ -771,7 +790,7 @@ class ConferenceAdministrationController < ApplicationController end def admin_update_description - params[:info].each do | locale, value | + params[:info].each do |locale, value| @this_conference.set_column_for_locale(:info, locale, html_value(value)) end @this_conference.save @@ -792,7 +811,7 @@ class ConferenceAdministrationController < ApplicationController def admin_update_payment_message begin - params[:payment_message].each do | locale, value | + params[:payment_message].each do |locale, value| @this_conference.set_column_for_locale(:payment_message, locale, html_value(value)) end @this_conference.save @@ -864,7 +883,7 @@ class ConferenceAdministrationController < ApplicationController end user_changed = false - params.each do | key, value | + params.each do |key, value| case key.to_sym when :city if value.present? @@ -995,7 +1014,7 @@ class ConferenceAdministrationController < ApplicationController @send_to = params[:send_to] @register_template = :administration if params[:button] == 'send' - view_context.broadcast_to(@send_to).each do | user | + view_context.broadcast_to(@send_to).each do |user| UserMailer.send_mail :broadcast do [ "#{request.protocol}#{request.host_with_port}", @@ -1108,11 +1127,11 @@ class ConferenceAdministrationController < ApplicationController event.end_time = event.start_time + params[:time_span].to_f.hours # save translations - (params[:info] || {}).each do | locale, value | + (params[:info] || {}).each do |locale, value| event.set_column_for_locale(:title, locale, html_value(value), current_user.id) unless value = event._title(locale) end - (params[:title] || {}).each do | locale, value | + (params[:title] || {}).each do |locale, value| event.set_column_for_locale(:info, locale, value, current_user.id) unless value = event._info(locale) end @@ -1126,22 +1145,47 @@ class ConferenceAdministrationController < ApplicationController do_404 return true end - + def admin_update_workshop_times case params[:button] when 'save_block' + empty_param = empty_params(:time, :time_span, :days) + if empty_param.present? + set_error_message "save_block_#{empty_param}_required".to_sym + else + @this_conference.workshop_blocks ||= [] + @this_conference.workshop_blocks[params[:workshop_block].to_i] = { + 'time' => params[:time], + 'length' => params[:time_span], + 'days' => params[:days].keys + } + @this_conference.save + set_success_message :block_saved + end + return false + when 'delete_block' @this_conference.workshop_blocks ||= [] - @this_conference.workshop_blocks[params[:workshop_block].to_i] = { - 'time' => params[:time], - 'length' => params[:time_span], - 'days' => params[:days].keys - } + @this_conference.workshop_blocks.delete_at(params[:workshop_block].to_i) @this_conference.save + set_success_message :block_deleted return false end do_404 - return true + return nil + end + + def admin_update_publish_schedule + case params[:button] + when 'publish' + @this_conference.workshop_schedule_published = !@this_conference.workshop_schedule_published + @this_conference.save + set_success_message "schedule_#{@this_conference.workshop_schedule_published ? '' : 'un'}published".to_sym + return false + end + + do_404 + return false end def admin_update_schedule @@ -1155,7 +1199,6 @@ class ConferenceAdministrationController < ApplicationController @entire_page = false get_scheule_data render partial: 'schedule' - return true when 'get-workshop-list' get_scheule_data(true) @@ -1166,12 +1209,11 @@ class ConferenceAdministrationController < ApplicationController @location = params[:location] @event_location = @location.present? && @location.to_i > 0 ? EventLocation.find(@location.to_i) : nil - @workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }.each do | workshop | + @workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }.each do |workshop| @ordered_workshops[workshop.id] = workshop end render partial: 'select_workshop_table' - return true when 'set-workshop' workshop = Workshop.find_by!(conference_id: @this_conference.id, id: params[:workshop].to_i) workshop.event_location_id = params[:location] @@ -1183,23 +1225,11 @@ class ConferenceAdministrationController < ApplicationController get_scheule_data render partial: 'schedule' - return true - end - - do_404 - return true - end - - def admin_update_schedule - case params[:button] - when 'publish' - @this_conference.workshop_schedule_published = !@this_conference.workshop_schedule_published - @this_conference.save - return false + else + do_404 end - do_404 - return false + return nil end def admin_update_providers @@ -1221,9 +1251,13 @@ class ConferenceAdministrationController < ApplicationController def get_empty(hash, keys) keys = [keys] unless keys.is_a?(Array) - keys.each do | key | + keys.each do |key| return key unless hash[key].present? end return nil end + + def empty_params(*args) + get_empty(params, args) + end end diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 803c77e..1a23fe1 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -2,6 +2,7 @@ require 'geocoder/calculations' require 'rest_client' class ConferencesController < ApplicationController + def list @page_title = 'articles.conferences.headings.Conference_List' @conference_list = { future: [], passed: [] } @@ -17,7 +18,7 @@ class ConferencesController < ApplicationController set_conference do_403 unless @this_conference.is_public || @this_conference.host?(current_user) - @workshops = Workshop.where(:conference_id => @conference.id) + @workshops = Workshop.where(:conference_id => @this_conference.id) if @this_conference.workshop_schedule_published @event_dlg = true @@ -59,7 +60,7 @@ class ConferencesController < ApplicationController steps = nil return do_404 unless registration_steps.present? - @register_template = :administration if params[:admin_step].present? + # @register_template = :administration if params[:admin_step].present? @errors = {} @warnings = [] @@ -72,8 +73,19 @@ class ConferencesController < ApplicationController @register_template = steps[steps.find_index($1.to_sym) - 1] elsif form_step == :paypal_confirm if @registration.present? && @registration.payment_confirmation_token == params[:confirmation_token] - @amount = PayPal!.details(params[:token]).amount.total - @registration.payment_info = {:payer_id => params[:PayerID], :token => params[:token], :amount => @amount}.to_yaml + if Rails.env.test? + @amount = params[:amount].to_f + info = YAML.load(@registration.payment_info) + info[:amount] = @amount + @registration.payment_info = info.to_yaml + else + @amount = PayPal!.details(params[:token]).amount.total + @registration.payment_info = { + payer_id: params[:PayerID], + token: params[:token], + amount: @amount + }.to_yaml + end @amount = (@amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2') @@ -86,7 +98,7 @@ class ConferencesController < ApplicationController info = YAML.load(@registration.payment_info) @amount = nil status = nil - if ENV['RAILS_ENV'] == 'test' + if Rails.env.test? status = info[:status] @amount = info[:amount] else @@ -114,7 +126,7 @@ class ConferencesController < ApplicationController case form_step when :confirm_email - return do_confirm + return confirm_email(params[:email], params[:token], register_path(@this_conference.slug)) when :contact_info if params[:name].present? && params[:name].gsub(/[\s\W]/, '').present? current_user.firstname = params[:name].squish @@ -188,18 +200,23 @@ class ConferencesController < ApplicationController amount = params[:amount].to_f if amount > 0 - @registration.payment_confirmation_token = ENV['RAILS_ENV'] == 'test' ? 'token' : Digest::SHA256.hexdigest(rand(Time.now.to_f * 1000000).to_i.to_s) - @registration.save - - host = "#{request.protocol}#{request.host_with_port}" - response = PayPal!.setup( - PayPalRequest(amount), - register_paypal_confirm_url(@this_conference.slug, :paypal_confirm, @registration.payment_confirmation_token), - register_paypal_confirm_url(@this_conference.slug, :paypal_cancel, @registration.payment_confirmation_token), - noshipping: true, - version: 204 - ) - if ENV['RAILS_ENV'] != 'test' + # we can't really test paypal integration in our tests, so we'll fake it instead + if Rails.env.test? + @registration.payment_confirmation_token = 'token' + @registration.payment_info = {amount: amount}.to_yaml + @registration.save! + redirect_to 'https://www.paypal.com' + else + @registration.payment_confirmation_token = Digest::SHA256.hexdigest(rand(Time.now.to_f * 1000000).to_i.to_s) + @registration.save! + pp = PayPal! + response = pp.setup( + PayPalRequest(amount), + register_paypal_confirm_url(@this_conference.slug, :paypal_confirm, @registration.payment_confirmation_token), + register_paypal_confirm_url(@this_conference.slug, :paypal_cancel, @registration.payment_confirmation_token), + noshipping: true, + version: 204 + ) redirect_to response.redirect_uri end return @@ -219,7 +236,7 @@ class ConferencesController < ApplicationController # this step is only completed if a payment has been made if form_step != :payment || (@registration.registration_fees_paid || 0) > 0 @registration.steps_completed ||= [] - @registration.steps_completed << form_step + @registration.steps_completed << form_step.to_s @registration.steps_completed.uniq! end end @@ -334,9 +351,7 @@ class ConferencesController < ApplicationController steps -= [:questions] # if this is a housing provider that is not attending the conference, remove these steps - if @registration.is_attending == 'n' - steps -= [:payment, :workshops] - end + steps -= [:payment, :workshops] if @registration.is_attending == 'n' else steps -= [:hosting] end @@ -344,8 +359,6 @@ class ConferencesController < ApplicationController steps -= [:hosting, :questions] end - steps += [:administration] if conference.host?(current_user) - return steps end @@ -394,11 +407,11 @@ class ConferencesController < ApplicationController def PayPalRequest(amount) Paypal::Payment::Request.new( - :currency_code => 'USD', # if nil, PayPal use USD as default - :description => 'Conference Registration', # item description - :quantity => 1, # item quantity - :amount => amount.to_f, # item value - :custom_fields => { + currency_code: 'USD', # if nil, PayPal use USD as default + description: 'Conference Registration', # item description + quantity: 1, # item quantity + amount: amount.to_f, # item value + custom_fields: { CARTBORDERCOLOR: "00ADEF", LOGOIMG: "https://en.bikebike.org/assets/bblogo-paypal.png" } diff --git a/app/controllers/oauths_controller.rb b/app/controllers/oauths_controller.rb deleted file mode 100644 index decccbb..0000000 --- a/app/controllers/oauths_controller.rb +++ /dev/null @@ -1,101 +0,0 @@ -class OauthsController < ApplicationController - skip_before_filter :require_login - - # sends the user on a trip to the provider, - # and after authorizing there back to the callback url. - def oauth - set_callback - session[:oauth_last_url] = params[:dest] || request.referer - login_at(auth_params[:provider]) - end - - def callback - set_callback - - user_info = (sorcery_fetch_user_hash auth_params[:provider] || {})[:user_info] - - email = user_info['email'] - fb_id = user_info['id'] - - # try to find the user by facebook id - user = User.find_by_fb_id(fb_id) - - # otherwise find the user by email - unless user.present? - # only look if the email address is present - user = User.find_user(email) if email.present? - end - - # create the user if the email is not recognized - if user.nil? - if email.present? - user = User.create(email: email, firstname: user_info['name'], fb_id: fb_id, locale: I18n.locale) - else - session[:oauth_update_user_info] = user_info - return redirect_to oauth_update_path - end - elsif user.fb_id.blank? || user.email.blank? - user.email = email - user.fb_id = fb_id - user.save! - end - - if user.present? && user.email.present? - # log in the user - auto_login(user) - end - - oauth_last_url = (session[:oauth_last_url] || home_path) - session.delete(:oauth_last_url) - redirect_to oauth_last_url - end - - def update - @main_title = @page_title = 'articles.conference_registration.headings.email_confirm' - @errors = { email: flash[:error] } if flash[:error].present? - render 'application/update_user' - end - - def save - unless params[:email].present? - return redirect_to oauth_update_path - end - - user = User.find_user(params[:email]) - - if user.present? - flash[:error] = :exists - return redirect_to oauth_update_path - end - - # create the user - user = User.new(email: params[:email], firstname: session[:oauth_update_user_info]['name'], fb_id: session[:oauth_update_user_info]['id']) - user.save! - - # log in - auto_login(user) - - # clear out the session - oauth_last_url = (session[:oauth_last_url] || home_path) - session.delete(:oauth_last_url) - session.delete(:oauth_update_user_info) - - # go to our final destination - redirect_to oauth_last_url - end - - private - def auth_params - params.permit(:code, :provider) - end - - def set_callback - # force https for prod - protocol = Rails.env.preview? || Rails.env.production? ? 'https://' : request.protocol - - # build the callback url - Sorcery::Controller::Config.send(params[:provider]).callback_url = - "#{protocol}#{request.env['HTTP_HOST']}/oauth/callback?provider=facebook" - end - -end \ No newline at end of file diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 04b0409..ea7a64b 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -1,10 +1,10 @@ - class WorkshopsController < ApplicationController + def workshops set_conference set_conference_registration! - @workshops = Workshop.where(:conference_id => @this_conference.id) - @my_workshops = Workshop.joins(:workshop_facilitators).where(:workshop_facilitators => {:user_id => current_user.id}, :conference_id => @this_conference.id) + @workshops = Workshop.where(conference_id: @this_conference.id) + @my_workshops = @workshops.select { |w| w.active_facilitator?(current_user) } render 'workshops/index' end @@ -58,7 +58,7 @@ class WorkshopsController < ApplicationController @is_translating ||= false if @is_translating - return do_404 if @translation.to_s == @workshop.locale.to_s || !I18n.backend.enabled_locales.include?(@translation.to_s) + return do_404 unless @translation.to_s != @workshop.locale.to_s && LinguaFranca.locale_enabled?(@translation.to_sym) return do_403 unless @workshop.can_translate?(current_user, @translation) @title = @workshop._title(@translation) diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb new file mode 100644 index 0000000..a9b1979 --- /dev/null +++ b/app/helpers/admin_helper.rb @@ -0,0 +1,163 @@ +module AdminHelper + + def administration_steps + { + info: [:administrators, :dates, :description, :poster], + payment: [:payment_message, :suggested_amounts, :paypal], + registration: [:registration_status, :stats, :registrations, :broadcast], + housing: [:providers, :housing], + events: [:locations, :meals, :events], + schedule: [:workshop_times, :schedule, :publish_schedule] + } + end + + def administration_sub_steps + { + location_edit: :locations, + event_edit: :events + } + end + + def get_administration_group(administration_step) + admin_step = administration_step.to_sym + admin_step = administration_sub_steps[admin_step] if administration_sub_steps[admin_step].present? + administration_steps.each do | group, steps | + steps.each do | step | + return group if step == admin_step + end + end + + return nil + end + + def broadcast_to(to, conference = nil) + conference ||= @this_conference || @conference + + users = [] + + case to.to_sym + when :registered + ConferenceRegistration.where(conference_id: conference.id).each do | r | + users << r.user if ((r.steps_completed || []).include? 'questions') && r.user.present? && r.is_attending != 'n' + end + when :pre_registered + ConferenceRegistration.where(conference_id: conference.id).each do | r | + users << r.user if registration_status(r) == :preregistered && r.is_attending != 'n' + end + when :workshop_facilitators + user_hash = {} + Workshop.where(conference_id: conference.id).each do | w | + w.active_facilitators.each do | u | + user_hash[u.id] ||= u if u.present? + end + end + users = user_hash.values + when :unregistered + ConferenceRegistration.where(conference_id: conference.id).each do | r | + users << r.user if registration_status(r) == :unregistered && r.is_attending != 'n' + end + when :housing_providers + ConferenceRegistration.where(conference_id: conference.id, can_provide_housing: true).each do | r | + users << r.user if r.user.present? + end + when :guests + ConferenceRegistration.where(conference_id: conference.id, housing: 'house').each do | r | + users << r.user if r.user.present? && r.is_attending != 'n' + end + when :all + User.all.each do | u | + users << u if u.present? && (u.is_subscribed.nil? || u.is_subscribed) + end + end + + return users + end + + def get_housing_match(host, guest, space) + housing_data = guest.housing_data || [] + + if housing_data['host'].present? + if housing_data['host'] == host.id + return space == housing_data['space'] ? :selected_space : :other_space + end + + return :other_host + end + + if space_matches?(space, guest.housing) && available_dates_match?(host, guest) + return :good_match + end + + return :bad_match + end + + def get_workshop_match(workshop, day, block, location) + if workshop.event_location_id.present? && workshop.present? + if (Date.parse params[:day]).wday == workshop.block['day'] && block == workshop.block['block'].to_i + return :selected_space + end + + if location.present? && location.id == workshop.event_location_id + return :other_space + end + + return :other_host + end + + if location.present? + needs = JSON.parse(workshop.needs || '[]').map &:to_sym + amenities = JSON.parse(location.amenities || '[]').map &:to_sym + + if (needs & amenities).length < needs.length + return :bad_match + end + end + + (((((@schedule[@day] || {})[:times] || {})[@time] || {})[:item] || {})[:workshops] || {}).each do | l, w | + if w[:workshop].id != workshop.id + f_a = w[:workshop].active_facilitators.map { | f | f.id } + f_b = workshop.active_facilitators.map { | f | f.id } + if (f_a & f_b).present? + return :bad_match + end + end + end + + # housing_data = guest.housing_data || [] + + # if housing_data['host'].present? + # if housing_data['host'] == host.id + # return space == housing_data['space'] ? :selected_space : :other_space + # end + + # return :other_host + # end + + # if space_matches?(space, guest.housing) && available_dates_match?(host, guest) + # return :good_match + # end + + # return :bad_match + return :good_match + end + + def space_matches?(host_space, guest_space) + return false unless host_space.present? && guest_space.present? + + if host_space.to_s == 'bed_space' || host_space.to_s == 'floor_space' + return guest_space.to_s == 'house' + end + + return host_space.to_s == 'tent_space' && guest_space.to_s == 'tent' + end + + def available_dates_match?(host, guest) + return false unless host.housing_data['availability'].present? && host.housing_data['availability'][1].present? + if host.housing_data['availability'][0] <= guest.arrival && + host.housing_data['availability'][1] >= guest.departure + return true + end + + return false + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1fbd759..563131d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,521 +1,14 @@ -require 'redcarpet' +require 'lingua_franca/form_helper' module ApplicationHelper + include PageHelper include RegistrationHelper - - @@keyQueue = nil - @@translationsOnThisPage = nil - @@lastTranslation = nil - @@allTranslations = nil - @@no_banner = true - @@banner_attribution_details = nil - @@banner_image = nil - @@has_content = true - @@front_page = false - @@body_class = nil - @@test_location = nil - - def init_vars - @@keyQueue = nil - @@no_banner = true - @@banner_attribution_details = nil - @@banner_image = nil - @@has_content = true - @@front_page = false - @@body_class = nil - end - - def this_is_the_front_page - @@front_page = true - end - - def header_is_fixed - @fixed_header = true - end - - def is_header_fixed? - @fixed_header ||= false - end - - def is_this_the_front_page? - return @@front_page - end - - def header_classes - classes = Array.new - classes << 'fixed' if is_header_fixed? - return classes - end - - def ThereAreTranslationsOnThisPage? - @@translationsOnThisPage - end - - def get_all_translations - @@allTranslations - end - - def title(page_title) - content_for(:title) { page_title.to_s } - end - - def description(page_description) - content_for(:description) { page_description.to_s } - end - - def banner_image(banner_image, name: nil, id: nil, user_id: nil, src: nil) - @@no_banner = false - @@banner_image = banner_image - if (name || id || user_id || src) - @@banner_attribution_details = {:name => name, :id => id, :user_id => user_id, :src => src} - end - content_for(:banner_image) { banner_image.to_s } - end - - def banner_attrs(banner_image) - @@no_banner = false - if banner_image.length > 0 - @@banner_image = banner_image - return {style: 'background-image: url(' + banner_image + ');', class: 'has-image' } - end - {class: 'no-image'} - end - - def has_banner? - !@@no_banner - end - - def has_content? - @@has_content - end - - def has_no_content - @@has_content = false - end - - def banner_title(banner_title) - @@no_banner = false - content_for(:banner) { ('

' + banner_title.to_s + '

').html_safe } - end - - def add_stylesheet(sheet) - @stylesheets ||= [] - @stylesheets << sheet unless @stylesheets.include?(sheet) - end - - def stylesheets - html = '' - Rack::MiniProfiler.step('inject_css') do - html += inject_css! - end - (@stylesheets || []).each do |css| - Rack::MiniProfiler.step("inject_css #{css}") do - html += inject_css! css.to_s - end - end - html += stylesheet_link_tag 'i18n-debug' if request.params['i18nDebug'] - return html.html_safe - end - - def add_inline_script(script) - @_inline_scripts ||= [] - script = Rails.application.assets.find_asset("#{script.to_s}.js").to_s - @_inline_scripts << script unless @_inline_scripts.include?(script) - end - - def inline_scripts - return '' unless @_inline_scripts.present? - "".html_safe - end - - def banner_attribution - if @@banner_image && @@banner_attribution_details - src = @@banner_attribution_details[:src] - attribution = '
' - if src == 'panoramio' - attribution += '© ' + - _('Banner_image_provided_by_panoramio_user') + - ' ' + @@banner_attribution_details[:name] + '' + - '' + _('Photos_provided_by_Panoramio_are_under_the_copyright_of_their_owners') + '' - end - attribution += '
' - attribution.html_safe - end - end - - def dom_ready(&block) - content_for(:dom_ready, &block) - end - - def body_class(c) - @@body_class ||= Array.new - @@body_class << (c.is_a?(Array) ? c.join(' ') : c) - end - - def page_style - classes = Array.new - - classes << 'has-translations' if ThereAreTranslationsOnThisPage? - classes << 'no-content' unless @@has_content - classes << 'has-banner-image' if @@banner_image - classes << @@body_class.join(' ') if @@body_class - classes << 'fixed-banner' if is_header_fixed? - - if params[:controller] - classes << params[:action] - unless params[:controller] == 'application' - classes << params[:controller] - - if params[:action] - classes << "#{params[:controller]}-#{params[:action]}" - end - end - end - return classes - end - - def yield_or_default(section, default = '') - content_for?(section) ? content_for(section) : default - end - - def _translate_me(translation) - @@translationsOnThisPage = true - datakeys = '' - translation['vars'].each { |key, value| datakeys += ' data-var-' + key.to_s + '="' + value.to_s.gsub('"', '"') + '"' } - ('' + (translation['html'] || translation['untranslated']) + '').to_s.html_safe - end - - def _do_translate(key, vars, behavior, behavior_size, locale) - translation = {'key' => key, 'lang' => '0', 'vars' => vars} - v = vars.dup - begin - v[:raise] = true - options = {:raise => true} - if locale - options[:locale] = locale.to_sym - end - translation['untranslated'] = I18n.translate(key, v, options) - translation['lang'] = locale.to_s - translation['is_translated'] = true - - hash = Hash.new - translations = Translation.where(["locale = ? AND key LIKE ?", locale.to_s, key + '%']).take(6).each { |o| hash[o.key] = o.value } - translation['translated'] = hash.to_json.gsub('"', '"') - rescue I18n::MissingTranslationData - default_translation = I18n::MissingTranslationExceptionHandler.note(key, behavior, behavior_size) - translation['untranslated'] = default_translation - end - return translation - end - - def _can_translate?() - false - end - - def off_screen(text, id = nil) - content_tag(:span, text.html_safe, id: id, class: 'screen-reader-text') - end - - def url_for_locale(locale, url = nil) - return url unless locale.present? - - unless url.present? - new_params = params.merge({action: (params[:_original_action] || params[:action])}) - new_params.delete(:_original_action) - - if Rails.env.development? || Rails.env.test? - return url_for(new_params.merge({lang: locale.to_s})) - end - - subdomain = Rails.env.preview? ? "preview-#{locale.to_s}" : locale.to_s - return url_for(new_params.merge(host: "#{subdomain}.bikebike.org")) - end - - return url if Rails.env.development? || Rails.env.test? - return "https://preview-#{locale.to_s}.bikebike.org#{url}" if Rails.env.preview? - "https://#{locale.to_s}.bikebike.org#{url}" - end - - def registration_steps(conference = @conference) - { - pre: [:policy, :contact_info, :workshops], - open: [:policy, :contact_info, :questions, :hosting, :payment, :workshops] - }[@this_conference.registration_status] - end - - def registration_status(registration) - return :unregistered if registration.nil? - return registration.status - end - - def sortable(objects, id = 'id', url: nil, &block) - result = '
    ' - objects.each_index do |i| - @this = objects[i] - result += '
  • ' - result += hidden_field_tag (id + "[#{i}]"), objects[i][id] - result += hidden_field_tag ('position' + "[#{i}]"), i, :class => 'sortable-position' - if block_given? - result += capture(objects[i], &block) - end - result += '
  • ' - end - result += '' - result.html_safe - end - - def tabs object, tabs - type = object.class.name.downcase - tab_list = '' - - tabs.each do |tab| - link = nil - if self.respond_to?(type + '_' + tab.to_s + '_path') - link = self.send(type + '_' + tab.to_s + '_path', object) - elsif self.respond_to?(tab.to_s + '_' + type + '_path') - link = self.send(tab.to_s + '_' + type + '_path', object) - end - - c = ['tab', 'tab-' + (link ? tab.to_s : 'show')] - if params[:action] == tab.to_s - c << 'current' - end - link_html = '' - if tab.is_a?(Hash) - func = tab.keys[0] - val = tab[func] - args = val ? (val.is_a?(Array) ? (val.collect { |v| object[v] } ) : [object[val]] ) : nil - - link_html = link_to func.to_s.gsub(/_path$/, ''), args ? self.send(func, args) : self.send(func), :class => c - else - #x - #link_html = link_to tab, link || object, :class => c - end - tab_list += link_html - end - ('').html_safe - end - - def tabs! - object = nil - tabs = nil - case params[:controller] - when 'organizations' - object = @organization - tabs = OrganizationsHelper::TABS - when 'conferences' - object = @conference - tabs = ConferencesHelper::TABS - when 'workshops' - object = [@conference, @workshop] - tabs = WorkshopsHelper::TABS - end - - if object && tabs - return tabs object, tabs - end - end - - def sub_tabs object, tabs - type = object.class.name.downcase - tab_list = '' - - tabs.each do |tab| - link = nil - if self.respond_to?(type + '_' + tab.to_s + '_path') - link = self.send(type + '_' + tab.to_s + '_path', object) - elsif self.respond_to?(tab.to_s + '_' + type + '_path') - link = self.send(tab.to_s + '_' + type + '_path', object) - end - - c = ['sub-tab', 'sub-tab-' + (link ? tab.to_s : 'show')] - if current_page?(link) - c << 'current' - end - tab_list += link_to tab, link || object, :class => c - end - ('').html_safe - end - - def sub_tabs! - object = nil - tabs = nil - case params[:controller] - when 'organizations' - object = @organization - tabs = OrganizationsHelper::SUB_TABS - when 'conferences' - object = @conference - tabs = ConferencesHelper::SUB_TABS - end - - if object && tabs - return sub_tabs object, tabs - end - end - - def m(*args) - _(*args) { |t| - markdown(t) - } - end - - def markdown(object, attribute = nil) - return '' unless object - content = attribute ? object.send(attribute.to_s) : object - @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML.new({ - filter_html: true, - hard_wrap: true, - space_after_headers: true, - fenced_code_blocks: true, - link_attributes: { target: "_blank" } - }), { - autolink: true, - disable_indented_code_blocks: true, - superscript: true - }) - @markdown.render(content).html_safe - end - - def paragraph(object, attribute = nil) - return '' unless object - content = attribute ? object.send(attribute.to_s) : object - result = '' - if content =~ /<(p|span|h\d|div)[^>]*>/ - result = content.gsub(/\s*(style|class|id|width|height|font)=\".*?\"/, '') - .gsub(/ /, ' ') - .gsub(/<(\/)?\s*h\d\s*>/, '<\1h3>') - .gsub(/

    (.*?)\s*()+/, '

    \1

    ') - .gsub(/]*>\s*(.*?)\s*<\/span>/, '\1') - .gsub(/

    \s*<\/p>/, '') - .gsub(/<(\/)?div>/, '<\1p>') - if !(result =~ /]*>/) - result = '

    ' + result + '

    ' - end - else - result = markdown(object, attribute) - end - result.html_safe - end - - def form_field(f, response = nil) - id = 'field_' + f.id.to_s - html = p(f, 'title') - - options = JSON.parse(f.options) - if f.field_type == 'multiple' - if f.help - html += ('
    ' + p(f, 'help') + '
    ').html_safe - end - - opts = Hash.new - options['options'].split(/\s*\n\s*/).each do |value| - kv = value.split(/\s*\|\s*/, 2) - opts[kv[0]] = kv[1] - end - - val = response ? ActiveSupport::JSON.decode(response.data) : Hash.new - - if f.repeats? - is_array = f.is_array? - opts.each do |key, value| - n = (id + (is_array ? ('_' + key) : '')).to_sym - v = is_array ? (val ? val[key] : nil) : key - o = {:label => value} - if f.required - options[:required] = true - end - html += _form_field(options['selection_type'], n, v, o) - end - else - html += _form_field(options['selection_type'], id.to_sym, options_for_select(opts.invert, val), {}) - end - else - #html += field(id.to_sym, options['input_type'] + '_tag', label: false, placeholder: f.help, value: response ? ActiveSupport::JSON.decode(response.data) : nil, required: f.required) - opts = {label: false, placeholder: f.help && f.help.length > 0 ? f.help : false} - if f.required - opts[:required] = true - end - html += _form_field(options['input_type'], id.to_sym, response ? ActiveSupport::JSON.decode(response.data) : nil, opts) - end - - html.html_safe - end - - def t(*a) - _(*a) - end - - def lookup_ip - if request.remote_ip == '127.0.0.1' || request.remote_ip == '::1' - session['remote_ip'] || (session['remote_ip'] = open("http://checkip.dyndns.org").first.gsub(/^.*\s([\d\.]+).*$/s, '\1').gsub(/[^\.\d]/, '')) - else - request.remote_ip - end - end - - def get_remote_location - Geocoder.search(session['remote_ip'] || (session['remote_ip'] = open("http://checkip.dyndns.org").first.gsub(/^.*\s([\d\.]+).*$/s, '\1').gsub(/[^\.\d]/, '')), language: 'en').first - end - - def lookup_ip_location - begin - if is_test? && ApplicationController::get_location.present? - Geocoder.search(ApplicationController::get_location, language: 'en').first - elsif request.remote_ip == '127.0.0.1' || request.remote_ip == '::1' - get_remote_location - else - request.location || get_remote_location - end - rescue - nil - end - end - - def hash_to_html_attributes(hash, prefix = '') - attributes = '' - if hash - hash.each { |k,v| - k = k.to_s - if v - if v.is_a?(Hash) - attributes += hash_to_html_attributes(v, 'data-') - else - attributes += " #{k}=\"" + (v.is_a?(Array) ? v.join(' ') : v) + '"' - end - end - } - end - attributes - end - - def icon(id, attributes = nil) - ('').html_safe - end - - def static_map(location, zoom, width, height) - require 'fileutils' - local_file_name = "#{location}-#{width}x#{height}z#{zoom}.png" - file = File.join("public", "maps/#{local_file_name}") - FileUtils.mkdir_p("public/maps") unless File.directory?("public/maps") - if !File.exist?(file) - url = "https://maps.googleapis.com/maps/api/staticmap?center=#{location}&zoom=#{zoom}&size=#{width}x#{height}&maptype=roadmap&markers=size:small%7C#{location}&key=AIzaSyAH7U8xUUb8IwDPy1wWuYGprzxf4E1Jj4o" - require 'open-uri' - open(file, 'wb') do |f| - f << open(url).read - end - end - - cdn("/maps/#{local_file_name}") - end - - def cdn(file) - (Rails.application.config.action_controller.asset_host || '') + file - end + include FormHelper + include I18nHelper + include WidgetsHelper + include GeocoderHelper + include TableHelper + include AdminHelper def is_production? Rails.env == 'production' || Rails.env == 'preview' @@ -525,1703 +18,7 @@ module ApplicationHelper Rails.env == 'test' end - def subdomain - request.env['SERVER_NAME'].gsub(/^(\w+)\..*$/, '\1') - end - - def is_test_server? - subdomain == 'test' - end - - def rand_hash(length = 16, model = nil, field = nil) - if field - hash = rand_hash(length) - while !model.to_s.to_s.singularize.classify.constantize.find_by(field => hash).nil? - hash = rand_hash(length) - end - end - rand(36**length).to_s(36) - end - - def get_panoramio_image(location) - if is_test? - params[:image] = 'panoramio.jpg' - params[:attribution_id] = 1234 - params[:attribution_user_id] = 5678 - params[:attribution_name] = 'Some Guy' - params[:attribution_src] = 'panoramio' - return params - end - - location = location.city + ', ' + (location.territory ? location.territory + ' ' : '') + location.country - $panoramios ||= Hash.new - $panoramios[location] ||= 0 - $panoramios[location] += 1 - result = Geocoder.search(location, language: 'en').first - if result - points = Geocoder::Calculations.bounding_box([result.latitude, result.longitude], 5, { :unit => :km }) - options = {:set => :public, :size => :original, :from => 0, :to => 20, :mapfilter => false, :miny => points[0], :minx => points[1], :maxy => points[2], :maxx => points[3]} - url = 'http://www.panoramio.com/map/get_panoramas.php?' + options.to_query - response = JSON.parse(open(url).read) - response['photos'].each { |img| - if img['width'].to_i > 980 - if Organization.find_by(:cover_attribution_id => img['photo_id'], :cover_attribution_src => 'panoramio').nil? && Conference.find_by(:cover_attribution_id => img['photo_id'], :cover_attribution_src => 'panoramio').nil? - params[:image] = img['photo_file_url'] - params[:attribution_id] = img['photo_id'] - params[:attribution_user_id] = img['owner_id'] - params[:attribution_name] = img['owner_name'] - params[:attribution_src] = 'panoramio' - return params - end - end - } - end - return nil - end - - def get_secure_info(name) - YAML.load(File.read(Rails.root.join("config/#{name.to_s}.yml")))[Rails.env].symbolize_keys - end - - def location(location, locale = I18n.locale) - return nil if location.blank? - - city = nil - region = nil - country = nil - if location.is_a?(Location) || location.is_a?(City) - country = location.country - region = location.territory - city = location.city - elsif location.data.present? && location.data['address_components'].present? - component_map = { - 'locality' => :city, - 'administrative_area_level_1' => :region, - 'country' => :country - } - location.data['address_components'].each do | component | - types = component['types'] - country = component['short_name'] if types.include? 'country' - region = component['short_name'] if types.include? 'administrative_area_level_1' - city = component['long_name'] if types.include? 'locality' - end - else - country = location.data['country_code'] - region = location.data['region_code'] - city = location.data['city'] - end - - # we need cities for our logic, don't let this continue if we don't have one - return nil unless city.present? - - hash = Hash.new - region_translation = region.present? && country.present? ? _("geography.subregions.#{country}.#{region}", locale: locale) : '' - country_translation = country.present? ? _("geography.countries.#{country}", locale: locale) : '' - hash[:city] = _!(city) if city.present? - hash[:region] = region_translation if region_translation.present? - hash[:country] = country_translation if country_translation.present? - - # return the formatted location or the first value if we only have one value - return hash.length > 1 ? _("geography.formats.#{hash.keys.join('_')}", locale: locale, vars: hash) : hash.values.first - end - - def location_link(location) - return '' unless location.present? && location.address.present? - content_tag(:a, (_!location.address), href: "http://www.google.com/maps/place/#{location.latitude},#{location.longitude}") - end - - def same_city?(location1, location2) - return false unless location1.present? && location2.present? - - location1 = location(location1) unless location1.is_a?(String) - location2 = location(location2) unless location2.is_a?(String) - - location1.eql? location2 - end - - def show_errors(field, value = nil) - return '' unless @errors && @errors[field].present? - - error_txt = _"errors.messages.fields.#{field.to_s}.#{@errors[field]}", :s, vars: { value: value } - - "
    #{error_txt}
    ".html_safe - end - - def nav_link(link, title = nil, class_name = nil) - if title.nil? && link.is_a?(Symbol) - title = link - link = send("#{link.to_s}_path") - end - if class_name.nil? && title.is_a?(Symbol) - class_name = title - end - title = _"page_titles.#{title.to_s.titlecase.gsub(/\s/, '_')}" - classes = [] - classes << class_name if class_name.present? - classes << "strlen-#{strip_tags(title).length}" - classes << 'current' if request.fullpath.start_with?(link.gsub(/^(.*?)\/$/, '\1')) - link_to "#{title}".html_safe, link, :class => classes - end - - def date(date, format = :long) - I18n.l(date.is_a?(String) ? Date.parse(date) : date, :format => format) - end - - def time(time, format = :short) - if time.is_a?(String) - time = Date.parse(time) - elsif time.is_a?(Float) || time.is_a?(Integer) - time = DateTime.now.midnight + time.hours - end - - I18n.l(time, :format => format) - end - - def date_span(date1, date2) - key = 'same_month' - if date1.year != date2.year - key = 'different_year' - elsif date1.month != date2.month - key = 'same_year' - end - d1 = I18n.l(date1.to_date, format: "span_#{key}_date_1".to_sym) - d2 = I18n.l(date2.to_date, format: "span_#{key}_date_2".to_sym) - _('date.date_span', vars: {:date_1 => d1, :date_2 => d2}) - end - - def time_length(length) - hours = length.to_i - minutes = ((length - hours) * 60).to_i - hours = hours > 0 ? (I18n.t 'datetime.distance_in_words.x_hours', count: hours) : nil - minutes = minutes > 0 ? (I18n.t 'datetime.distance_in_words.x_minutes', count: minutes) : nil - return hours.present? ? (minutes.present? ? (I18n.t 'datetime.distance_in_words.x_and_y', x: hours, y: minutes) : hours) : minutes - end - - def hour_span(time1, time2) - (time2 - time1) / 3600 - end - - def hours(time1, time2) - time_length hour_span(time1, time2) - end - def generate_confirmation(user, url, expiry = nil) ApplicationController::generate_confirmation(user, url, expiry) end - - def money(amount) - return _!('$0.00') if amount == 0 - _!((amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '$\1.\2')) - end - - def percent(p) - return _!('0.00%') if p == 0 - _!((p * 10000).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2%')) - end - - def data_set(header_type, header_key, attributes = {}, &block) - raw_data_set(header_type, _(header_key), attributes, &block) - end - - def raw_data_set(header_type, header, attributes = {}, &block) - attributes[:class] = attributes[:class].split(' ') if attributes[:class].is_a?(String) - attributes[:class] = [attributes[:class].to_s] if attributes[:class].is_a?(Symbol) - attributes[:class] ||= [] - attributes[:class] << 'data-set' - content_tag(:div, attributes) do - content_tag(header_type, header, class: 'data-set-key') + - content_tag(:div, class: 'data-set-value', &block) - end - end - - def conference_days_options(conference = nil) - conference ||= @this_conference || @conference - return [] unless conference - - dates = [] - day = conference.start_date - 7.days - last_day = conference.end_date + 7.days - - while day <= last_day - dates << day - day += 1.day - end - - return dates - end - - def conference_days_options_list(period, conference = nil, format = nil) - conference ||= @this_conference || @conference - return [] unless conference - - days = [] - - conference_days_options(conference).each do |day| - belongs_to_periods = [] - belongs_to_periods << :before if day <= conference.start_date - belongs_to_periods << :after if day >= conference.end_date - belongs_to_periods << :before_plus_one if day <= (conference.start_date + 1.day) - belongs_to_periods << :after_minus_one if day >= (conference.end_date - 1.day) - belongs_to_periods << :during if day >= conference.start_date && day <= conference.end_date - days << [date(day.to_date, format || :span_same_year_date_1), day.to_date] if belongs_to_periods.include?(period) - end - return days - end - - def registration_status_options_list(conference = nil) - conference ||= @this_conference || @conference - return [] unless conference - - options = Array.new - [:closed, :pre, :open].each do | opt | - options << [(_"forms.labels.generic.registration_statuses.#{opt}"), opt] - end - - return options - end - - def month_select(value = nil, args = {}) - options = (1..12).to_a.map { |month| [ (I18n.t "date.#{args[:format] || 'month_names'}")[month], month ] } - selectfield args[:name] || :month, value, options, args - end - - def month_day_select(value = nil, args = {}) - options = (1..31).to_a.map { |day| [ day, day ] } - selectfield args[:name] || :month_day, value, options, args - end - - def day_select(value = nil, args = {}) - selectfield :day, value, conference_days_options_list(:during, nil, args[:format]), args - end - - def hour_select(value = nil, args = {}, start_time = 8, end_time = 23.5, step = 0.5) - time = start_time - times = [] - while time <= end_time - times << [time(DateTime.now.midnight + time.hours), time] - time += step - end - selectfield :time, value, times, args - end - - def length_select(value = nil, args = {}, min_length = 0.5, max_length = 6, step = 0.5) - length = min_length - lengths = [] - while length <= max_length - lengths << [time_length(length), length] - length += step - end - selectfield :time_span, value, lengths, args - end - - def contact_reason_select - reasons = [] - [:website, :conference].each do | reason | - reasons << [ _("forms.labels.generic.reasons.#{reason.to_s}"), reason ] - end - [['Something about the website', :website]] - selectfield :reason, nil, reasons, required: true, heading: 'articles.contact.headings.reason', label: false, full: true - end - - def block_select(value = nil, args = {}) - blocks = {} - @workshop_blocks.each_with_index do | info, block | - info['days'].each do | day | - blocks[(day.to_i * 10) + block] = [ "#{(I18n.t 'date.day_names')[day.to_i]} Block #{block + 1}", "#{day}:#{block}" ] - end - end - selectfield :workshop_block, value, blocks.sort.to_h.values, args - end - - def location_select(value = nil, args = {}) - locations = [] - if @this_conference.event_locations.present? - @this_conference.event_locations.each do | location | - locations << [ location.title, location.id ] unless ((args[:invalid_locations] || []).include? location.id) - end - end - selectfield :event_location, value, locations, args - end - - def location_name(id) - begin - location = EventLocation.find(id) - rescue - return '' - end - return '' unless location.present? - return location.title - end - - def host_options_list(hosts) - options = [[nil, nil]] - hosts.each do | id, registration | - options << [registration.user.name, id] - end - return options - end - - def registration_step_menu - steps = current_registration_steps(@registration) - return '' unless steps.present? && steps.length > 1 - - pre_registration_steps = '' - post_registration_steps = '' - post_registration = false - - steps.each do | step | - text = _"articles.conference_registration.headings.#{step[:name].to_s}" - - if step[:name] == :workshops - post_registration = true - end - - h = content_tag :li, class: [step[:enabled] ? :enabled : nil, @register_template == step[:name] ? :current : nil, post_registration ? :post : :pre].compact do - if step[:enabled] - content_tag :div, (link_to text, register_step_path(@this_conference.slug, step[:name])).html_safe, class: :step - else - content_tag :div, text, class: :step - end - end - - if post_registration - post_registration_steps += h.html_safe - else - pre_registration_steps += h.html_safe - end - end - - html = ( - row class: 'flow-steps' do - columns do - (content_tag :ul, id: 'registration-steps' do - pre_registration_steps.html_safe + - post_registration_steps.html_safe - end).html_safe - end - end - ) - - return html.html_safe - end - - def broadcast_to(to, conference = nil) - conference ||= @this_conference || @conference - - users = [] - - case to.to_sym - when :registered - ConferenceRegistration.where(conference_id: conference.id).each do | r | - users << r.user if ((r.steps_completed || []).include? 'questions') && r.user.present? && r.is_attending != 'n' - end - when :pre_registered - ConferenceRegistration.where(conference_id: conference.id).each do | r | - users << r.user if registration_status(r) == :preregistered && r.is_attending != 'n' - end - when :workshop_facilitators - user_hash = {} - Workshop.where(conference_id: conference.id).each do | w | - w.active_facilitators.each do | u | - user_hash[u.id] ||= u if u.present? - end - end - users = user_hash.values - when :unregistered - ConferenceRegistration.where(conference_id: conference.id).each do | r | - users << r.user if registration_status(r) == :unregistered && r.is_attending != 'n' - end - when :housing_providers - ConferenceRegistration.where(conference_id: conference.id, can_provide_housing: true).each do | r | - users << r.user if r.user.present? - end - when :guests - ConferenceRegistration.where(conference_id: conference.id, housing: 'house').each do | r | - users << r.user if r.user.present? && r.is_attending != 'n' - end - when :all - User.all.each do | u | - users << u if u.present? && (u.is_subscribed.nil? || u.is_subscribed) - end - end - - puts "Users:" - users.each do | u | - puts "\t#{u.id}\t#{u.email}" - end - return users - end - - def broadcast_options(conference = nil) - conference ||= @this_conference || @conference - - options = [ - :registered, - :pre_registered, - :workshop_facilitators, - :unregistered, - :housing_providers, - :guests, - :all - ] - - if conference.registration_status != :open - options -= [:registered, :guests] - options -= [:pre_registered] unless conference.registration_status != :pre - end - - return options - end - - def admin_steps # deprecated - [:stats, :edit, :payment, :broadcast, :housing, :locations, :meals, :events, :workshop_times, :schedule] - end - - def administration_steps - { - info: [:administrators, :dates, :description, :poster], - payment: [:payment_message, :suggested_amounts, :paypal], - registration: [:registration_status, :stats, :registrations, :broadcast], - housing: [:providers, :housing], - events: [:locations, :meals, :events], - schedule: [:workshop_times, :schedule, :publish_schedule] - } - end - - def administration_sub_steps - { - location_edit: :locations, - event_edit: :events - } - end - - def get_administration_group(administration_step) - admin_step = administration_step.to_sym - admin_step = administration_sub_steps[admin_step] if administration_sub_steps[admin_step].present? - administration_steps.each do | group, steps | - steps.each do | step | - return group if step == admin_step - end - end - - return nil - end - - def valid_admin_steps # deprecated - admin_steps + [:broadcast_sent, :organizations] - end - - def admin_menu - steps = '' - first = true - admin_steps.each do | step | - steps += content_tag(:li, class: (step.to_s == @admin_step ? :current : nil)) do - link_to _("menu.submenu.admin.#{step.to_s.titlecase.gsub(/\s/, '_')}"), first ? - register_step_path(@this_conference.slug, :administration) : - administration_step_path(@this_conference.slug, step.to_s) - end - first = false - end - content_tag :ul, steps.html_safe, id: 'registration-admin-menu' - end - - def admin_update_form(options = {}, &block) - form_tag(administration_update_path(@this_conference.slug, @admin_step), options, &block) - end - - def interest_button(workshop) - interested = workshop.interested?(current_user) ? :remove_interest : :show_interest - id = "#{interested.to_s.gsub('_', '-')}-#{workshop.id}" - return (off_screen (_"forms.actions.aria.#{interested.to_s}"), id) + - (button_tag interested, :value => :toggle_interest, :class => (workshop.interested?(current_user) ? :delete : :add), aria: { labelledby: id }) - end - - def interest_text(workshop) - if workshop.interested?(current_user) - return _'articles.workshops.info.you_are_interested_count', :vars => {:count => (workshop.interested_count - 1)} - end - - return _'articles.workshops.info.interested_count', :vars => {:count => workshop.interested_count} - end - - def host_guests_table(registration) - id = registration.id - html = '' - - @housing_data[id][:guests].each do | area, guests | - guest_rows = '' - guests.each do | guest_id, guest | - status_html = '' - - @housing_data[id][:guest_data][guest_id][:errors].each do | error, value | - if value.is_a?(Array) - value.each do | v | - status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: v)) - end - else - status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: value)) - end - end - - @housing_data[id][:guest_data][guest_id][:warnings].each do | error, value | - if value.is_a?(Array) - value.each do | v | - status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", v)) - end - else - status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", vars: value)) - end - end - - if status_html.present? - status_html = content_tag(:ul, status_html.html_safe) - end - - guest_rows += content_tag :tr, id: "hosted-guest-#{guest_id}" do - (content_tag :td, guest[:guest].user.name) + - (content_tag :td do - (guest[:guest].city + - (content_tag :a, (_'actions.workshops.Remove'), href: '#', class: 'remove-guest', data: { guest: guest_id })).html_safe - end) + - (content_tag :td, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy]) - end - end - - space_size = (@housing_data[id][:space][area] || 0) - - # add empty rows to represent empty guest spots - for i in guests.size...space_size - guest_rows += content_tag :tr, class: 'empty-space' do - (content_tag :td, ' '.html_safe, colspan: 2) + - (content_tag :td) - end - end - - status_html = '' - if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? - @housing_data[id][:warnings][:space][area].each do | w | - status_html += content_tag(:li, _("warnings.messages.housing.space.#{w.to_s}")) - end - end - if status_html.present? - status_html = content_tag(:ul, status_html.html_safe) - end - - html += content_tag :tr do - (content_tag :th, (_"forms.labels.generic.#{area}"), colspan: 2) + - (content_tag :th, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy]) - end - html += guest_rows - html += content_tag :tr, class: 'place-guest' do - content_tag :td, class: guests.size >= space_size ? 'full' : nil, colspan: 3 do - content_tag :a, (_'forms.actions.generic.place_guest'), class: 'select-guest', href: '#', data: { host: id, space: area } - end - end - end - - content_tag :table, html.html_safe, class: 'host-table' - end - - def get_housing_match(host, guest, space) - housing_data = guest.housing_data || [] - - if housing_data['host'].present? - if housing_data['host'] == host.id - return space == housing_data['space'] ? :selected_space : :other_space - end - - return :other_host - end - - if space_matches?(space, guest.housing) && available_dates_match?(host, guest) - return :good_match - end - - return :bad_match - end - - def get_workshop_match(workshop, day, block, location) - if workshop.event_location_id.present? && workshop.present? - if (Date.parse params[:day]).wday == workshop.block['day'] && block == workshop.block['block'].to_i - return :selected_space - end - - if location.present? && location.id == workshop.event_location_id - return :other_space - end - - return :other_host - end - - if location.present? - needs = JSON.parse(workshop.needs || '[]').map &:to_sym - amenities = JSON.parse(location.amenities || '[]').map &:to_sym - - if (needs & amenities).length < needs.length - return :bad_match - end - end - - (((((@schedule[@day] || {})[:times] || {})[@time] || {})[:item] || {})[:workshops] || {}).each do | l, w | - if w[:workshop].id != workshop.id - f_a = w[:workshop].active_facilitators.map { | f | f.id } - f_b = workshop.active_facilitators.map { | f | f.id } - if (f_a & f_b).present? - return :bad_match - end - end - end - - # housing_data = guest.housing_data || [] - - # if housing_data['host'].present? - # if housing_data['host'] == host.id - # return space == housing_data['space'] ? :selected_space : :other_space - # end - - # return :other_host - # end - - # if space_matches?(space, guest.housing) && available_dates_match?(host, guest) - # return :good_match - # end - - # return :bad_match - return :good_match - end - - def space_matches?(host_space, guest_space) - return false unless host_space.present? && guest_space.present? - - if host_space.to_s == 'bed_space' || host_space.to_s == 'floor_space' - return guest_space.to_s == 'house' - end - - return host_space.to_s == 'tent_space' && guest_space.to_s == 'tent' - end - - def available_dates_match?(host, guest) - return false unless host.housing_data['availability'].present? && host.housing_data['availability'][1].present? - if host.housing_data['availability'][0] <= guest.arrival && - host.housing_data['availability'][1] >= guest.departure - return true - end - - return false - end - - def host_guests_widget(registration) - html = '' - classes = ['host'] - - id = registration.id - @housing_data[id][:guests].each do | area, guests | - max_space = @housing_data[id][:space][area] || 0 - area_name = (_"forms.labels.generic.#{area}") - status_html = '' - if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? - @housing_data[id][:warnings][:space][area].each do | w | - status_html += content_tag(:div, _("warnings.housing.space.#{w.to_s}"), class: 'warning') - end - end - space_html = content_tag(:h5, area_name + _!(" (#{guests.size.to_s}/#{max_space.to_s})") + status_html.html_safe) - guest_items = '' - guests.each do | guest_id, guest | - guest_items += content_tag(:li, guest[:guest].user.name, id: "hosted-guest-#{guest_id}") - end - space_html += content_tag(:ul, guest_items.html_safe) - space_html += button_tag :place_guest, type: :button, value: "#{area}:#{id}", class: [:small, 'place-guest', 'on-top-only', guests.size >= max_space ? (guests.size > max_space ? :overbooked : :booked) : nil, max_space > 0 ? nil : :unwanted] - html += content_tag(:div, space_html, class: [:space, area, max_space > 0 || guests.size > 0 ? nil : 'on-top-only']) - end - - classes << 'status-warning' if @housing_data[id][:warnings].present? - classes << 'status-error' if @housing_data[id][:errors].present? - - return { html: html.html_safe, class: classes.join(' ') } - end - - def signin_link - @login_dlg ||= true - link_to (_'forms.actions.generic.login'), settings_path, data: { 'sign-in': true } - end - - def link_with_confirmation(link_text, confirmation_text, path, args = {}) - @confirmation_dlg ||= true - args[:data] ||= {} - args[:data][:confirmation] = true - link_to path, args do - (link_text.to_s + content_tag(:template, confirmation_text, class: 'message')).html_safe - end - end - - def link_info_dlg(link_text, info_text, info_title, args = {}) - @info_dlg ||= true - args[:data] ||= {} - args[:data]['info-title'] = info_title - args[:data]['info-text'] = true - content_tag(:a, args) do - (link_text.to_s + content_tag(:template, info_text, class: 'message')).html_safe - end - end - - def button_with_confirmation(button_name, confirmation_text = nil, args = {}) - if confirmation_text.is_a? Hash - args = confirmation_text - confirmation_text = nil - end - - confirmation_text ||= (_"forms.confirmations.#{button_name.to_s}", :p) - @confirmation_dlg ||= true - args[:data] ||= {} - args[:data][:confirmation] = true - button_tag button_name, args do - ((_"forms.actions.generic.#{button_name.to_s}") + content_tag(:template, confirmation_text, class: 'message')).html_safe - end - end - - def richtext(text, reduce_headings = 2) - return '' unless text.present? - return _!(text). - gsub(/<(\/?)h4>/, '<\1h' + (reduce_headings + 4).to_s + '>'). - gsub(/<(\/?)h3>/, '<\1h' + (reduce_headings + 3).to_s + '>'). - gsub(/<(\/?)h2>/, '<\1h' + (reduce_headings + 2).to_s + '>'). - gsub(/<(\/?)h1>/, '<\1h' + (reduce_headings + 1).to_s + '>'). - html_safe - end - - def truncate(text) - strip_tags(text.gsub('>', '> ')).gsub(/^(.{40,60})\s.*$/m, '\1…').html_safe - end - - def translate_fields(object, field_options = {}, options = {}) - html = '' - nav = '' - - # set the selected locale - selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym - - I18n.backend.enabled_locales.each do | locale | - # ses if this should b the selected field - class_name = selected_locale == locale.to_sym ? 'selected' : nil - - # add the locale to the nav - nav += content_tag(:li, - content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), - class: class_name, data: { locale: locale }).html_safe - - fields = '' - field_options.each do | name, __options | - _options = __options.deep_dup - # add the field - value = object.is_a?(Hash) ? object[locale.to_sym] : object.get_column_for_locale!(name, locale, false) - - # use the default value if we need to - if _options[:default].present? && value.blank? - value = _(_options[:default], locale: locale) - end - - _options[:index] = locale - _options[:lang] = locale - _options[:parent_options] = { lang: locale } - type = "#{_options[:type].to_s}" - _options.delete(:type) - - fields += self.send(type, name, value, _options).html_safe - end - - html += content_tag(:li, fields.html_safe, class: class_name, data: { locale: locale }).html_safe - end - - if options[:class].nil? - options[:class] = [] - elsif options[:class].is_a?(String) - options[:class] = [options[:class]] - end - options[:class] += ['translator', 'multi-field-translator'] - - (fieldset(nil, options) do - content_tag(:ul, nav.html_safe, class: 'locale-select').html_safe + - content_tag(:ul, html.html_safe, class: 'text-editors').html_safe - end).html_safe - end - - def translate_textarea(name, object, property = nil, options = {}) - html = '' - nav = '' - - # see if options was passed in as property - if options.blank? && property.is_a?(Hash) - options = property - property = nil - end - - # set the selected locale - selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym - - I18n.backend.enabled_locales.each do | locale | - # ses if this should b the selected field - class_name = selected_locale == locale.to_sym ? 'selected' : nil - - # add the locale to the nav - nav += content_tag(:li, - content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), - class: class_name, data: { locale: locale }).html_safe - - # add the field - value = object.is_a?(Hash) ? object[locale.to_sym] : object.get_column_for_locale!(name, locale, false) - - # use the default value if we need to - if options[:default].present? && value.blank? - value = _(options[:default], locale: locale) - end - - html += content_tag(:li, textarea(name, value, { - label: false, - edit_on: options[:edit_on], - parent_options: { - lang: locale - }, - index: locale - }).html_safe, class: class_name, data: { locale: locale }).html_safe - end - - if options[:class].nil? - options[:class] = [] - elsif options[:class].is_a?(String) - options[:class] = [options[:class]] - end - options[:class] += ['translator'] - - (fieldset(name, options) do - content_tag(:ul, nav.html_safe, class: 'locale-select').html_safe + - content_tag(:ul, html.html_safe, class: 'text-editors').html_safe - end).html_safe - end - - def textarea(name, value = nil, options = {}) - id = unique_id(name) - label_id = "#{id}-label" - description_id = nil - html = '' - - if options[:heading].present? - label_id = "#{name.to_s}-label" unless options[:label] - html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: label_id) - end - - if options[:label] == false - label_id = options[:labelledby] - elsif options[:label].present? - html += label_tag([name, id], nil, id: label_id) do - _(options[:label], :t, vars: options[:vars] || {}) - end - else - html += label_tag([name, id], nil, id: label_id) - end - - if options[:help].present? - description_id ||= "#{id}-desc" - html += content_tag(:div, _(options[:help], :s, 2), id: description_id, class: 'input-field-help') - end - - if options[:warning].present? - description_id ||= "#{id}-desc" - html += content_tag(:div, _(options[:warning], :s, 2), id: description_id, class: 'warning-info') - end - - aria = {} - aria[:labelledby] = label_id if label_id.present? - aria[:describedby] = description_id if description_id.present? - css_class = [ - options[:short] === true ? :short : nil - ].compact - - html_name = name.to_s + (options[:index] ? "[#{options[:index]}]" : '') - if options[:plain] - html += (text_area_tag html_name, value, - id: id, - lang: options[:lang], - aria: aria, - class: css_class - ) - else - html += content_tag(:div, value.present? ? value.html_safe : '', - id: id, - data: { name: html_name, 'edit-on': options[:edit_on] || :load }, - lang: options[:lang], - aria: aria, - tabindex: 0, - class: [:textarea] + css_class - ) - - add_stylesheet :editor - add_inline_script :pen - add_inline_script :markdown - add_inline_script :editor - end - - parent_options = options[:parent_options] || {} - if parent_options[:class].nil? - parent_options[:class] = [] - elsif parent_options[:class].is_a?(String) - parent_options[:class] = [parent_options[:class]] - end - - parent_options[:class] += ['text-area-field', 'input-field'] - html = content_tag(:div, html.html_safe, parent_options).html_safe - html += _original_content(options[:original_value], options[:original_lang]) if options[:original_value].present? - - return html.html_safe - end - - def fieldset(name = nil, options = {}, &block) - html = '' - label = '' - description = '' - description_id = nil - errors = '' - - if name.present? - if options[:label] != false - label = content_tag(:legend, - _(( - options[:label].is_a?(String) ? - options[:label] : - "forms.labels.generic.#{name}"), :t, vars: options[:vars] || {})) - end - - if options[:help].present? - description_id = unique_id("#{name.to_s}-desc") - description = content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id) - end - - errors = (show_errors name) - end - - html = label + errors + description + content_tag(:div, class: :fieldgroup, &block) - - aria = description_id.present? ? { describedby: description_id } : nil - (content_tag(:fieldset, html.html_safe, - aria: aria, - class: ((options[:class] || []) + [ - options[:inline] ? :inline : nil, - options[:inline_label] ? 'inline-label' : nil, - errors.present? ? 'has-error' : nil - ]).compact - ) - ).html_safe - end - - def selectfield(name, value, select_options, options = {}) - unless select_options.first.is_a?(Array) - so = select_options - select_options = [] - so.each do | opt | - select_options << [ I18n.t("forms.options.#{name.to_s}.#{opt.to_s}"), opt] - end - end - textfield(name, value, options.merge({type: :select, options: select_options})) - end - - def telephonefield(name, value, options = {}) - textfield(name, value, options.merge({type: :telephone})) - end - - def numberfield(name, value, options = {}) - textfield(name, value, options.merge({type: :number})) - end - - def searchfield(name, value, options = {}) - textfield(name, value, options.merge({type: :search})) - end - - def userfield(name, value, options = {}) - # eventually this will be a dynamic field to find users, for now we'll just use emails - # add_inline_script :userfield - emailfield(name, value, options)# .merge({ - # parent_options: { class: ['user-field'] }, - # after: content_tag(:div, '', class: 'user-name') - # })) - end - - def emailfield(name, value, options = {}) - textfield(name, value, options.merge({type: :email})) - end - - def filefield(name, value, options = {}) - textfield(name, value, options.merge({type: :file})) - end - - def passwordfield(name, value, options = {}) - textfield(name, value, options.merge({type: :password})) - end - - def textfield(name, value, options = {}) - html = '' - id = unique_id(name) - description_id = nil - - if options[:heading].present? - description_id ||= "#{id.to_s}-desc" - html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: description_id) - end - - if options[:help].present? - description_id ||= "#{id.to_s}-desc" - html += content_tag(:div, _(options[:help], :s, 2, vars: options[:vars] || {}), class: 'input-field-help', id: description_id) - end - - html += show_errors name, value - - inside_label = '' - - if options[:type] == :file - inside_label = (content_tag(:div, class: 'file-field-selector') do - (options[:preview] ? content_tag(:img, nil, src: value.present? ? value.url : nil).html_safe : '').html_safe + - content_tag(:div, (value.present? ? File.basename(value.url) : (_'forms.labels.generic.no_file_selected')), class: 'file-field-name ' + (value.present? ? 'selected' : 'unselected')).html_safe + - content_tag(:a, (_'forms.actions.generic.select_file'), class: :button) - end) - end - - label_text = nil - if options[:label].present? - label_text = _(options[:label], :t, vars: options[:vars] || {}) - elsif options[:label] != false - label_text = (_"forms.labels.generic.#{name}") - elsif options[:type] == :select || options[:type] == :file - # add an empty label so that the drop down button will still appear - label_text = '' - end - - label_options = {} - # let the label be selected if the input is hidden - label_options[:tabindex] = 0 if options[:type] == :file - - unless label_text.nil? - html += label_tag id, (label_text + inside_label).html_safe - end - - input_options = { - id: id, - required: options[:required], - lang: options[:lang], - min: options[:min], - max: options[:max], - step: options[:step], - aria: description_id ? { describedby: description_id } : nil - } - - case name - when :address - input_options[:autocomplete] = 'address-line1' - when :name - input_options[:autocomplete] = 'name' - when :location - input_options[:autocomplete] = 'address-level2' - when :email - input_options[:autocomplete] = 'email' - when :phone - input_options[:autocomplete] = 'tel' - when :paypal_email_address, :paypal_username, :paypal_password, :paypal_signature - input_options[:autocomplete] = 'off' - end - - case options[:type] - when :select - option_list = options_for_select(options[:options], value) - - # make sure that we have an empty option if the select is required - if options[:required] && options[:options].first.present? && options[:options].first.last.present? - option_list = ('' + option_list).html_safe - end - html += select_tag(name, option_list, input_options) - when :file - add_inline_script :filefield - input_options[:tabindex] = '-1' - html += off_screen(file_field_tag name, input_options) - else - input_options[:autocomplete] = 'off' if options[:type] == :search - html += send("#{(options[:type] || :text).to_s}_field_tag", name, value, input_options) - end - - if options[:after].present? - html += options[:after].html_safe - end - - html = content_tag(:div, html.html_safe, - class: [ - "#{(options[:type] || :text).to_s}-field", - 'input-field', - value.present? ? nil : 'empty', - options[:big] ? 'big' : nil, - options[:small] ? 'small' : nil, - options[:stretch] ? 'stretch-item' : nil, - options[:full] ? 'full' : nil, - options[:inline_label] ? 'inline-label' : nil, - (@errors || {})[name].present? ? 'has-error' : nil - ].compact + (((options[:parent_options] || {})[:class]) || [])) - - html += _original_content(options[:original_value], options[:original_lang]) if options[:original_value].present? - - return html.html_safe - end - - def radiobuttons(name, boxes, value, label_key, options = {}) - checkboxes(name, boxes, [value], label_key, options.merge({radiobuttons: true})) - end - - def checkbox(name, value, label_key, options = {}) - checkboxes(name, [true], value, label_key, options) - end - - def unique_id(id) - id = id.to_s.gsub('[', '_').gsub(']', '') - - @_ids ||= {} - @_ids[id] ||= 0 - - new_id = id - - if @_ids[id] > 0 - new_id += "--#{@_ids[id]}" - end - - @_ids[id] += 1 - - return new_id - end - - def checkboxes(name, boxes, values, label_key, options = {}) - html = '' - - label_id = nil - description_id = nil - - if options[:heading].present? - label_id ||= unique_id("#{name.to_s}-label") - html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: label_id) - end - - help = nil - - if options[:help].present? - description_id ||= unique_id("#{name.to_s}-desc") - help = content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id) - end - - html += help if help.present? && !options[:right_help] - - boxes_html = '' - - labels = nil - is_single = !values.is_a?(Array) - if boxes.length > 0 - if boxes.first.is_a?(Array) - labels = boxes.map(&:first) - boxes = boxes.map(&:last) - end - elsif !boxes.first.is_a?(Integer) - values = values.present? ? values.map(&:to_s) : [] unless is_single - boxes = boxes.map(&:to_s) - end - - # convert the required value into a pure boolean - required = !!options[:required] - - boxes.each_with_index do | box, i | - checked = (is_single ? values.present? : values.include?(box)) - values -= [box] if checked && !is_single - id = nil - if options[:radiobuttons].present? - id = unique_id("#{name.to_s}_#{box}") - boxes_html += radio_button_tag(name, box, checked, id: id, required: required) - else - _name = (is_single ? name : "#{name.to_s}[#{box}]") - id = unique_id(_name) - boxes_html += check_box_tag(_name, 1, checked, data: { toggles: options[:toggles] }.compact, id: id, required: required) - end - - # we only need the required attribute on one element - required = false - - if labels.present? - label = labels[i] - elsif is_single - label = _(label_key.to_s) - elsif box.is_a?(Integer) - label = I18n.t(label_key.to_s)[box] - else - label = _("#{label_key.to_s}.#{box}") - end - - boxes_html += label_tag(id, label) - end - - if options[:other].present? && !is_single - id = nil - if options[:radiobuttons].present? - id = unique_id("#{name.to_s}_other") - boxes_html += radio_button_tag(name, :other, values.present?, id: id) - else - _name = "#{name.to_s}[other]" - id = unique_id(_name) - boxes_html += check_box_tag(_name, 1, values.present?, id: id) - end - boxes_html += label_tag id, - content_tag(:div, - text_field_tag("other_#{name.to_s}", values.first, placeholder: (_"#{label_key}.other"), required: values.present?), - class: 'other') - end - - html += content_tag(:fieldset, content_tag(:div, boxes_html.html_safe, - class: [ - 'check-box-field', - 'input-field', - options[:vertical] ? 'vertical' : nil, - options[:inline] ? 'inline' : nil, - options[:small] ? 'small' : nil, - options[:big] ? 'big' : nil - ].compact).html_safe, - aria: { - labelledby: label_id, - describedby: description_id - }, - class: [ - options[:centered] ? 'centered' : nil, - options[:right_help] ? 'right-help' : nil - ].compact - ) - - html += help if help.present? && options[:right_help] - - return html.html_safe - end - - def companion(registration) - if registration.housing_data.present? && registration.housing_data['companions'].present? && registration.housing_data['companions'].first.present? - companion_user = User.find_user(registration.housing_data['companions'].first) - - if companion_user.present? - cr = ConferenceRegistration.where(user_id: companion_user.id).order(created_at: :desc).limit(1).first - - if cr.present? && ((cr.steps_completed || []).include? 'questions') - return companion_user - end - end - return :unregistered - end - return nil - end - - def comment(comment) - add_inline_script :time - add_js_translation('datetime.distance_in_words') - - content_tag(:div, class: 'comment-body') do - content_tag(:h4, comment.user.name, class: 'comment-title') + - content_tag(:time, time(comment.created_at, :default), datetime: comment.created_at.to_s) + - content_tag(:div, class: 'comment-text') do - markdown comment.comment - end - end - end - - def html_edit_table(excel_data, options = {}) - attributes = { class: options[:class], id: options[:id] } - attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? - - if options[:column_names].is_a? Hash - return content_tag(:table, attributes) do - max_columns = 0 - column_names = {} - (content_tag(:thead) do - headers = '' - options[:column_names].each do | header_name, columns | - column_names[header_name] ||= [] - headers += content_tag(:th, excel_data[:keys][header_name].present? ? _(excel_data[:keys][header_name]) : '', colspan: 2) - row_count = columns.size - columns.each do | column | - column_names[header_name] << column - if (options[:row_spans] || {})[column].present? - row_count += (options[:row_spans][column] - 1) - for i in 1...options[:row_spans][column] - column_names[header_name] << false - end - end - end - max_columns = row_count if row_count > max_columns - end - content_tag(:tr, headers.html_safe) - end) + (content_tag(:tbody) do - rows = '' - - for i in 0...max_columns - columns_html = '' - column_names.each do | header_name, columns | - column = columns[i] - if column.present? - attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } - if (options[:row_spans] || {})[column].present? - attributes[:rowspan] = options[:row_spans][column] - end - columns_html += content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '', rowspan: attributes[:rowspan]) + - edit_column(nil, column, nil, attributes, excel_data, options) - elsif column != false - columns_html += content_tag(:td, ' ', colspan: 2, class: :empty) - end - end - rows += content_tag(:tr, columns_html.html_safe, { class: 'always-edit', data: { key: '' } }) - end - rows.html_safe - end) - end - else - return content_tag(:table, attributes) do - (content_tag(:tbody) do - rows = '' - excel_data[:columns].each do |column| - if (excel_data[:column_types] || {})[column] != :table && ((options[:column_names] || []).include? column) - rows += content_tag(:tr, { class: 'always-edit', data: { key: '' } }) do - attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } - columns = content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '') + - edit_column(nil, column, nil, attributes, excel_data, options) - end - end - end - rows.html_safe - end) - end - end - end - - def html_table(excel_data, options = {}) - options[:html] = true - attributes = { class: options[:class], id: options[:id] } - attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? - content_tag(:table, attributes) do - (content_tag(:thead) do - content_tag(:tr, excel_header_columns(excel_data)) - end) + - content_tag(:tbody, excel_rows(excel_data, {}, options)) - end - end - - def excel_table(excel_data) - format_xls 'table' do - workbook use_autowidth: true - format bg_color: '333333' - format 'td', font_name: 'Calibri', fg_color: '333333' - format 'th', font_name: 'Calibri', b: true, bg_color: '333333', fg_color: 'ffffff' - format 'th.sub-table', font_name: 'Calibri', b: true, bg_color: 'DDDDDD', fg_color: '333333' - format 'td.datetime', num_fmt: 22, font_name: 'Courier New', sz: 10, fg_color: '333333' - format 'td.date.day', num_fmt: 14, font_name: 'Courier New', sz: 10, fg_color: '333333' - format 'td.money', num_fmt: 2, font_name: 'Courier New', sz: 10, fg_color: '333333' - format 'td.number', font_name: 'Courier New', sz: 10, fg_color: '333333' - format 'td.bold', font_name: 'Calibri', fg_color: '333333', b: true - end - - content_tag(:table) do - (content_tag(:thead) do - content_tag(:tr, excel_header_columns(excel_data)) - end) + - content_tag(:tbody, excel_rows(excel_data)) - end - end - - def excel_header_columns(data, padding = {}, class_name = nil) - columns = '' - - data[:columns].each do |column| - unless data[:column_types].present? && data[:column_types][column] == :table - columns += content_tag(:th, data[:keys][column].present? ? _(data[:keys][column]) : '', class: class_name) - end - end - - pad_columns(columns, padding, :th) - end - - def excel_empty_row(data, padding = {}) - columns = '' - - data[:columns].each do |column| - unless data[:column_types].present? && data[:column_types][column] == :table - columns += content_tag(:td) - end - end - - content_tag(:tr, pad_columns(columns, padding)) - end - - def pad_columns(columns, padding, column_type = :td) - left = '' - - for i in 1..(padding['left'] || 0) - left += content_tag(:td) - end - - right = '' - for i in 1..(padding['right'] || 0) - right += content_tag(:td) - end - - (left + columns + right).html_safe - end - - def excel_columns(row, data, padding = {}, options = {}) - columns = '' - - data[:columns].each do |column| - value = row[column].present? ? (_!row[column].to_s) : '' - class_name = nil - is_sub_table = false - - if data[:column_types].present? && data[:column_types][column].present? - if data[:column_types][column] == :table - is_sub_table = true - else - class_name = data[:column_types][column] - end - end - - unless is_sub_table - attributes = { class: [class_name] } - if options[:html] && row[:html_values].present? && row[:html_values][column].present? - value = row[:html_values][column] - end - - if options[:editable] - attributes[:data] = { 'column-id' => column } - end - - if (options[:column_names] || []).include? column - attributes[:tabindex] = 0 - end - - columns += content_tag(:td, value, attributes) - end - end - - pad_columns(columns, padding) - end - - def editor_columns(row, data, padding = {}, options = {}) - columns = '' - - data[:columns].each do |column| - value = row[column].present? ? (_!row[column].to_s) : '' - class_name = nil - is_sub_table = false - - if data[:column_types].present? && data[:column_types][column].present? - if data[:column_types][column] == :table - is_sub_table = true - else - class_name = data[:column_types][column] - end - end - - unless is_sub_table - attributes = { class: [class_name] } - - if options[:editable] - attributes[:data] = { 'column-id' => column } - end - - if (options[:column_names] || []).include? column - columns += edit_column(row, column, value, attributes, data, options) - else - columns += content_tag(:td, value, attributes) - end - - end - end - - pad_columns(columns, padding) - end - - def edit_column(row, column, value, attributes, data, options) - attributes[:class] << 'has-editor' - raw_value = row.present? ? (row[:raw_values][column] || value) : nil - - if row.present? && options[:html] && row[:html_values].present? && row[:html_values][column].present? - value = row[:html_values][column] - end - - editor_attributes = { class: 'cell-editor', data: { value: raw_value.to_s } } - - # create the control but add the original value to set the width and height - editor_value = content_tag(:div, value, class: 'value') - if (options[:column_options] || {})[column].present? - value = (editor_value.html_safe + select_tag(column, options_for_select([['', '']] + options[:column_options][column], raw_value), editor_attributes)).html_safe - elsif data[:column_types][column] == :text - editor_attributes[:name] = column - value = (editor_value.html_safe + content_tag(:textarea, raw_value, editor_attributes)).html_safe - else - editor_attributes[:name] = column - editor_attributes[:value] = raw_value - editor_attributes[:required] = :required if (options[:required_columns] || []).include? column - type = data[:column_types][column] || :unknown - editor_attributes[:type] = { money: :number, number: :number, email: :email }[type] || :text - value = (editor_value.html_safe + content_tag(:input, nil, editor_attributes)).html_safe - end - - return content_tag(:td, value, attributes) - end - - def excel_sub_tables(row, data, padding = {}, options = {}) - rows = '' - - # shift the table right - new_padding = { - 'left' => (padding['right'] || 0) + 1, - 'right' => (padding['right'] || 0) - 1 - } - - data[:columns].each do |column| - if data[:column_types].present? && data[:column_types][column] == :table - rows += content_tag(:tr, excel_header_columns(row[column], new_padding, 'sub-table')) - rows += excel_rows(row[column], new_padding) - rows += excel_empty_row(row[column], new_padding) - end - - end - - rows.html_safe - end - - def excel_rows(data, padding = {}, options = {}) - rows = '' - data[:data].each do |row| - attributes = {} - - if options[:primary_key].present? - attributes[:data] = { key: row[options[:primary_key]] } - end - - attributes[:class] = [] - - if options[:editable] - attributes[:class] << :editable - end - - rows += content_tag(:tr, excel_columns(row, data, padding, options), attributes) + - excel_sub_tables(row, data, padding) - rows += content_tag(:tr, editor_columns(row, data, padding, options), class: :editor) if options[:editable] - end - rows.html_safe - end - - def registrations_edit_table_options - { - id: 'create-table', - class: ['registrations', 'admin-edit', 'always-editing'], - primary_key: :id, - column_names: { - contact_info: [ - :name, - :email, - :is_subscribed, - :city, - :preferred_language - ] + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym }, - questions: [ - :registration_fees_paid, - :is_attending, - :arrival, - :departure, - :housing, - :bike, - :food, - :companion_email, - :allergies, - :other - ], - hosting: [ - :can_provide_housing, - :address, - :phone, - :first_day, - :last_day - ] + ConferenceRegistration.all_spaces + - ConferenceRegistration.all_considerations + [ - :notes - ] - }, - row_spans: { - allergies: 3, - other: 2 - }, - required_columns: [:name, :email], - editable: administration_update_path(@this_conference.slug, @admin_step), - column_options: @column_options - } - end - - def registrations_table_options - { - id: 'search-table', - class: ['registrations', 'admin-edit'], - primary_key: :id, - column_names: [ - :registration_fees_paid, - :is_attending, - :is_subscribed, - :city, - :preferred_language, - :arrival, - :departure, - :housing, - :bike, - :food, - :companion_email, - :allergies, - :other, - :can_provide_housing, - :address, - :phone, - :first_day, - :last_day, - :notes - ] + - User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym } + - ConferenceRegistration.all_spaces + - ConferenceRegistration.all_considerations, - editable: administration_update_path(@this_conference.slug, @admin_step), - column_options: @column_options - } - end - - def potential_provider(registration) - return false unless registration.present? && registration.city.present? && registration.conference.present? - conditions = registration.conference.provider_conditions || - Conference.default_provider_conditions - return city_distance_less_than(registration.conference.city, registration.city, - (conditions['distance']['number'] || '0').to_i, - conditions['distance']['unit']) - end - - def city_distance_less_than(city1, city2, max_distance, unit) - return false if city1.nil? || city2.nil? - return true if city1.id == city2.id - return false if max_distance < 1 - return Geocoder::Calculations.distance_between( - [city1.latitude, city1.longitude], [city2.latitude, city2.longitude], - units: unit.to_sym) < max_distance - end - - private - def _original_content(value, lang) - content_tag(:div, ( - content_tag(:h4, _('translate.content.Translation_of')) + - content_tag(:div, value, class: 'value', lang: lang) - ).html_safe, class: 'original-text') - end - - def _form_field(type, name, value, options) - if type == 'check_box' - self.send(type + '_tag', name, "1", value, options) - else - self.send(type + '_tag', name, value, options) - end - end end diff --git a/app/helpers/bike_bike_form_helper.rb b/app/helpers/bike_bike_form_helper.rb deleted file mode 100644 index 818c048..0000000 --- a/app/helpers/bike_bike_form_helper.rb +++ /dev/null @@ -1,407 +0,0 @@ - -module BikeBikeFormHelper - include ActionView::Helpers::FormTagHelper - - TEMPLATE_DIR = 'layouts/fields' - - def check_box_tag(name, value = "1", checked = false, options = {}) - render_field(name, options = get_options(name, options), super(name, value, checked, options), value) - end - - def color_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def date_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def datetime_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def datetime_local_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def email_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def file_field_tag(name, options = {}) - render_field(name, options = get_options(name, options), super(name, options)) - end - - def hidden_field_tag(name, value = nil, options = {}) - super(name, value, options) - end - - def month_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def number_field_tag(name, value = nil, options = {}) - options[:_no_wrapper] = true - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def password_field_tag(name = "password", value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def phone_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def radio_button_tag(name, value, checked = false, options = {}) - render_field(name, options = get_options(name, options), super(name, value, checked, options), value) - end - - def range_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def search_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def select_tag(name, option_tags = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, option_tags, options)) - end - - def telephone_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def text_area_tag(name, content = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, content, options), content) - end - - def text_field_tag(name, value = nil, options = {}) - if options[:_no_wrapper] - options.delete(:_no_wrapper) - options[:no_wrapper] = true - end - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def time_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def url_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def week_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def country_select_tag(name, value, options={}) - #options[:no_wrapper] = true - render_field(name, options = get_options(name, options), super(name, value, options), value) - end - - def subregion_select_tag(name, value, parent_region_or_code, options = {}, html_options = {}) - render_field(name, options = get_options(name, options), super(name, value, parent_region_or_code, options), value) - end - - #def button_tag - #def field_set_tag - #def form_tag - #def image_submit_tag - #def label_tag - #def submit_tag - #def utf8_enforcer_tag - - # FormHelper methods - - def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") - render_field(method, options = get_options(method, options), super(object_name, method, options, checked_value, unchecked_value)) - end - - def color_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def date_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def datetime_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def datetime_local_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def email_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def file_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options)) - end - - def hidden_field(object_name, method, options = {}) - super(object_name, method, options) - end - - def month_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def number_field(object_name, method, options = {}) - options[:_no_wrapper] = true - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def password_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def phone_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def radio_button(object_name, method, tag_value, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, tag_value, options), get_value(method, options)) - end - - def range_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def search_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def telephone_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def text_area(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def text_field(object_name, method, options = {}) - if options[:_no_wrapper] - options.delete(:_no_wrapper) - options[:no_wrapper] = true - end - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def time_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def url_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def week_field(object_name, method, options = {}) - render_field(method, options = get_options(method, options), super(object_name, method, options), get_value(method, options)) - end - - def form_for(*args, &block) - @record = args.first - - template = 'errors_' + @record.class.name.underscore - template = 'errors_default' unless lookup_context.exists?(template, [TEMPLATE_DIR], true) - - ( render (TEMPLATE_DIR + '/' + template) ) + super(*args, &block) - end - - def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) - render_field(method, options = get_options(method, options), super(object, method, collection, value_method, text_method, options, html_options, &block), get_value(method, options)) - end - - def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) - render_field(method, options = get_options(method, options), super(object, method, collection, value_method, text_method, options, html_options, &block), get_value(method, options)) - end - - def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {}) - render_field(method, options = get_options(method, options), super(object, method, collection, value_method, text_method, options, html_options), get_value(method, options)) - end - - def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) - render_field(method, options = get_options(method, options), super(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options), get_value(method, options)) - end - - def select(object, method, choices = nil, options = {}, html_options = {}, &block) - render_field(method, options = get_options(method, options), super(object, method, choices, options, html_options, &block), get_value(method, options)) - end - - def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {}) - render_field(method, options = get_options(method, options), super(object, method, priority_zones, options, html_options), get_value(method, options)) - end - - def country_select(object, method, priorities_or_options = {}, options_or_html_options = {}, html_options = {}) - if priorities_or_options.is_a? Array - options = options_or_html_options = get_options(method, priorities_or_options) - else - options = priorities_or_options = get_options(method, priorities_or_options) - end - render_field(method, options, super(object, method, priorities_or_options, options_or_html_options, html_options), get_value(method, options)) - end - - def subregion_select(object, method, parent_region_or_code, options = {}, html_options = {}) - render_field(method, options = get_options(method, options), super(object, method, parent_region_or_code, options, html_options), get_value(method, options)) - end - - # Custom fields - - def image_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), BikeBikeFormHelper.image_field_tag(name, value, options), value) - end - - def organization_select_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), BikeBikeFormHelper.organization_select_field_tag(name, value, options), value) - end - - def user_select_field_tag(name, value = nil, options = {}) - render_field(name, options = get_options(name, options), BikeBikeFormHelper.user_select_field_tag(name, value, options), value) - end - - #def grouped_options_for_select - #def option_groups_from_collection_for_select - #def options_for_select - #def options_from_collection_for_select - #def time_zone_options_for_select - - def form_actions(actions = []) - BikeBikeFormHelper.form_actions(actions) - end - - class << self - - def form_actions(actions = []) - render(:actions, {:actions => actions.is_a?(Array) ? actions : [actions]}) - end - - def image_field_tag(name, value, options, form = nil) - render(:field_image_field, {:name => name, :value => value, :options => options, :form => form}) - end - - def organization_select_field_tag(name, value, options, form = nil) - render(:field_organization_select_field, {:name => name, :value => value, :options => options, :form => form}) - end - - def user_select_field_tag(name, value, options, form = nil) - render(:field_user_select_field, {:name => name, :value => value, :options => options, :form => form}) - end - - def get_options(name, options, type) - if options[:placeholder] === false - options.delete(:placeholder) - elsif (['email_field', 'number_field', 'phone_field', 'search_field', 'telephone_field', 'text_area', 'text_field', 'url_field'].include? type) - options[:placeholder] ||= I18n.translate('form.placeholder.Enter_your_' + name.to_s) - end - return options - end - - def render_field(type, name, options, html, value = nil) - options.symbolize_keys! - if (options.has_key?(:no_wrapper) && options[:no_wrapper]) || /country/.match(name.to_s) && /^subregion_select/.match(type.to_s) || options[:type] == 'hidden' - return html - end - - params = Hash.new - params[:name] = name.to_sym - params[:options] = options - params[:html] = html - params[:type] = type - params[:value] = value - - template = template_exists?(type) ? type : 'default' - params[:label_template] = options[:label] === false ? nil : get_label_template(type, options) - params[:label_position] = options[:label] === false ? :none : label_position(type, options) - - render(template, params) - end - - def get_label_template(type, options) - if !options[:label] && /select(_field)?$/.match(type.to_s) - return nil - end - template_exists?('label_' + type) ? type : 'default' - end - - def label_position(type, options) - # one of: :before, :after, :inside, or :none - case type - when 'image_field' - return :inside - when 'organization_select_field' - return :none - #when 'select_field' - # return :before - end - return :before - end - - private - def render (template, params) - view = ActionView::Base.new(ActionController::Base.view_paths, params) - view.extend ApplicationHelper - view.render (TEMPLATE_DIR + '/' + template.to_s) - end - - def template_exists? (template) - view = ActionView::Base.new(ActionController::Base.view_paths, {}) - view.extend ApplicationHelper - view.lookup_context.exists?(template, [TEMPLATE_DIR], true) - end - end - - private - def get_type() - caller[1][/`.*'/][1..-2].gsub(/^(.*?)(_tag)?$/, '\1') - end - - def get_value(method, options) - options && options[:object] ? options[:object][method] : nil - end - - def get_options(name, options) - options[:_controller] = params[:controller] - BikeBikeFormHelper.get_options(name, options, get_type()) - end - - def render_field(name, options, html, value = nil) - BikeBikeFormHelper.render_field(get_type(), name, options, html, value) - end - - class BikeBikeFormBuilder < ActionView::Helpers::FormBuilder - ActionView::Base.default_form_builder = BikeBikeFormHelper::BikeBikeFormBuilder - - def image_field(method, value, options = {}) - custom_field(method, value, options, 'image_field') - end - - def organization_select_field(method, value, options = {}) - custom_field(method, value, options, 'organization_select_field') - end - - def user_select_field(method, value, options = {}) - custom_field(method, value, options, 'user_select_field') - end - - def actions(actions = []) - BikeBikeFormHelper.form_actions(actions) - end - - private - def custom_field(method, value, options, type) - if defined? params - options[:_controller] = params[:controller] - end - options[:_record] = object - options = BikeBikeFormHelper.get_options(method, options, type) - html = BikeBikeFormHelper.send(type + '_tag', method, value, options, self) - BikeBikeFormHelper.render_field(type, method, options, html, value) - end - end -end diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb new file mode 100644 index 0000000..5e1e815 --- /dev/null +++ b/app/helpers/form_helper.rb @@ -0,0 +1,729 @@ + +module FormHelper + def off_screen(text, id = nil) + content_tag(:span, text.html_safe, id: id, class: 'screen-reader-text') + end + + def translate_fields(object, field_options = {}, options = {}) + html = '' + nav = '' + + # set the selected locale + selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym + + I18n.backend.enabled_locales.each do | locale | + # ses if this should b the selected field + class_name = selected_locale == locale.to_sym ? 'selected' : nil + + # add the locale to the nav + nav += content_tag(:li, + content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), + class: class_name, data: { locale: locale }).html_safe + + fields = '' + field_options.each do | name, __options | + _options = __options.deep_dup + # add the field + value = object.is_a?(Hash) ? object[locale.to_sym] : object.get_column_for_locale!(name, locale, false) + + # use the default value if we need to + if _options[:default].present? && value.blank? + value = _(_options[:default], locale: locale) + end + + _options[:index] = locale + _options[:lang] = locale + _options[:parent_options] = { lang: locale } + type = "#{_options[:type].to_s}" + _options.delete(:type) + + fields += self.send(type, name, value, _options).html_safe + end + + html += content_tag(:li, fields.html_safe, class: class_name, data: { locale: locale }).html_safe + end + + if options[:class].nil? + options[:class] = [] + elsif options[:class].is_a?(String) + options[:class] = [options[:class]] + end + options[:class] += ['translator', 'multi-field-translator'] + + (fieldset(nil, options) do + content_tag(:ul, nav.html_safe, class: 'locale-select').html_safe + + content_tag(:ul, html.html_safe, class: 'text-editors').html_safe + end).html_safe + end + + def translate_textarea(name, object, property = nil, options = {}) + html = '' + nav = '' + + # see if options was passed in as property + if options.blank? && property.is_a?(Hash) + options = property + property = nil + end + + # set the selected locale + selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym + + I18n.backend.enabled_locales.each do | locale | + # ses if this should b the selected field + class_name = selected_locale == locale.to_sym ? 'selected' : nil + + # add the locale to the nav + nav += content_tag(:li, + content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), + class: class_name, data: { locale: locale }).html_safe + + # add the field + value = object.is_a?(Hash) ? object[locale.to_sym] : object.get_column_for_locale!(name, locale, false) + + # use the default value if we need to + if options[:default].present? && value.blank? + value = _(options[:default], locale: locale) + end + + html += content_tag(:li, textarea(name, value, { + label: false, + edit_on: options[:edit_on], + parent_options: { + lang: locale + }, + index: locale + }).html_safe, class: class_name, data: { locale: locale }).html_safe + end + + if options[:class].nil? + options[:class] = [] + elsif options[:class].is_a?(String) + options[:class] = [options[:class]] + end + options[:class] += ['translator'] + + (fieldset(name, options) do + content_tag(:ul, nav.html_safe, class: 'locale-select').html_safe + + content_tag(:ul, html.html_safe, class: 'text-editors').html_safe + end).html_safe + end + + def textarea(name, value = nil, options = {}) + id = unique_id(name) + label_id = "#{id}-label" + description_id = nil + html = '' + + if options[:heading].present? + label_id = "#{name.to_s}-label" unless options[:label] + html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: label_id) + end + + if options[:label] == false + label_id = options[:labelledby] + elsif options[:label].present? + html += label_tag([name, id], nil, id: label_id) do + _(options[:label], :t, vars: options[:vars] || {}) + end + else + html += label_tag([name, id], nil, id: label_id) + end + + if options[:help].present? + description_id ||= "#{id}-desc" + html += content_tag(:div, _(options[:help], :s, 2), id: description_id, class: 'input-field-help') + end + + if options[:warning].present? + description_id ||= "#{id}-desc" + html += content_tag(:div, _(options[:warning], :s, 2), id: description_id, class: 'warning-info') + end + + aria = {} + aria[:labelledby] = label_id if label_id.present? + aria[:describedby] = description_id if description_id.present? + css_class = [ + options[:short] === true ? :short : nil + ].compact + + html_name = name.to_s + (options[:index] ? "[#{options[:index]}]" : '') + if options[:plain] + html += (text_area_tag html_name, value, + id: id, + lang: options[:lang], + aria: aria, + class: css_class + ) + else + html += content_tag(:div, value.present? ? value.html_safe : '', + id: id, + data: { name: html_name, 'edit-on': options[:edit_on] || :load }, + lang: options[:lang], + aria: aria, + tabindex: 0, + class: [:textarea] + css_class + ) + + add_stylesheet :editor + add_inline_script :pen + add_inline_script :markdown + add_inline_script :editor + end + + parent_options = options[:parent_options] || {} + if parent_options[:class].nil? + parent_options[:class] = [] + elsif parent_options[:class].is_a?(String) + parent_options[:class] = [parent_options[:class]] + end + + parent_options[:class] += ['text-area-field', 'input-field'] + html = content_tag(:div, html.html_safe, parent_options).html_safe + html += _original_content(options[:original_value], options[:original_lang]) if options[:original_value].present? + + return html.html_safe + end + + def fieldset(name = nil, options = {}, &block) + html = '' + label = '' + description = '' + description_id = nil + errors = '' + + if name.present? + if options[:label] != false + label = content_tag(:legend, + _(( + options[:label].is_a?(String) ? + options[:label] : + "forms.labels.generic.#{name}"), :t, vars: options[:vars] || {})) + end + + if options[:help].present? + description_id = unique_id("#{name.to_s}-desc") + description = content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id) + end + + errors = (show_errors name) + end + + html = label + errors + description + content_tag(:div, class: :fieldgroup, &block) + + aria = description_id.present? ? { describedby: description_id } : nil + (content_tag(:fieldset, html.html_safe, + aria: aria, + class: ((options[:class] || []) + [ + options[:inline] ? :inline : nil, + options[:inline_label] ? 'inline-label' : nil, + errors.present? ? 'has-error' : nil + ]).compact + ) + ).html_safe + end + + def selectfield(name, value, select_options, options = {}) + unless select_options.first.is_a?(Array) + so = select_options + select_options = [] + so.each do | opt | + select_options << [ I18n.t("forms.options.#{name.to_s}.#{opt.to_s}"), opt] + end + end + textfield(name, value, options.merge({type: :select, options: select_options})) + end + + def telephonefield(name, value, options = {}) + textfield(name, value, options.merge({type: :telephone})) + end + + def numberfield(name, value, options = {}) + textfield(name, value, options.merge({type: :number})) + end + + def searchfield(name, value, options = {}) + textfield(name, value, options.merge({type: :search})) + end + + def userfield(name, value, options = {}) + # eventually this will be a dynamic field to find users, for now we'll just use emails + # add_inline_script :userfield + emailfield(name, value, options)# .merge({ + # parent_options: { class: ['user-field'] }, + # after: content_tag(:div, '', class: 'user-name') + # })) + end + + def emailfield(name, value, options = {}) + textfield(name, value, options.merge({type: :email})) + end + + def filefield(name, value, options = {}) + textfield(name, value, options.merge({type: :file})) + end + + def passwordfield(name, value, options = {}) + textfield(name, value, options.merge({type: :password})) + end + + def textfield(name, value, options = {}) + html = '' + id = unique_id(name) + html_name = name.to_s + (options[:index] ? "[#{options[:index]}]" : '') + description_id = nil + + if options[:heading].present? + description_id ||= "#{id.to_s}-desc" + html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: description_id) + end + + if options[:help].present? + description_id ||= "#{id.to_s}-desc" + html += content_tag(:div, _(options[:help], :s, 2, vars: options[:vars] || {}), class: 'input-field-help', id: description_id) + end + + html += show_errors name, value + + inside_label = '' + + if options[:type] == :file + inside_label = (content_tag(:div, class: 'file-field-selector') do + (options[:preview] ? content_tag(:img, nil, src: value.present? ? value.url : nil).html_safe : '').html_safe + + content_tag(:div, (value.present? ? File.basename(value.url) : (_'forms.labels.generic.no_file_selected')), class: 'file-field-name ' + (value.present? ? 'selected' : 'unselected')).html_safe + + content_tag(:a, (_'forms.actions.generic.select_file'), class: :button) + end) + end + + label_text = nil + if options[:label].present? + label_text = _(options[:label], :t, vars: options[:vars] || {}) + elsif options[:label] != false + label_text = (_"forms.labels.generic.#{name}") + elsif options[:type] == :select || options[:type] == :file + # add an empty label so that the drop down button will still appear + label_text = '' + end + + label_options = {} + # let the label be selected if the input is hidden + label_options[:tabindex] = 0 if options[:type] == :file + + unless label_text.nil? + html += label_tag id, (label_text + inside_label).html_safe + end + + input_options = { + id: id, + required: options[:required], + lang: options[:lang], + min: options[:min], + max: options[:max], + step: options[:step], + aria: description_id ? { describedby: description_id } : nil + } + + case name + when :address + input_options[:autocomplete] = 'address-line1' + when :name + input_options[:autocomplete] = 'name' + when :location + input_options[:autocomplete] = 'address-level2' + when :email + input_options[:autocomplete] = 'email' + when :phone + input_options[:autocomplete] = 'tel' + when :paypal_email_address, :paypal_username, :paypal_password, :paypal_signature + input_options[:autocomplete] = 'off' + end + + case options[:type] + when :select + option_list = options_for_select(options[:options], value) + + # make sure that we have an empty option if the select is required + if options[:required] && options[:options].first.present? && options[:options].first.last.present? + option_list = ('' + option_list).html_safe + end + html += select_tag(html_name, option_list, input_options) + when :file + add_inline_script :filefield + input_options[:tabindex] = '-1' + html += off_screen(file_field_tag html_name, input_options) + else + input_options[:autocomplete] = 'off' if options[:type] == :search + html += send("#{(options[:type] || :text).to_s}_field_tag", html_name, value, input_options) + end + + if options[:after].present? + html += options[:after].html_safe + end + + html = content_tag(:div, html.html_safe, + class: [ + "#{(options[:type] || :text).to_s}-field", + 'input-field', + value.present? ? nil : 'empty', + options[:big] ? 'big' : nil, + options[:small] ? 'small' : nil, + options[:stretch] ? 'stretch-item' : nil, + options[:full] ? 'full' : nil, + options[:inline_label] ? 'inline-label' : nil, + (@errors || {})[name].present? ? 'has-error' : nil + ].compact + (((options[:parent_options] || {})[:class]) || [])) + + html += _original_content(options[:original_value], options[:original_lang]) if options[:original_value].present? + + return html.html_safe + end + + def radiobuttons(name, boxes, value, label_key, options = {}) + checkboxes(name, boxes, [value], label_key, options.merge({radiobuttons: true})) + end + + def checkbox(name, value, label_key, options = {}) + checkboxes(name, [true], value, label_key, options) + end + + def unique_id(id) + id = id.to_s.gsub('[', '_').gsub(']', '') + + @_ids ||= {} + @_ids[id] ||= 0 + + new_id = id + + if @_ids[id] > 0 + new_id += "--#{@_ids[id]}" + end + + @_ids[id] += 1 + + return new_id + end + + def checkboxes(name, boxes, values, label_key, options = {}) + html = '' + boxes.map! { |box| box.is_a?(String) ? box.to_sym : box } + values.map! { |value| value.is_a?(String) ? value.to_sym : value } if values.is_a?(Array) + + label_id = nil + description_id = nil + + if options[:heading].present? + label_id ||= unique_id("#{name.to_s}-label") + html += content_tag(:h3, _(options[:heading], :t, vars: options[:vars] || {}), id: label_id) + end + + help = nil + + if options[:help].present? + description_id ||= unique_id("#{name.to_s}-desc") + help = content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id) + end + + html += help if help.present? && !options[:right_help] + + boxes_html = '' + + labels = nil + is_single = !values.is_a?(Array) + if boxes.length > 0 + if boxes.first.is_a?(Array) + labels = boxes.map(&:first) + boxes = boxes.map(&:last) + end + elsif !boxes.first.is_a?(Integer) + values = values.present? ? values.map(&:to_s) : [] unless is_single + boxes = boxes.map(&:to_s) + end + + # convert the required value into a pure boolean + required = !!options[:required] + + boxes.each_with_index do |box, i| + checked = (is_single ? values.present? : values.include?(box)) + values -= [box] if checked && !is_single + id = nil + if options[:radiobuttons].present? + id = unique_id("#{name.to_s}_#{box}") + boxes_html += radio_button_tag(name, box, checked, id: id, required: required) + else + _name = (is_single ? name : "#{name.to_s}[#{box}]") + id = unique_id(_name) + boxes_html += check_box_tag(_name, 1, checked, data: { toggles: options[:toggles] }.compact, id: id, required: required) + end + + # we only need the required attribute on one element + required = false + + if labels.present? + label = labels[i] + elsif is_single + label = _(label_key.to_s) + elsif box.is_a?(Integer) + label = I18n.t(label_key.to_s)[box] + else + label = _("#{label_key.to_s}.#{box}") + end + + boxes_html += label_tag(id, label) + end + + if options[:other].present? && !is_single + id = nil + if options[:radiobuttons].present? + id = unique_id("#{name.to_s}_other") + boxes_html += radio_button_tag(name, :other, values.present?, id: id) + else + _name = "#{name.to_s}[other]" + id = unique_id(_name) + boxes_html += check_box_tag(_name, 1, values.present?, id: id) + end + boxes_html += label_tag id, + content_tag(:div, + text_field_tag("other_#{name.to_s}", values.first, placeholder: (_"#{label_key}.other"), required: values.present?), + class: 'other') + end + + html += content_tag(:fieldset, content_tag(:div, boxes_html.html_safe, + class: [ + 'check-box-field', + 'input-field', + options[:vertical] ? 'vertical' : nil, + options[:inline] ? 'inline' : nil, + options[:small] ? 'small' : nil, + options[:big] ? 'big' : nil + ].compact).html_safe, + aria: { + labelledby: label_id, + describedby: description_id + }, + class: [ + options[:centered] ? 'centered' : nil, + options[:right_help] ? 'right-help' : nil + ].compact + ) + + html += help if help.present? && options[:right_help] + + return html.html_safe + end + + def button(value = nil, options = {}, &block) + if !block_given? && (value.nil? || value.is_a?(Symbol)) + return button_tag(I18n.t("forms.actions.generic.#{(value || :button)}"), options) + end + + button_tag(value, options, &block) + end + + def conference_days_options(conference = nil) + conference ||= @this_conference || @conference + return [] unless conference + + dates = [] + day = conference.start_date - 7.days + last_day = conference.end_date + 7.days + + while day <= last_day + dates << day + day += 1.day + end + + return dates + end + + def conference_days_options_list(period, conference = nil, format = nil) + conference ||= @this_conference || @conference + return [] unless conference + + days = [] + + conference_days_options(conference).each do |day| + belongs_to_periods = [] + belongs_to_periods << :before if day <= conference.start_date + belongs_to_periods << :after if day >= conference.end_date + belongs_to_periods << :before_plus_one if day <= (conference.start_date + 1.day) + belongs_to_periods << :after_minus_one if day >= (conference.end_date - 1.day) + belongs_to_periods << :during if day >= conference.start_date && day <= conference.end_date + days << [date(day.to_date, format || :span_same_year_date_1), day.to_date] if belongs_to_periods.include?(period) + end + return days + end + + def registration_status_options_list(conference = nil) + conference ||= @this_conference || @conference + return [] unless conference + + options = Array.new + [:closed, :pre, :open].each do | opt | + options << [(_"forms.labels.generic.registration_statuses.#{opt}"), opt] + end + + return options + end + + def month_select(value = nil, args = {}) + options = (1..12).to_a.map { |month| [ (I18n.t "date.#{args[:format] || 'month_names'}")[month], month ] } + selectfield args[:name] || :month, value, options, args + end + + def month_day_select(value = nil, args = {}) + options = (1..31).to_a.map { |day| [ day, day ] } + selectfield args[:name] || :month_day, value, options, args + end + + def day_select(value = nil, args = {}) + selectfield :day, value, conference_days_options_list(:during, nil, args[:format]), args + end + + def hour_select(value = nil, args = {}, start_time = 8, end_time = 23.5, step = 0.5) + time = start_time + times = [] + while time <= end_time + times << [time(DateTime.now.midnight + time.hours), time] + time += step + end + selectfield :time, value, times, args + end + + def length_select(value = nil, args = {}, min_length = 0.5, max_length = 6, step = 0.5) + length = min_length + lengths = [] + while length <= max_length + lengths << [time_length(length), length] + length += step + end + selectfield :time_span, value, lengths, args + end + + def contact_reason_select + reasons = [] + [:website, :conference].each do | reason | + reasons << [ _("forms.labels.generic.reasons.#{reason.to_s}"), reason ] + end + [['Something about the website', :website]] + selectfield :reason, nil, reasons, required: true, heading: 'articles.contact.headings.reason', label: false, full: true + end + + def block_select(value = nil, args = {}) + blocks = {} + @workshop_blocks.each_with_index do | info, block | + info['days'].each do | day | + blocks[(day.to_i * 10) + block] = [ "#{(I18n.t 'date.day_names')[day.to_i]} Block #{block + 1}", "#{day}:#{block}" ] + end + end + selectfield :workshop_block, value, blocks.sort.to_h.values, args + end + + def location_select(value = nil, args = {}) + locations = [] + if @this_conference.event_locations.present? + @this_conference.event_locations.each do | location | + locations << [ location.title, location.id ] unless ((args[:invalid_locations] || []).include? location.id) + end + end + selectfield :event_location, value, locations, args + end + + def location_name(id) + begin + location = EventLocation.find(id) + rescue + return '' + end + return '' unless location.present? + return location.title + end + + def host_options_list(hosts) + options = [[nil, nil]] + hosts.each do | id, registration | + options << [registration.user.name, id] + end + return options + end + + def registration_step_menu + steps = current_registration_steps(@registration) + return '' unless steps.present? && steps.length > 1 + + pre_registration_steps = '' + post_registration_steps = '' + post_registration = false + + steps.each do | step | + text = _"articles.conference_registration.headings.#{step[:name].to_s}" + + if step[:name] == :workshops + post_registration = true + end + + h = content_tag :li, class: [step[:enabled] ? :enabled : nil, @register_template == step[:name] ? :current : nil, post_registration ? :post : :pre].compact do + if step[:enabled] + content_tag :div, (link_to text, register_step_path(@this_conference.slug, step[:name])).html_safe, class: :step + else + content_tag :div, text, class: :step + end + end + + if post_registration + post_registration_steps += h.html_safe + else + pre_registration_steps += h.html_safe + end + end + + html = ( + row class: 'flow-steps' do + columns do + (content_tag :ul, id: 'registration-steps' do + pre_registration_steps.html_safe + + post_registration_steps.html_safe + end).html_safe + end + end + ) + + return html.html_safe + end + + def broadcast_options(conference = nil) + conference ||= @this_conference || @conference + + options = [ + :registered, + :pre_registered, + :workshop_facilitators, + :unregistered, + :housing_providers, + :guests, + :all + ] + + if conference.registration_status != :open + options -= [:registered, :guests] + options -= [:pre_registered] unless conference.registration_status != :pre + end + + return options + end + + def show_errors(field, value = nil) + return '' unless @errors && @errors[field].present? + + error_txt = _"errors.messages.fields.#{field.to_s}.#{@errors[field]}", :s, vars: { value: value } + + content_tag(:div, error_txt, class: 'field-error').html_safe + end + + private + def _original_content(value, lang) + content_tag(:div, ( + content_tag(:h4, _('translate.content.Translation_of')) + + content_tag(:div, value, class: 'value', lang: lang) + ).html_safe, class: 'original-text') + end +end diff --git a/app/helpers/geocoder_helper.rb b/app/helpers/geocoder_helper.rb new file mode 100644 index 0000000..6f5d29a --- /dev/null +++ b/app/helpers/geocoder_helper.rb @@ -0,0 +1,100 @@ +module GeocoderHelper + def lookup_ip + if request.remote_ip == '127.0.0.1' || request.remote_ip == '::1' + session['remote_ip'] || (session['remote_ip'] = open("http://checkip.dyndns.org").first.gsub(/^.*\s([\d\.]+).*$/s, '\1').gsub(/[^\.\d]/, '')) + else + request.remote_ip + end + end + + def get_remote_location + Geocoder.search(session['remote_ip'] || (session['remote_ip'] = open("http://checkip.dyndns.org").first.gsub(/^.*\s([\d\.]+).*$/s, '\1').gsub(/[^\.\d]/, '')), language: 'en').first + end + + def lookup_ip_location + begin + if is_test? && ApplicationController::get_location.present? + Geocoder.search(ApplicationController::get_location, language: 'en').first + elsif request.remote_ip == '127.0.0.1' || request.remote_ip == '::1' + get_remote_location + else + request.location || get_remote_location + end + rescue + nil + end + end + + def potential_provider(registration) + return false unless registration.present? && registration.city.present? && registration.conference.present? + conditions = registration.conference.provider_conditions || + Conference.default_provider_conditions + return city_distance_less_than(registration.conference.city, registration.city, + conditions['distance']['number'], conditions['distance']['unit']) + end + + def city_distance_less_than(city1, city2, max_distance, unit) + return false if city1.nil? || city2.nil? + return true if city1.id == city2.id + return false if max_distance < 1 + return Geocoder::Calculations.distance_between( + [city1.latitude, city1.longitude], [city2.latitude, city2.longitude], + units: unit.to_sym) < max_distance + end + + def location(location, locale = I18n.locale) + return nil if location.blank? + + city = nil + region = nil + country = nil + if location.is_a?(Location) || location.is_a?(City) + country = location.country + region = location.territory + city = location.city + elsif location.data.present? && location.data['address_components'].present? + component_map = { + 'locality' => :city, + 'administrative_area_level_1' => :region, + 'country' => :country + } + location.data['address_components'].each do | component | + types = component['types'] + country = component['short_name'] if types.include? 'country' + region = component['short_name'] if types.include? 'administrative_area_level_1' + city = component['long_name'] if types.include? 'locality' + end + else + country = location.data['country_code'] + region = location.data['region_code'] + city = location.data['city'] + end + + # we need cities for our logic, don't let this continue if we don't have one + return nil unless city.present? + + hash = Hash.new + region_translation = region.present? && country.present? ? _("geography.subregions.#{country}.#{region}", locale: locale) : '' + country_translation = country.present? ? _("geography.countries.#{country}", locale: locale) : '' + hash[:city] = _!(city) if city.present? + hash[:region] = region_translation if region_translation.present? + hash[:country] = country_translation if country_translation.present? + + # return the formatted location or the first value if we only have one value + return hash.length > 1 ? _("geography.formats.#{hash.keys.join('_')}", locale: locale, vars: hash) : hash.values.first + end + + def location_link(location) + return '' unless location.present? && location.address.present? + content_tag(:a, (_!location.address), href: "http://www.google.com/maps/place/#{location.latitude},#{location.longitude}") + end + + def same_city?(location1, location2) + return false unless location1.present? && location2.present? + + location1 = location(location1) unless location1.is_a?(String) + location2 = location(location2) unless location2.is_a?(String) + + location1.eql? location2 + end +end diff --git a/app/helpers/i18n_helper.rb b/app/helpers/i18n_helper.rb new file mode 100644 index 0000000..1b463dd --- /dev/null +++ b/app/helpers/i18n_helper.rb @@ -0,0 +1,74 @@ + +module I18nHelper + def url_for_locale(locale, url = nil) + return url unless locale.present? + + unless url.present? + new_params = params.merge({action: (params[:_original_action] || params[:action])}) + new_params.delete(:_original_action) + + if Rails.env.development? || Rails.env.test? + return url_for(new_params.merge({lang: locale.to_s})) + end + + subdomain = Rails.env.preview? ? "preview-#{locale.to_s}" : locale.to_s + return url_for(new_params.merge(host: "#{subdomain}.bikebike.org")) + end + + return url if Rails.env.development? || Rails.env.test? + return "https://preview-#{locale.to_s}.bikebike.org#{url}" if Rails.env.preview? + "https://#{locale.to_s}.bikebike.org#{url}" + end + + def date(date, format = :long) + I18n.l(date.is_a?(String) ? Date.parse(date) : date, :format => format) + end + + def time(time, format = :short) + if time.is_a?(String) + time = Date.parse(time) + elsif time.is_a?(Float) || time.is_a?(Integer) + time = DateTime.now.midnight + time.hours + end + + I18n.l(time, format: format) + end + + def date_span(date1, date2) + key = 'same_month' + if date1.year != date2.year + key = 'different_year' + elsif date1.month != date2.month + key = 'same_year' + end + d1 = I18n.l(date1.to_date, format: "span_#{key}_date_1".to_sym) + d2 = I18n.l(date2.to_date, format: "span_#{key}_date_2".to_sym) + _('date.date_span', vars: {:date_1 => d1, :date_2 => d2}) + end + + def time_length(length) + hours = length.to_i + minutes = ((length - hours) * 60).to_i + hours = hours > 0 ? (I18n.t 'datetime.distance_in_words.x_hours', count: hours) : nil + minutes = minutes > 0 ? (I18n.t 'datetime.distance_in_words.x_minutes', count: minutes) : nil + return hours.present? ? (minutes.present? ? (I18n.t 'datetime.distance_in_words.x_and_y', x: hours, y: minutes) : hours) : minutes + end + + def hour_span(time1, time2) + (time2 - time1) / 3600 + end + + def hours(time1, time2) + time_length hour_span(time1, time2) + end + + def money(amount) + return _!('$0.00') if amount == 0 + _!((amount * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '$\1.\2')) + end + + def percent(p) + return _!('0.00%') if p == 0 + _!((p * 10000).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2%')) + end +end diff --git a/app/helpers/page_helper.rb b/app/helpers/page_helper.rb new file mode 100644 index 0000000..a31ebe4 --- /dev/null +++ b/app/helpers/page_helper.rb @@ -0,0 +1,88 @@ + +module PageHelper + @@no_banner = true + @@banner_image = nil + @@has_content = true + @@body_class = nil + + def title(page_title) + content_for(:title) { page_title.to_s } + end + + def description(page_description) + content_for(:description) { page_description.to_s } + end + + def banner_image(banner_image, name: nil, id: nil, user_id: nil, src: nil) + @@no_banner = false + @@banner_image = banner_image + content_for(:banner_image) { banner_image.to_s } + end + + def has_content? + @@has_content + end + + def add_stylesheet(sheet) + @stylesheets ||= [] + @stylesheets << sheet unless @stylesheets.include?(sheet) + end + + def stylesheets + html = '' + Rack::MiniProfiler.step('inject_css') do + html += inject_css! + end + (@stylesheets || []).each do |css| + Rack::MiniProfiler.step("inject_css #{css}") do + html += inject_css! css.to_s + end + end + html += stylesheet_link_tag 'i18n-debug' if request.params['i18nDebug'] + return html.html_safe + end + + def add_inline_script(script) + @_inline_scripts ||= [] + script = Rails.application.assets.find_asset("#{script.to_s}.js").to_s + @_inline_scripts << script unless @_inline_scripts.include?(script) + end + + def inline_scripts + return '' unless @_inline_scripts.present? + "".html_safe + end + + def dom_ready(&block) + content_for(:dom_ready, &block) + end + + def body_class(c) + @@body_class ||= Array.new + @@body_class << (c.is_a?(Array) ? c.join(' ') : c) + end + + def page_style + classes = Array.new + + classes << 'no-content' unless @@has_content + classes << 'has-banner-image' if @@banner_image + classes << @@body_class.join(' ') if @@body_class + + if params[:controller] + classes << params[:action] + unless params[:controller] == 'application' + classes << params[:controller] + + if params[:action] + classes << "#{params[:controller]}-#{params[:action]}" + end + end + end + return classes + end + + def yield_or_default(section, default = '') + content_for?(section) ? content_for(section) : default + end +end diff --git a/app/helpers/registration_helper.rb b/app/helpers/registration_helper.rb index c5ec3fa..0c81ba4 100644 --- a/app/helpers/registration_helper.rb +++ b/app/helpers/registration_helper.rb @@ -1,4 +1,16 @@ module RegistrationHelper + def registration_steps(conference = @conference) + { + pre: [:policy, :contact_info, :workshops], + open: [:policy, :contact_info, :questions, :hosting, :payment, :workshops] + }[@this_conference.registration_status] + end + + def registration_status(registration) + return :unregistered if registration.nil? + return registration.status + end + def current_registration_steps(registration = @registration) return nil unless registration.present? @@ -6,32 +18,36 @@ module RegistrationHelper current_steps = [] disable_steps = false completed_steps = registration.steps_completed || [] - # registration_complete = registration_complete?(registration) if potential_provider(registration) steps -= [:questions] else steps -= [:hosting] end + + steps -= [:payment] unless registration.conference.paypal_email_address.present? && registration.conference.paypal_username.present? && registration.conference.paypal_password.present? && registration.conference.paypal_signature.present? + steps -= [:payment, :workshops] if registration.is_attending == 'n' - steps.each do | step | + steps.each do |step| # disable the step if we've already found an incomplete step - enabled = !disable_steps# || registration_complete + # enabled = !disable_steps# || registration_complete # record whether or not we've found an incomplete step - disable_steps ||= !completed_steps.include?(step.to_s) && ![:payment, :workshops].include?(step) current_steps << { name: step, - enabled: enabled + enabled: !disable_steps } + disable_steps ||= !completed_steps.include?(step.to_s)# && ![:payment, :workshops].include?(step) end + return current_steps end def current_step(registration = @registration) completed_steps = registration.steps_completed || [] last_step = nil - (current_registration_steps(registration) || []).each do | step | + steps = current_registration_steps(registration) || [] + steps.each do | step | # return the last enabled step if this one is disabled return last_step unless step[:enabled] @@ -43,6 +59,6 @@ module RegistrationHelper end # if all else fails, return the first step - return registration_steps(registration.conference).last + return steps.last[:name] end end diff --git a/app/helpers/table_helper.rb b/app/helpers/table_helper.rb new file mode 100644 index 0000000..4b20d3c --- /dev/null +++ b/app/helpers/table_helper.rb @@ -0,0 +1,366 @@ +module TableHelper + def html_edit_table(excel_data, options = {}) + attributes = { class: options[:class], id: options[:id] } + attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? + + if options[:column_names].is_a? Hash + return content_tag(:table, attributes) do + max_columns = 0 + column_names = {} + (content_tag(:thead) do + headers = '' + options[:column_names].each do | header_name, columns | + column_names[header_name] ||= [] + headers += content_tag(:th, excel_data[:keys][header_name].present? ? _(excel_data[:keys][header_name]) : '', colspan: 2) + row_count = columns.size + columns.each do | column | + column_names[header_name] << column + if (options[:row_spans] || {})[column].present? + row_count += (options[:row_spans][column] - 1) + for i in 1...options[:row_spans][column] + column_names[header_name] << false + end + end + end + max_columns = row_count if row_count > max_columns + end + content_tag(:tr, headers.html_safe) + end) + (content_tag(:tbody) do + rows = '' + + for i in 0...max_columns + columns_html = '' + column_names.each do | header_name, columns | + column = columns[i] + if column.present? + attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } + if (options[:row_spans] || {})[column].present? + attributes[:rowspan] = options[:row_spans][column] + end + columns_html += content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '', rowspan: attributes[:rowspan]) + + edit_column(nil, column, nil, attributes, excel_data, options) + elsif column != false + columns_html += content_tag(:td, ' ', colspan: 2, class: :empty) + end + end + rows += content_tag(:tr, columns_html.html_safe, { class: 'always-edit', data: { key: '' } }) + end + rows.html_safe + end) + end + else + return content_tag(:table, attributes) do + (content_tag(:tbody) do + rows = '' + excel_data[:columns].each do |column| + if (excel_data[:column_types] || {})[column] != :table && ((options[:column_names] || []).include? column) + rows += content_tag(:tr, { class: 'always-edit', data: { key: '' } }) do + attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } + columns = content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '') + + edit_column(nil, column, nil, attributes, excel_data, options) + end + end + end + rows.html_safe + end) + end + end + end + + def html_table(excel_data, options = {}) + options[:html] = true + attributes = { class: options[:class], id: options[:id] } + attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? + content_tag(:table, attributes) do + (content_tag(:thead) do + content_tag(:tr, excel_header_columns(excel_data)) + end) + + content_tag(:tbody, excel_rows(excel_data, {}, options)) + end + end + + def excel_table(excel_data) + format_xls 'table' do + workbook use_autowidth: true + format bg_color: '333333' + format 'td', font_name: 'Calibri', fg_color: '333333' + format 'th', font_name: 'Calibri', b: true, bg_color: '333333', fg_color: 'ffffff' + format 'th.sub-table', font_name: 'Calibri', b: true, bg_color: 'DDDDDD', fg_color: '333333' + format 'td.datetime', num_fmt: 22, font_name: 'Courier New', sz: 10, fg_color: '333333' + format 'td.date.day', num_fmt: 14, font_name: 'Courier New', sz: 10, fg_color: '333333' + format 'td.money', num_fmt: 2, font_name: 'Courier New', sz: 10, fg_color: '333333' + format 'td.number', font_name: 'Courier New', sz: 10, fg_color: '333333' + format 'td.bold', font_name: 'Calibri', fg_color: '333333', b: true + end + + content_tag(:table) do + (content_tag(:thead) do + content_tag(:tr, excel_header_columns(excel_data)) + end) + + content_tag(:tbody, excel_rows(excel_data)) + end + end + + def excel_header_columns(data, padding = {}, class_name = nil) + columns = '' + + data[:columns].each do |column| + unless data[:column_types].present? && data[:column_types][column] == :table + columns += content_tag(:th, data[:keys][column].present? ? _(data[:keys][column]) : '', class: class_name) + end + end + + pad_columns(columns, padding, :th) + end + + def excel_empty_row(data, padding = {}) + columns = '' + + data[:columns].each do |column| + unless data[:column_types].present? && data[:column_types][column] == :table + columns += content_tag(:td) + end + end + + content_tag(:tr, pad_columns(columns, padding)) + end + + def pad_columns(columns, padding, column_type = :td) + left = '' + + for i in 1..(padding['left'] || 0) + left += content_tag(:td) + end + + right = '' + for i in 1..(padding['right'] || 0) + right += content_tag(:td) + end + + (left + columns + right).html_safe + end + + def excel_columns(row, data, padding = {}, options = {}) + columns = '' + + data[:columns].each do |column| + value = row[column].present? ? (_!row[column].to_s) : '' + class_name = nil + is_sub_table = false + + if data[:column_types].present? && data[:column_types][column].present? + if data[:column_types][column] == :table + is_sub_table = true + else + class_name = data[:column_types][column] + end + end + + unless is_sub_table + attributes = { class: [class_name] } + if options[:html] && row[:html_values].present? && row[:html_values][column].present? + value = row[:html_values][column] + end + + if options[:editable] + attributes[:data] = { 'column-id' => column } + end + + if (options[:column_names] || []).include? column + attributes[:tabindex] = 0 + end + + columns += content_tag(:td, value, attributes) + end + end + + pad_columns(columns, padding) + end + + def editor_columns(row, data, padding = {}, options = {}) + columns = '' + + data[:columns].each do |column| + value = row[column].present? ? (_!row[column].to_s) : '' + class_name = nil + is_sub_table = false + + if data[:column_types].present? && data[:column_types][column].present? + if data[:column_types][column] == :table + is_sub_table = true + else + class_name = data[:column_types][column] + end + end + + unless is_sub_table + attributes = { class: [class_name] } + + if options[:editable] + attributes[:data] = { 'column-id' => column } + end + + if (options[:column_names] || []).include? column + columns += edit_column(row, column, value, attributes, data, options) + else + columns += content_tag(:td, value, attributes) + end + + end + end + + pad_columns(columns, padding) + end + + def edit_column(row, column, value, attributes, data, options) + attributes[:class] << 'has-editor' + raw_value = row.present? ? (row[:raw_values][column] || value) : nil + + if row.present? && options[:html] && row[:html_values].present? && row[:html_values][column].present? + value = row[:html_values][column] + end + + editor_attributes = { class: 'cell-editor', data: { value: raw_value.to_s } } + + # create the control but add the original value to set the width and height + editor_value = content_tag(:div, value, class: 'value') + if (options[:column_options] || {})[column].present? + value = (editor_value.html_safe + select_tag(column, options_for_select([['', '']] + options[:column_options][column], raw_value), editor_attributes)).html_safe + elsif data[:column_types][column] == :text + editor_attributes[:name] = column + value = (editor_value.html_safe + content_tag(:textarea, raw_value, editor_attributes)).html_safe + else + editor_attributes[:name] = column + editor_attributes[:value] = raw_value + editor_attributes[:required] = :required if (options[:required_columns] || []).include? column + type = data[:column_types][column] || :unknown + editor_attributes[:type] = { money: :number, number: :number, email: :email }[type] || :text + value = (editor_value.html_safe + content_tag(:input, nil, editor_attributes)).html_safe + end + + return content_tag(:td, value, attributes) + end + + def excel_sub_tables(row, data, padding = {}, options = {}) + rows = '' + + # shift the table right + new_padding = { + 'left' => (padding['right'] || 0) + 1, + 'right' => (padding['right'] || 0) - 1 + } + + data[:columns].each do |column| + if data[:column_types].present? && data[:column_types][column] == :table + rows += content_tag(:tr, excel_header_columns(row[column], new_padding, 'sub-table')) + rows += excel_rows(row[column], new_padding) + rows += excel_empty_row(row[column], new_padding) + end + + end + + rows.html_safe + end + + def excel_rows(data, padding = {}, options = {}) + rows = '' + data[:data].each do |row| + attributes = {} + + if options[:primary_key].present? + attributes[:data] = { key: row[options[:primary_key]] } + end + + attributes[:class] = [] + + if options[:editable] + attributes[:class] << :editable + end + + rows += content_tag(:tr, excel_columns(row, data, padding, options), attributes) + + excel_sub_tables(row, data, padding) + rows += content_tag(:tr, editor_columns(row, data, padding, options), class: :editor) if options[:editable] + end + rows.html_safe + end + + def registrations_edit_table_options + { + id: 'create-table', + class: ['registrations', 'admin-edit', 'always-editing'], + primary_key: :id, + column_names: { + contact_info: [ + :name, + :email, + :is_subscribed, + :city, + :preferred_language + ] + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym }, + questions: [ + :registration_fees_paid, + :is_attending, + :arrival, + :departure, + :housing, + :bike, + :food, + :companion_email, + :allergies, + :other + ], + hosting: [ + :can_provide_housing, + :address, + :phone, + :first_day, + :last_day + ] + ConferenceRegistration.all_spaces + + ConferenceRegistration.all_considerations + [ + :notes + ] + }, + row_spans: { + allergies: 3, + other: 2 + }, + required_columns: [:name, :email], + editable: administration_update_path(@this_conference.slug, @admin_step), + column_options: @column_options + } + end + + def registrations_table_options + { + id: 'search-table', + class: ['registrations', 'admin-edit'], + primary_key: :id, + column_names: [ + :registration_fees_paid, + :is_attending, + :is_subscribed, + :city, + :preferred_language, + :arrival, + :departure, + :housing, + :bike, + :food, + :companion_email, + :allergies, + :other, + :can_provide_housing, + :address, + :phone, + :first_day, + :last_day, + :notes + ] + + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym } + + ConferenceRegistration.all_spaces + + ConferenceRegistration.all_considerations, + editable: administration_update_path(@this_conference.slug, @admin_step), + column_options: @column_options + } + end +end diff --git a/app/helpers/widgets_helper.rb b/app/helpers/widgets_helper.rb new file mode 100644 index 0000000..9f7bb35 --- /dev/null +++ b/app/helpers/widgets_helper.rb @@ -0,0 +1,288 @@ +require 'redcarpet' + +module WidgetsHelper + + def m(*args) + _(*args) { |t| + markdown(t) + } + end + + def markdown(object, attribute = nil) + return '' unless object + content = attribute ? object.send(attribute.to_s) : object + @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML.new({ + filter_html: true, + hard_wrap: true, + space_after_headers: true, + fenced_code_blocks: true, + link_attributes: { target: "_blank" } + }), { + autolink: true, + disable_indented_code_blocks: true, + superscript: true + }) + @markdown.render(content).html_safe + end + + def paragraph(object, attribute = nil) + return '' unless object + content = attribute ? object.send(attribute.to_s) : object + result = '' + if content =~ /<(p|span|h\d|div)[^>]*>/ + result = content.gsub(/\s*(style|class|id|width|height|font)=\".*?\"/, '') + .gsub(/ /, ' ') + .gsub(/<(\/)?\s*h\d\s*>/, '<\1h3>') + .gsub(/

    (.*?)\s*()+/, '

    \1

    ') + .gsub(/]*>\s*(.*?)\s*<\/span>/, '\1') + .gsub(/

    \s*<\/p>/, '') + .gsub(/<(\/)?div>/, '<\1p>') + if !(result =~ /]*>/) + result = '

    ' + result + '

    ' + end + else + result = markdown(object, attribute) + end + result.html_safe + end + + def nav_link(link, title = nil, class_name = nil) + if title.nil? && link.is_a?(Symbol) + title = link + link = send("#{link.to_s}_path") + end + if class_name.nil? && title.is_a?(Symbol) + class_name = title + end + title = _"page_titles.#{title.to_s.titlecase.gsub(/\s/, '_')}" + classes = [] + classes << class_name if class_name.present? + classes << "strlen-#{strip_tags(title).length}" + classes << 'current' if request.fullpath.start_with?(link.gsub(/^(.*?)\/$/, '\1')) + link_to "#{title}".html_safe, link, :class => classes + end + + def data_set(header_type, header_key, attributes = {}, &block) + raw_data_set(header_type, _(header_key), attributes, &block) + end + + def raw_data_set(header_type, header, attributes = {}, &block) + attributes[:class] = attributes[:class].split(' ') if attributes[:class].is_a?(String) + attributes[:class] = [attributes[:class].to_s] if attributes[:class].is_a?(Symbol) + attributes[:class] ||= [] + attributes[:class] << 'data-set' + content_tag(:div, attributes) do + content_tag(header_type, header, class: 'data-set-key') + + content_tag(:div, class: 'data-set-value', &block) + end + end + + def admin_update_form(options = {}, &block) + form_tag(administration_update_path(@this_conference.slug, @admin_step), options, &block) + end + + def interest_button(workshop) + interested = workshop.interested?(current_user) ? :remove_interest : :show_interest + id = "#{interested.to_s.gsub('_', '-')}-#{workshop.id}" + return (off_screen (_"forms.actions.aria.#{interested.to_s}"), id) + + (button interested, :value => :toggle_interest, :class => (workshop.interested?(current_user) ? :delete : :add), aria: { labelledby: id }) + end + + def interest_text(workshop) + if workshop.interested?(current_user) + return _'articles.workshops.info.you_are_interested_count', :vars => {:count => (workshop.interested_count - 1)} + end + + return _'articles.workshops.info.interested_count', :vars => {:count => workshop.interested_count} + end + + def host_guests_table(registration) + id = registration.id + html = '' + + @housing_data[id][:guests].each do | area, guests | + guest_rows = '' + guests.each do | guest_id, guest | + status_html = '' + + @housing_data[id][:guest_data][guest_id][:errors].each do | error, value | + if value.is_a?(Array) + value.each do | v | + status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: v)) + end + else + status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: value)) + end + end + + @housing_data[id][:guest_data][guest_id][:warnings].each do | error, value | + if value.is_a?(Array) + value.each do | v | + status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", v)) + end + else + status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", vars: value)) + end + end + + if status_html.present? + status_html = content_tag(:ul, status_html.html_safe) + end + + guest_rows += content_tag :tr, id: "hosted-guest-#{guest_id}" do + (content_tag :td, guest[:guest].user.name) + + (content_tag :td do + (guest[:guest].city + + (content_tag :a, (_'actions.workshops.Remove'), href: '#', class: 'remove-guest', data: { guest: guest_id })).html_safe + end) + + (content_tag :td, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy]) + end + end + + space_size = (@housing_data[id][:space][area] || 0) + + # add empty rows to represent empty guest spots + for i in guests.size...space_size + guest_rows += content_tag :tr, class: 'empty-space' do + (content_tag :td, ' '.html_safe, colspan: 2) + + (content_tag :td) + end + end + + status_html = '' + if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? + @housing_data[id][:warnings][:space][area].each do | w | + status_html += content_tag(:li, _("warnings.messages.housing.space.#{w.to_s}")) + end + end + if status_html.present? + status_html = content_tag(:ul, status_html.html_safe) + end + + html += content_tag :tr do + (content_tag :th, (_"forms.labels.generic.#{area}"), colspan: 2) + + (content_tag :th, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy]) + end + html += guest_rows + html += content_tag :tr, class: 'place-guest' do + content_tag :td, class: guests.size >= space_size ? 'full' : nil, colspan: 3 do + content_tag :a, (_'forms.actions.generic.place_guest'), class: 'select-guest', href: '#', data: { host: id, space: area } + end + end + end + + content_tag :table, html.html_safe, class: 'host-table' + end + + def host_guests_widget(registration) + html = '' + classes = ['host'] + + id = registration.id + @housing_data[id][:guests].each do | area, guests | + max_space = @housing_data[id][:space][area] || 0 + area_name = (_"forms.labels.generic.#{area}") + status_html = '' + if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? + @housing_data[id][:warnings][:space][area].each do | w | + status_html += content_tag(:div, _("warnings.housing.space.#{w.to_s}"), class: 'warning') + end + end + space_html = content_tag(:h5, area_name + _!(" (#{guests.size.to_s}/#{max_space.to_s})") + status_html.html_safe) + guest_items = '' + guests.each do | guest_id, guest | + guest_items += content_tag(:li, guest[:guest].user.name, id: "hosted-guest-#{guest_id}") + end + space_html += content_tag(:ul, guest_items.html_safe) + space_html += button :place_guest, type: :button, value: "#{area}:#{id}", class: [:small, 'place-guest', 'on-top-only', guests.size >= max_space ? (guests.size > max_space ? :overbooked : :booked) : nil, max_space > 0 ? nil : :unwanted] + html += content_tag(:div, space_html, class: [:space, area, max_space > 0 || guests.size > 0 ? nil : 'on-top-only']) + end + + classes << 'status-warning' if @housing_data[id][:warnings].present? + classes << 'status-error' if @housing_data[id][:errors].present? + + return { html: html.html_safe, class: classes.join(' ') } + end + + def signin_link + @login_dlg ||= true + link_to (_'forms.actions.generic.login'), settings_path, data: { 'sign-in': true } + end + + def link_with_confirmation(link_text, confirmation_text, path, args = {}) + @confirmation_dlg ||= true + args[:data] ||= {} + args[:data][:confirmation] = true + link_to path, args do + (link_text.to_s + content_tag(:template, confirmation_text, class: 'message')).html_safe + end + end + + def link_info_dlg(link_text, info_text, info_title, args = {}) + @info_dlg ||= true + args[:data] ||= {} + args[:data]['info-title'] = info_title + args[:data]['info-text'] = true + content_tag(:a, args) do + (link_text.to_s + content_tag(:template, info_text, class: 'message')).html_safe + end + end + + def button_with_confirmation(button_name, confirmation_text = nil, args = {}) + if confirmation_text.is_a? Hash + args = confirmation_text + confirmation_text = nil + end + + confirmation_text ||= (_"forms.confirmations.#{button_name.to_s}", :p) + @confirmation_dlg ||= true + args[:data] ||= {} + args[:data][:confirmation] = true + button button_name, args do + ((_"forms.actions.generic.#{button_name.to_s}") + content_tag(:template, confirmation_text, class: 'message')).html_safe + end + end + + def richtext(text, reduce_headings = 2) + return '' unless text.present? + return _!(text). + gsub(/<(\/?)h4>/, '<\1h' + (reduce_headings + 4).to_s + '>'). + gsub(/<(\/?)h3>/, '<\1h' + (reduce_headings + 3).to_s + '>'). + gsub(/<(\/?)h2>/, '<\1h' + (reduce_headings + 2).to_s + '>'). + gsub(/<(\/?)h1>/, '<\1h' + (reduce_headings + 1).to_s + '>'). + html_safe + end + + def truncate(text) + strip_tags(text.gsub('>', '> ')).gsub(/^(.{40,60})\s.*$/m, '\1…').html_safe + end + + def companion(registration) + if registration.housing_data.present? && registration.housing_data['companions'].present? && registration.housing_data['companions'].first.present? + companion_user = User.find_user(registration.housing_data['companions'].first) + + if companion_user.present? + cr = ConferenceRegistration.where(user_id: companion_user.id).order(created_at: :desc).limit(1).first + + if cr.present? && ((cr.steps_completed || []).include? 'questions') + return companion_user + end + end + return :unregistered + end + return nil + end + + def comment(comment) + add_inline_script :time + add_js_translation('datetime.distance_in_words') + + content_tag(:div, class: 'comment-body') do + content_tag(:h4, _!(comment.user.name), class: 'comment-title') + + content_tag(:time, time(comment.created_at, :default), datetime: comment.created_at.to_s) + + content_tag(:div, class: 'comment-text') do + _!(markdown comment.comment) + end + end + end +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index d01221a..bdd9ea2 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -1,12 +1,12 @@ require 'diffy' class UserMailer < ActionMailer::Base - add_template_helper(ApplicationHelper) include LinguaFrancaHelper + add_template_helper(ApplicationHelper) before_filter :set_host - default from: "Bike!Bike! " + default from: "Bike!Bike! " def email_confirmation(confirmation) @confirmation = EmailConfirmation.find(confirmation) if confirmation.present? diff --git a/app/models/authentication.rb b/app/models/authentication.rb deleted file mode 100644 index 69a2df1..0000000 --- a/app/models/authentication.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Authentication < ActiveRecord::Base - belongs_to :user -end diff --git a/app/models/city.rb b/app/models/city.rb deleted file mode 100644 index ed82ef8..0000000 --- a/app/models/city.rb +++ /dev/null @@ -1,166 +0,0 @@ -require 'geocoder' -require 'geocoder/railtie' -require 'geocoder/calculations' - -Geocoder::Railtie.insert - -class City < ActiveRecord::Base - geocoded_by :address - translates :city - - reverse_geocoded_by :latitude, :longitude, :address => :full_address - after_validation :geocode, if: ->(obj){ obj.country_changed? or obj.territory_changed? or obj.city_changed? or obj.latitude.blank? or obj.longitude.blank? } - - def address - ([city!, territory, country] - [nil, '']).join(', ') - end - - def get_translation(locale) - location = Geocoder.search(address, language: locale.to_s).first - - # if the service lets us down, return nil - return nil unless location.present? - - searched_component = false - location.data['address_components'].each do | component | - # city is usually labeled a 'locality' but sometimes this is missing and only 'colloquial_area' is present - if component['types'].first == 'locality' - return component['short_name'] - end - - if component['types'] == location.data['types'] - searched_component = component['short_name'] - end - end - - # return the type we searched for but it's still possible that it will be false - searched_component - end - - # this method will get called automatically if a translation is asked for but not found - def translate_city(locale) - translation = get_translation(locale) - - # if we found it, set it - if translation.present? - set_column_for_locale(:city, locale, translation) - save! - end - - return translation - end - - def to_s - ([ - city, - territory.present? && country.present? ? I18n.t("geography.subregions.#{country}.#{territory}") : '', - country.present? ? I18n.t("geography.countries.#{country}") : '' - ] - ['', nil]).join(', ') - end - - def self.search(str) - cache = CityCache.search(str) - - # return the city if this search is in our cache - return cache.city if cache.present? - - # look up the city in the geocoder - location = Geocoder.search(str, language: 'en').first - - # return nil to indicate that the service is down - return nil unless location.present? - # see if the city is already present in our database - city = City.find_by_place_id(location.data['place_id']) - - # if we didn't find a match by place id, collect the city, territory, and country from the result - unless city.present? - # google names things differently than we do, we'll look for these items - component_alises = { - 'locality' => :city, - 'administrative_area_level_1' => :territory, - 'country' => :country - } - - # and populate this map to eventually create the city if we need to - city_data = { - locale: :en, - latitude: location.data['geometry']['location']['lat'], - longitude: location.data['geometry']['location']['lng'], - place_id: location.data['place_id'] - } - - # these things are definitely not cities, make sure we don't think they're one - not_a_city = [ - 'administrative_area_level_1', - 'country', - 'street_address', - 'street_number', - 'postal_code', - 'postal_code_prefix', - 'route', - 'intersection', - 'premise', - 'subpremise', - 'natural_feature', - 'airport', - 'park', - 'point_of_interest', - 'bus_station', - 'train_station', - 'transit_station', - 'room', - 'post_box', - 'parking', - 'establishment', - 'floor' - ] - - searched_component = nil - location.data['address_components'].each do | component | - property = component_alises[component['types'].first] - city_data[property] = component['short_name'] if property.present? - - # ideally we will find the component that is labeled a locality but - # if that fails we will select what was searched for, hopefully they searched for a city - # and not an address or country - # some places are not labeled 'locality', search for 'Halifax NS' for example and you will - # get 'administrative_area_level_2' since Halifax is a municipality - if component['types'] == location.data['types'] && !not_a_city.include?(component['types'].first) - searched_component = component['short_name'] - end - end - - # fall back to the searched component - city_data[:city] ||= searched_component - - # we need to have the city and country at least - return false unless city_data[:city].present? && city_data[:country].present? - - # one last attempt to make sure we don't already have a record of this city - city = City.where(city: city_data[:city], territory: city_data[:territory], country: city_data[:country]).first - - # only if we still can't find the city, then save it as a new one - unless city.present? - city = City.new(city_data) - # if we found exactly what we were looking for, keep these location details - # otherwise we may have searched for 'The Bronx' and set the sity the 'New York' but these details will be about The Bronx - # so if we try to show New York on a map it will always point to The Bronx, not very fair to those from Staten Island - unless city_data[:city] == searched_component - new_location = Geocoder.search(str, language: 'en').first - city.latitude = new_location.data['geometry']['location']['lat'] - city.longitude = new_location.data['geometry']['location']['lng'] - city.place_id = new_location.data['place_id'] - end - - # and create the new city - city.save! - end - end - - # save this to our cache - CityCache.cache(str, city.id) - - # and return it - return city - end -end diff --git a/app/models/city_cache.rb b/app/models/city_cache.rb deleted file mode 100644 index 1e72f8d..0000000 --- a/app/models/city_cache.rb +++ /dev/null @@ -1,21 +0,0 @@ -class CityCache < ActiveRecord::Base - self.table_name = :city_cache - - belongs_to :city - - # look for a term to see if its already been searched for - def self.search(str) - CityCache.find_by_search(normalize_string(str)) - end - - # cache this search term - def self.cache(str, city_id) - CityCache.create(city_id: city_id, search: normalize_string(str)) - end - - private - def self.normalize_string(str) - # remove accents, unnecessary whitespace, punctuation, and lowcase tje string - I18n.transliterate(str).gsub(/[^\w\s]/, '').gsub(/\s\s+/, ' ').strip.downcase - end -end diff --git a/app/models/comment.rb b/app/models/comment.rb deleted file mode 100644 index a82e08c..0000000 --- a/app/models/comment.rb +++ /dev/null @@ -1,37 +0,0 @@ -class Comment < ActiveRecord::Base - belongs_to :user - - def comment_object - model_type.classify.constantize.find(model_id) - end - - def set_model(model) - model_type = model.class.name.tableize - model_id = model.id - end - - def self.for(model) - where(model_type: model.class.name.tableize, model_id: model.id).order(created_at: :asc) - end - - def self.create_for(model, user, comment) - create( - model_type: model.class.name.tableize, - model_id: model.id, - user_id: user.id, - comment: comment - ) - end - - def add_comment(user, comment) - Comment.create_for(self, user, comment) - end - - def comments - Comment.for(self) - end - - def reply? - model_type == 'comments' - end -end diff --git a/app/models/conference.rb b/app/models/conference.rb deleted file mode 100644 index 5bb10bc..0000000 --- a/app/models/conference.rb +++ /dev/null @@ -1,160 +0,0 @@ -class Conference < ActiveRecord::Base - translates :info, :title, :payment_message - - mount_uploader :cover, CoverUploader - mount_uploader :poster, PosterUploader - - belongs_to :conference_type - belongs_to :city - - has_many :conference_host_organizations, dependent: :destroy - has_many :organizations, through: :conference_host_organizations - has_many :conference_administrators, dependent: :destroy - has_many :administrators, through: :conference_administrators, source: :user - has_many :event_locations - - has_many :workshops - - accepts_nested_attributes_for :conference_host_organizations, reject_if: proc {|u| u[:organization_id].blank?}, allow_destroy: true - - before_create :make_slug, :make_title - - def to_param - slug - end - - def host_organization?(org) - return false unless org.present? - org_id = org.is_a?(Organization) ? org.id : org - - organizations.each do |o| - return true if o.id = org_id - end - - return false - end - - def host?(user) - if user.present? - return true if user.administrator? - - conference_administrators.each do |u| - return true if user.id == u.id - end - - organizations.each do |o| - return true if o.host?(user) - end - end - return false - end - - def url(action = :show) - path(action) - end - - def path(action = :show) - action = action.to_sym - '/conferences/' + conference_type.slug + '/' + slug + (action == :show ? '' : '/' + action.to_s) - end - - def location - return nil unless organizations.present? - organizations.first.location - end - - def registered?(user) - registration = ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id) - return registration ? registration.is_attending : false - end - - def registration_exists?(user) - ConferenceRegistration.find_by(:user_id => user.id, :conference_id => id).present? - end - - def registration_open - registration_status == :open - end - - def can_register? - registration_status == :open || registration_status == :pre - end - - def registration_status - s = read_attribute(:registration_status) - s.present? ? s.to_sym : nil - end - - def registration_status=(new_registration_status) - write_attribute :registration_status, new_registration_status.to_s - end - - def make_slug(reset = false) - if reset - self.slug = nil - end - - self.slug ||= Conference.generate_slug( - conferencetype || :annual, - conference_year, - city_name.gsub(/\s/, '') - ) - end - - def make_title(reset = false) - if reset - self.title = nil - end - - self.title ||= Conference.generate_title( - conferencetype || :annual, - conference_year, - city_name.gsub(/\s/, '') - ) - end - - def city_name - return city.city if city.present? - return location.present? ? location.city : nil - end - - def conference_year - self.year || (end_date.present? ? end_date.year : nil) - end - - def over? - return false unless end_date.present? - return end_date < DateTime.now - end - - def self.default_payment_amounts - [25, 50, 100] - end - - def self.conference_types - { - annual: { slug: '%{city}%{year}', title: 'Bike!Bike! %{year}'}, - n: { slug: 'North%{year}', title: 'Bike!Bike! North %{year}'}, - s: { slug: 'South%{year}', title: 'Bike!Bike! South %{year}'}, - e: { slug: 'East%{year}', title: 'Bike!Bike! East %{year}'}, - w: { slug: 'West%{year}', title: 'Bike!Bike! West %{year}'}, - ne: { slug: 'Northeast%{year}', title: 'Bike!Bike! Northeast %{year}'}, - nw: { slug: 'Northwest%{year}', title: 'Bike!Bike! Northwest %{year}'}, - se: { slug: 'Southeast%{year}', title: 'Bike!Bike! Southeast %{year}'}, - sw: { slug: 'Southwest%{year}', title: 'Bike!Bike! Southwest %{year}'} - } - end - - def self.generate_slug(type, year, city) - Conference.conference_types[(type || :annual).to_sym][:slug].gsub('%{city}', city).gsub('%{year}', year.to_s) - end - - def self.generate_title(type, year, city) - Conference.conference_types[(type || :annual).to_sym][:title].gsub('%{city}', city).gsub('%{year}', year.to_s) - end - - def self.default_provider_conditions - { 'distance' => { 'number' => 0, 'unit' => 'mi' }} - end - -end diff --git a/app/models/conference_admin.rb b/app/models/conference_admin.rb deleted file mode 100644 index 0762f7d..0000000 --- a/app/models/conference_admin.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ConferenceAdmin < ActiveRecord::Base -end diff --git a/app/models/conference_administrator.rb b/app/models/conference_administrator.rb deleted file mode 100644 index 2fd7823..0000000 --- a/app/models/conference_administrator.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ConferenceAdministrator < ActiveRecord::Base - belongs_to :user - belongs_to :conference -end diff --git a/app/models/conference_host_organization.rb b/app/models/conference_host_organization.rb deleted file mode 100644 index f9ab1a8..0000000 --- a/app/models/conference_host_organization.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ConferenceHostOrganization < ActiveRecord::Base - belongs_to :conference - belongs_to :organization -end diff --git a/app/models/conference_registration.rb b/app/models/conference_registration.rb deleted file mode 100644 index 8fbdb1c..0000000 --- a/app/models/conference_registration.rb +++ /dev/null @@ -1,74 +0,0 @@ -class ConferenceRegistration < ActiveRecord::Base - belongs_to :conference - belongs_to :user - has_many :conference_registration_responses - - AttendingOptions = [:yes, :no] - - def languages - user.languages - end - - def self.all_housing_options - [:none, :tent, :house] - end - - def self.all_spaces - [:bed_space, :floor_space, :tent_space] - end - - def self.all_bike_options - [:yes, :no] - end - - def self.all_food_options - [:meat, :vegetarian, :vegan] - end - - def self.all_considerations - [:vegan, :smoking, :pets, :quiet] - end - - def city - city_id.present? ? City.find(city_id) : nil - end - - def status(was = false) - # our user hasn't registered if their user doesn't exist or they haven't entered a city - return :unregistered if user.nil? || check(:city, was).blank? - - # registration completes once a guest has entered a housing preference or - # a housing provider has opted in or out of providing housing - return :preregistered unless - check(:housing, was).present? || !check(:can_provide_housing, was).nil? - - # they must be registered - return :registered - end - - around_update :check_status - - def check_status - yield - - old_status = status(true) - new_status = status - - if old_status.present? && old_status != new_status - if (conference.registration_status == :pre && new_status == :preregistered) || - (conference.registration_status == :open && new_status == :registered) - - UserMailer.send_mail :registration_confirmation do - { - :args => self - } - end - end - end - end - -private - def check(field, was) - send("#{field}#{was ? '_was' : ''}") - end -end diff --git a/app/models/conference_registration_form_field.rb b/app/models/conference_registration_form_field.rb deleted file mode 100644 index 2e400f5..0000000 --- a/app/models/conference_registration_form_field.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ConferenceRegistrationFormField < ActiveRecord::Base - belongs_to :conference - belongs_to :registration_form_field -end diff --git a/app/models/conference_registration_response.rb b/app/models/conference_registration_response.rb deleted file mode 100644 index d9dc8a0..0000000 --- a/app/models/conference_registration_response.rb +++ /dev/null @@ -1,5 +0,0 @@ -class ConferenceRegistrationResponse < ActiveRecord::Base - belongs_to :conference_registration - belongs_to :user - #belongs_to :conference, :through => :conference_registration -end diff --git a/app/models/conference_type.rb b/app/models/conference_type.rb deleted file mode 100644 index f8cfca0..0000000 --- a/app/models/conference_type.rb +++ /dev/null @@ -1,7 +0,0 @@ -class ConferenceType < ActiveRecord::Base - #belongs_to :conference - - def to_param - slug - end -end diff --git a/app/models/email_confirmation.rb b/app/models/email_confirmation.rb deleted file mode 100644 index 6ddb699..0000000 --- a/app/models/email_confirmation.rb +++ /dev/null @@ -1,40 +0,0 @@ -class EmailConfirmation < ActiveRecord::Base - belongs_to :user - before_create :prepare - - def prepare - # clean up any expired records - EmailConfirmation.delete_all(['expiry < ?', Time.now]) - - # fill in defaults - self.expiry ||= Time.now + 1.day - - while self.token.nil? do - # create a token based on the user id and current time - self.token = generate_token - - # conflicts should be extremely rare, but let's just be sure - if EmailConfirmation.exists?(:token => self.token) - self.token = nil # keep the loop going - # because we generate the token based on the time, just make sure - # some time has passed - sleep 0.1 - end - end - end - - def valid_for_user?(user) - user.id == user_id && !expired? - end - - def expired? - expiry >= Time.now - end - - protected - - def generate_token - Digest::SHA256.hexdigest(user_id.to_s + (Time.now.to_f * 1000000).to_i.to_s) - end - -end diff --git a/app/models/event.rb b/app/models/event.rb deleted file mode 100644 index cf4677f..0000000 --- a/app/models/event.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Event < ActiveRecord::Base - translates :info, :title - - belongs_to :conference - belongs_to :event_location - - def conference_day - return nil unless start_time.present? && end_time.present? - - start_day = conference.start_date.change(hour: 0, minute: 0, second: 0) - w_start_day = start_time.change(hour: 0, minute: 0, second: 0) - return (((w_start_day - start_day) / 86400) + 1).to_i - end - - def duration - return nil unless start_time.present? && end_time.present? - ((end_time - start_time) / 60).to_i - end - -end diff --git a/app/models/event_location.rb b/app/models/event_location.rb deleted file mode 100644 index bb09ffb..0000000 --- a/app/models/event_location.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'geocoder' -require 'geocoder/railtie' - -Geocoder::Railtie.insert - -class EventLocation < ActiveRecord::Base - belongs_to :conference - geocoded_by :full_address - - reverse_geocoded_by :latitude, :longitude, :address => :full_address - after_validation :geocode, if: ->(obj){ obj.address_changed? } - - def full_address - l = conference.location - [address, l.city, l.territory, l.country].join(', ') - end - - def self.all_spaces - Workshop.all_spaces + [:event_space] - end - - def self.all_amenities - Workshop.all_needs - end -end diff --git a/app/models/event_type.rb b/app/models/event_type.rb deleted file mode 100644 index 3c7329a..0000000 --- a/app/models/event_type.rb +++ /dev/null @@ -1,2 +0,0 @@ -class EventType < ActiveRecord::Base -end diff --git a/app/models/location.rb b/app/models/location.rb deleted file mode 100644 index 737fb50..0000000 --- a/app/models/location.rb +++ /dev/null @@ -1,21 +0,0 @@ -class Location < ActiveRecord::Base - #attr_accessible :title, :country, :territory, :city, :street, :postal_code, :latitude, :longitude - has_many :locations_organization - has_many :organizations, :through => :locations_organization - - geocoded_by :full_address - - reverse_geocoded_by :latitude, :longitude, :address => :full_address - after_validation :geocode, if: ->(obj){ obj.country_changed? or obj.territory_changed? or obj.city_changed? or obj.street_changed? or obj.postal_code_changed? or obj.latitude.blank? or obj.longitude.blank? } - - def full_address - addr = title - addr = (addr ? ', ' : '') + (street || '') - addr = (addr ? ', ' : '') + (city || '') - addr = (addr ? ', ' : '') + (territory || '') - addr = (addr ? ' ' : '') + (country || '') - addr = (addr ? ' ' : '') + (postal_code || '') - addr - end - -end diff --git a/app/models/locations_organization.rb b/app/models/locations_organization.rb deleted file mode 100644 index 60a2d25..0000000 --- a/app/models/locations_organization.rb +++ /dev/null @@ -1,6 +0,0 @@ -class LocationsOrganization < ActiveRecord::Base - belongs_to :location - belongs_to :organization - - self.primary_key = :location_id -end diff --git a/app/models/organization.rb b/app/models/organization.rb deleted file mode 100644 index 3b46249..0000000 --- a/app/models/organization.rb +++ /dev/null @@ -1,77 +0,0 @@ -class Organization < ActiveRecord::Base - mount_uploader :logo, LogoUploader - mount_uploader :avatar, AvatarUploader - mount_uploader :cover, CoverUploader - - has_many :locations_organization - has_many :locations, through: :locations_organization - - has_many :user_organization_relationships, dependent: :destroy - has_many :users, through: :user_organization_relationships - - accepts_nested_attributes_for :locations, :reject_if => proc {|l| l[id].blank?} - accepts_nested_attributes_for :user_organization_relationships, :reject_if => proc {|u| u[:user_id].blank?}, :allow_destroy => true - before_create :make_slug - - def location - locations.first - end - - def longitude - location.longitude - end - - def latitude - location.latitude - end - - def to_param - slug - end - - def host?(user) - return false unless user.present? - return true if user.administrator? - - users.each do |u| - return true if u.id == user.id - end - return false - end - - def generate_slug(name, location = nil) - s = name.gsub(/[^a-z1-9]+/i, '-').chomp('-').gsub(/\-([A-Z])/, '\1') - if Organization.find_by(:slug => s).present? && !location.nil? - if location.city.present? - s += '-' + location.city - end - if Organization.find_by(:slug => s).present? && location.territory.present? - s += '-' + location.territory - end - if Organization.find_by(:slug => s).present? - s += '-' + location.country - end - end - attempt = 1 - ss = s - - while Organization.find_by(:slug => s) - attempt += 1 - s = ss + '-' + attempt.to_s - end - s - end - - def self.find_by_city(city) - Organization.joins(:locations).where(locations: { - city_id: city.is_a?(City) ? city.id : city - }) - end - - private - def make_slug - if !self.slug - self.slug = generate_slug(self.name, self.locations && self.locations[0]) - end - end -end diff --git a/app/models/organization_status.rb b/app/models/organization_status.rb deleted file mode 100644 index 309f828..0000000 --- a/app/models/organization_status.rb +++ /dev/null @@ -1,2 +0,0 @@ -class OrganizationStatus < ActiveRecord::Base -end diff --git a/app/models/registration_form_field.rb b/app/models/registration_form_field.rb deleted file mode 100644 index c02288d..0000000 --- a/app/models/registration_form_field.rb +++ /dev/null @@ -1,75 +0,0 @@ -class RegistrationFormField < ActiveRecord::Base - Types = { - :single => [:title, :required, :input_type, :help], - :multiple => [:title, :required, :selection_type, :options, :other, :help] - } - - Fields = { - :title => {:control => 'text_field'}, - :input_type => {:control => 'select', :options => [[:text_field, :text_area, :number_field, :date_field, :time_field, :phone_field, :checkbox]], :option => true}, - :selection_type => {:control => 'select', :options => [[:check_box, :radio_button, :select]], :option => true}, - :options => {:control => 'text_area', :option => true}, - :help => {:control => 'text_area'}, - :other => {:control => 'check_box', :option => true}, - :required => {:control => 'check_box'} - } - - def self.TypesForField(field) - types = [] - Types.each do |k, t| - if t.include?(field) - types << k - end - end - types - end - - def input_type - get_from_options 'input_type' - end - - def selection_type - get_from_options 'selection_type' - end - - def other - get_from_options 'other' - end - - def self.GetOptions(type, values) - o = {} - Fields.each do |k, f| - if f[:option] && Types[type.to_sym].include?(k) - o[k] = values[k] - end - end - o - end - - def self.GetNonOptionKeys(type, values) - o = [] - Fields.each do |k, f| - if !f[:option] && Types[type.to_sym].include?(k) - o << k - end - end - o - end - - def repeats?() - field_type.to_s == 'multiple' && selection_type.to_s != 'select' - end - - def is_array?() - field_type.to_s == 'multiple' && selection_type.to_s != 'radio_button' - end - - private - def get_from_options(key) - if options - _options = ActiveSupport::JSON.decode(options) - return _options[key] - end - nil - end -end diff --git a/app/models/user.rb b/app/models/user.rb deleted file mode 100644 index c04d2b6..0000000 --- a/app/models/user.rb +++ /dev/null @@ -1,68 +0,0 @@ -class User < ActiveRecord::Base - authenticates_with_sorcery! do |config| - config.authentications_class = Authentication - end - - validates :email, uniqueness: true - - mount_uploader :avatar, AvatarUploader - - has_many :user_organization_relationships - has_many :organizations, through: :user_organization_relationships - has_many :conferences, through: :conference_administrators - has_many :authentications, :dependent => :destroy - accepts_nested_attributes_for :authentications - - before_update do |user| - user.locale ||= I18n.locale - user.email.downcase! - end - - before_save do |user| - user.locale ||= I18n.locale - user.email.downcase! - end - - def can_translate?(to_locale = nil, from_locale = nil) - is_translator unless to_locale.present? - - from_locale = I18n.locale unless from_locale.present? - return languages.present? && - to_locale.to_s != from_locale.to_s && - languages.include?(to_locale.to_s) && - languages.include?(from_locale.to_s) - end - - def name - firstname || username || email - end - - def named_email - name = firstname || username - return email unless name - return "#{name} <#{email}>" - end - - def administrator? - role == 'administrator' - end - - def self.AVAILABLE_LANGUAGES - [:en, :es, :fr, :ar] - end - - def self.get(email) - user = find_user(email) - - unless user - user = create(email: email, locale: I18n.locale) - end - - return user - end - - def self.find_user(email) - User.where('lower(email) = ?', email.downcase).first - end - -end diff --git a/app/models/user_organization_relationship.rb b/app/models/user_organization_relationship.rb deleted file mode 100644 index 789182a..0000000 --- a/app/models/user_organization_relationship.rb +++ /dev/null @@ -1,11 +0,0 @@ -class UserOrganizationRelationship < ActiveRecord::Base - belongs_to :user - belongs_to :organization - - Administrator = 'administrator' - Member = 'member' - - DefaultRelationship = Member - - AllRelationships = [Administrator, Member] -end diff --git a/app/models/version.rb b/app/models/version.rb deleted file mode 100644 index 6fd7588..0000000 --- a/app/models/version.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Version < ActiveRecord::Base -end diff --git a/app/models/workshop.rb b/app/models/workshop.rb deleted file mode 100644 index bab1994..0000000 --- a/app/models/workshop.rb +++ /dev/null @@ -1,180 +0,0 @@ -class Workshop < ActiveRecord::Base - translates :info, :title - - belongs_to :conference - belongs_to :event_location - - has_many :workshop_facilitators, :dependent => :destroy - has_many :users, :through => :workshop_facilitators - - accepts_nested_attributes_for :workshop_facilitators, :reject_if => proc {|u| u[:user_id].blank?}, :allow_destroy => true - - before_create :make_slug - - def to_param - slug - end - - def role(user) - return nil unless user - workshop_facilitators.each do |u| - if u.user_id == user.id - return conference.registration_exists?(user) ? u.role.to_sym : :unregistered - end - end - return nil - end - - def facilitator?(user) - !!role(user) - end - - def active_facilitators - users = [] - workshop_facilitators.each do |u| - users << User.find(u.user_id) unless u.role.to_sym == :requested || u.user_id.nil? - end - return users - end - - def active_facilitator?(user) - facilitator?(user) && !requested_collaborator?(user) - end - - def public_facilitator?(user) - return false if !active_facilitator?(user) - return true if creator?(user) - conference.registered?(user) - end - - def creator?(user) - role(user) == :creator - end - - def collaborator?(user) - role(user) == :collaborator - end - - def requested_collaborator?(user) - role(user) == :requested - end - - def can_edit?(user) - creator?(user) || collaborator?(user) || conference.host?(user) - end - - def can_remove?(owner, facilitator) - # creators cannot be removed - return false if creator?(facilitator) - - # creator can remove anyone, facilitators can remove themselves - return creator?(owner) || owner.id == facilitator.id - end - - def can_delete?(user) - creator?(user) || conference.host?(user) - end - - def can_show_interest?(user) - user.present? && !active_facilitator?(user) - end - - def interested?(user) - user.present? && !active_facilitator?(user) && WorkshopInterest.find_by(workshop_id: id, user_id: user.id) - end - - def interested_count - interested.size - end - - def interested - return [] unless id - return @interested if @interested.present? - - collaborators = [] - workshop_facilitators.each do |f| - collaborators << f.user_id unless f.role.to_sym == :requested || f.user_id.nil? - end - return 10 unless collaborators.present? - @interested = WorkshopInterest.where("workshop_id=#{id} AND user_id NOT IN (#{collaborators.join ','})") || [] - end - - def can_translate?(user, lang) - return false unless user.present? - user.can_translate?(lang, locale) || (can_edit?(user) && lang.to_s != locale.to_s) - end - - def conference_day - return nil unless start_time.present? && end_time.present? - - start_day = conference.start_date.change(hour: 0, minute: 0, second: 0) - w_start_day = start_time.change(hour: 0, minute: 0, second: 0) - return (((w_start_day - start_day) / 86400) + 1).to_i - end - - def duration - return nil unless start_time.present? && end_time.present? - ((end_time - start_time) / 60).to_i - end - - def self.all_themes - [:race_gender, :mechanics, :funding, :organization, :community] - end - - def self.all_spaces - [:meeting_room, :workshop, :outdoor_meeting] - end - - def self.all_needs - [:sound, :projector, :tools] - end - - def get_translators(data, loc = nil) - notify_list = {} - active_facilitators.each do |facilitator| - notify_list[facilitator.id] = facilitator - end - - data.each do | column, value | - ( - loc.present? ? - get_translators_for_column_and_locale(column, loc) : - get_translators_for_column(column) - ).each do |id| - notify_list[id] = User.find(id) - end - end - return notify_list - end - - def comments - Comment.for(self) - end - - def add_comment(user, comment) - Comment.create_for(self, user, comment) - end - - private - def make_slug - if !self.slug - s = self.title.gsub(/[^a-z1-9]+/i, '-').chomp('-').gsub(/\-([A-Z])/, '\1') - if Organization.find_by(:slug => s) && self.locations && self.locations[0] - s += '-' + self.locations[0].city - if Organization.find_by(:slug => s) && locations[0].territory - s += '-' + self.locations[0].territory - end - if Organization.find_by(:slug => s) - s += '-' + self.locations[0].country - end - end - attempt = 1 - ss = s - while Organization.find_by(:slug => s) - attempt += 1 - s = ss + '-' + attempt - end - self.slug = s - end - end -end diff --git a/app/models/workshop_facilitator.rb b/app/models/workshop_facilitator.rb deleted file mode 100644 index cbd4419..0000000 --- a/app/models/workshop_facilitator.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopFacilitator < ActiveRecord::Base -end diff --git a/app/models/workshop_interest.rb b/app/models/workshop_interest.rb deleted file mode 100644 index d35704d..0000000 --- a/app/models/workshop_interest.rb +++ /dev/null @@ -1,4 +0,0 @@ -class WorkshopInterest < ActiveRecord::Base - belongs_to :workshop - has_one :user -end diff --git a/app/models/workshop_presentation_style.rb b/app/models/workshop_presentation_style.rb deleted file mode 100644 index cc6ad25..0000000 --- a/app/models/workshop_presentation_style.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopPresentationStyle < ActiveRecord::Base -end diff --git a/app/models/workshop_requested_resource.rb b/app/models/workshop_requested_resource.rb deleted file mode 100644 index 317eea9..0000000 --- a/app/models/workshop_requested_resource.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopRequestedResource < ActiveRecord::Base -end diff --git a/app/models/workshop_resource.rb b/app/models/workshop_resource.rb deleted file mode 100644 index ea63a8a..0000000 --- a/app/models/workshop_resource.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopResource < ActiveRecord::Base -end diff --git a/app/models/workshop_stream.rb b/app/models/workshop_stream.rb deleted file mode 100644 index cf7d691..0000000 --- a/app/models/workshop_stream.rb +++ /dev/null @@ -1,2 +0,0 @@ -class WorkshopStream < ActiveRecord::Base -end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb deleted file mode 100644 index 4e4ad8e..0000000 --- a/app/uploaders/avatar_uploader.rb +++ /dev/null @@ -1,104 +0,0 @@ -# encoding: utf-8 -require 'carrierwave/processing/mini_magick' - -class AvatarUploader < CarrierWave::Uploader::Base - - include CarrierWave::ImageOptimizer - include CarrierWave::MiniMagick - - # Include RMagick or MiniMagick support: - # include CarrierWave::RMagick - # include CarrierWave::MiniMagick - - # Choose what kind of storage to use for this uploader: - - storage :file - process :optimize - - @@sizes = {:thumb => [120, 120], :icon => [48, 48], :preview => [360, 120], :normal => [512, 512]} - # storage :fog - - # Override the directory where uploaded files will be stored. - # This is a sensible default for uploaders that are meant to be mounted: - def store_dir - "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" - end - - # Provide a default URL as a default if there hasn't been a file uploaded: - # def default_url - # # For Rails 3.1+ asset pipeline compatibility: - # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) - # - #"/images/fallback/" + [version_name, "default.png"].compact.join('_') - # "http://placehold.it/" + (@@sizes[version_name] || [300, 300]).join('x') - #end - - # Process files as they are uploaded: - # process :scale => [200, 300] - # - #def scale(width, height) - #end - - # Create different versions of your uploaded files: - version :thumb do - process :resize_to_fill => @@sizes[:thumb] - end - - version :icon do - process :resize_to_fill => @@sizes[:icon] - end - - version :preview do - process :resize_to_fit => @@sizes[:preview] - end - - version :normal do - process :resize_to_fit => @@sizes[:normal] - end - - # Add a white list of extensions which are allowed to be uploaded. - # For images you might use something like this: - # def extension_white_list - # %w(jpg jpeg gif png) - # end - - # Override the filename of the uploaded files: - # Avoid using model.id or version_name here, see uploader/store.rb for details. - # def filename - # "something.jpg" if original_filename - # end - - def image - @image ||= MiniMagick::Image.open(file.path) - end - - def is_landscape? - image['width'] > (image['height'] * 1.25) - end - - #def recreate_versions!(*versions) - # if !current_path.nil? - # current_path = "'" + (current_path || '') + "'" - # end - # super(*versions) - #end - -# def manipulate! -# cache_stored_file! if !cached? -# image = ::MiniMagick::Image.open(current_path) -# -# begin -# image.format(@format.to_s.downcase) if @format -# image = yield(image) -# image.write(current_path) -# image.run_command("identify", '"' + current_path + '"') -# ensure -# image.destroy! -# end -# rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e -# default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) -# message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) -# raise CarrierWave::ProcessingError, message -# end - -end diff --git a/app/uploaders/cover_uploader.rb b/app/uploaders/cover_uploader.rb deleted file mode 100644 index c40dbcb..0000000 --- a/app/uploaders/cover_uploader.rb +++ /dev/null @@ -1,54 +0,0 @@ -# encoding: utf-8 -require 'carrierwave/processing/mini_magick' - -class CoverUploader < CarrierWave::Uploader::Base - - include CarrierWave::ImageOptimizer - include CarrierWave::MiniMagick - - storage :file - process :optimize - - def store_dir - "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" - end - - version :preview do - process :resize_to_fit => [480, 240] - end - - version :full do - process :resize_to_fit => [1200, 800] - end - - def image - @image ||= MiniMagick::Image.open(file.path) - end - - def is_landscape? - image['width'] > image['height'] - end - - def manipulate! - cache_stored_file! if !cached? - image = ::MiniMagick::Image.open(current_path) - - begin - image.format(@format.to_s.downcase) if @format - image = yield(image) - image.write(current_path) - begin - image.run_command("identify", current_path) - rescue - image.run_command("identify", '"' + current_path + '"') - end - ensure - image.destroy! - end - rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e - default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) - message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) - raise CarrierWave::ProcessingError, message - end - -end diff --git a/app/uploaders/logo_uploader.rb b/app/uploaders/logo_uploader.rb deleted file mode 100644 index b9f7127..0000000 --- a/app/uploaders/logo_uploader.rb +++ /dev/null @@ -1,57 +0,0 @@ -# encoding: utf-8 - -class LogoUploader < CarrierWave::Uploader::Base - - # Include RMagick or MiniMagick support: - # include CarrierWave::RMagick - include CarrierWave::ImageOptimizer - include CarrierWave::MiniMagick - - # Include RMagick or MiniMagick support: - # include CarrierWave::RMagick - # include CarrierWave::MiniMagick - - # Choose what kind of storage to use for this uploader: - - storage :file - process :optimize - - # Override the directory where uploaded files will be stored. - # This is a sensible default for uploaders that are meant to be mounted: - def store_dir - "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" - end - - # Provide a default URL as a default if there hasn't been a file uploaded: - # def default_url - # # For Rails 3.1+ asset pipeline compatibility: - # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) - # - # "/images/fallback/" + [version_name, "default.png"].compact.join('_') - # end - - # Process files as they are uploaded: - # process :scale => [200, 300] - # - # def scale(width, height) - # # do something - # end - - # Create different versions of your uploaded files: - # version :thumb do - # process :scale => [50, 50] - # end - - # Add a white list of extensions which are allowed to be uploaded. - # For images you might use something like this: - # def extension_white_list - # %w(jpg jpeg gif png) - # end - - # Override the filename of the uploaded files: - # Avoid using model.id or version_name here, see uploader/store.rb for details. - # def filename - # "something.jpg" if original_filename - # end - -end diff --git a/app/uploaders/poster_uploader.rb b/app/uploaders/poster_uploader.rb deleted file mode 100644 index b6e2220..0000000 --- a/app/uploaders/poster_uploader.rb +++ /dev/null @@ -1,69 +0,0 @@ -# encoding: utf-8 -require 'carrierwave/processing/mini_magick' - -class PosterUploader < CarrierWave::Uploader::Base - - include CarrierWave::ImageOptimizer - include CarrierWave::MiniMagick - - storage :file - process :optimize - - @@sizes = { - :thumb => [120, 120], - :icon => [48, 48], - :preview => [512, 512], - :full => [1024, 1024] - } - - def store_dir - "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" - end - - version :thumb do - process :resize_to_fill => @@sizes[:thumb] - end - - version :icon do - process :resize_to_fill => @@sizes[:icon] - end - - version :preview do - process :resize_to_fit => @@sizes[:preview] - end - - version :full do - process :resize_to_fit => @@sizes[:full] - end - - def image - @image ||= MiniMagick::Image.open(file.path) - end - - def is_landscape? - image['width'] > image['height'] - end - - def manipulate! - cache_stored_file! if !cached? - image = ::MiniMagick::Image.open(current_path) - - begin - image.format(@format.to_s.downcase) if @format - image = yield(image) - image.write(current_path) - begin - image.run_command("identify", current_path) - rescue - image.run_command("identify", '"' + current_path + '"') - end - ensure - image.destroy! - end - rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e - default = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :locale => :en) - message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e, :default => default) - raise CarrierWave::ProcessingError, message - end - -end diff --git a/app/views/admin/new.html.haml b/app/views/admin/new.html.haml index 633a7bb..2429552 100644 --- a/app/views/admin/new.html.haml +++ b/app/views/admin/new.html.haml @@ -19,6 +19,6 @@ = checkbox :is_featured, @this_conference.id.present? && @this_conference.is_featured != false, 'forms.labels.generic.is_featured' = columns(medium: 12) do .actions.next-prev - = button_tag :save, value: :save + = button :save, value: :save - if @this_conference.id.present? = button_with_confirmation :delete, value: :delete, class: 'delete' diff --git a/app/views/application/500.html.haml b/app/views/application/500.html.haml index 30fa659..f86f5fc 100644 --- a/app/views/application/500.html.haml +++ b/app/views/application/500.html.haml @@ -1,6 +1,5 @@ -= render :partial => 'application/header', :locals => {:image_file => 'parts.jpg'} += render partial: 'application/header', locals: {image_file: 'parts.jpg'} = row do = columns(medium: 12) do %p= _'error.500.description', :p = render 'contact', cancel_btn: false, contact_reason: :website - \ No newline at end of file diff --git a/app/views/application/_contact.html.haml b/app/views/application/_contact.html.haml index 3475c48..b944096 100644 --- a/app/views/application/_contact.html.haml +++ b/app/views/application/_contact.html.haml @@ -9,6 +9,6 @@ = textfield :subject, nil, required: true, big: true = textarea :message, nil, required: true, plain: true .actions - = button_tag :send, value: :send + = button :send, value: :send - if cancel_btn %button.close.subdued=_'forms.actions.generic.cancel' diff --git a/app/views/application/_login.html.haml b/app/views/application/_login.html.haml index 65a41e6..591e622 100644 --- a/app/views/application/_login.html.haml +++ b/app/views/application/_login.html.haml @@ -2,5 +2,5 @@ = form_tag do_confirm_path, class: 'flex-form' do = hidden_field_tag :dest, settings_path if dest.present? = emailfield :email, nil, big: true - = button_tag :continue, :value => :confirm_email + = button :continue, value: :confirm_email = link_to (_'forms.actions.generic.facebook_sign_in','Facebook Sign In'), auth_at_provider_path(provider: :facebook, dest: dest), class: [:button, :facebook] diff --git a/app/views/application/_login_confirm.html.haml b/app/views/application/_login_confirm.html.haml index 20245e1..f02c011 100644 --- a/app/views/application/_login_confirm.html.haml +++ b/app/views/application/_login_confirm.html.haml @@ -4,4 +4,4 @@ = form_tag :do_confirm, :authenticity_token => false, class: 'flex-form' do = emailfield :email, nil, required: true, big: true = hidden_field_tag :token, @confirmation.token - = button_tag :login + = button :login diff --git a/app/views/application/_login_confirmation_sent.html.haml b/app/views/application/_login_confirmation_sent.html.haml index 782fefc..5338ec0 100644 --- a/app/views/application/_login_confirmation_sent.html.haml +++ b/app/views/application/_login_confirmation_sent.html.haml @@ -1,4 +1,4 @@ = row do - = columns(medium: 12) do - %h2=_'articles.permission_denied.headings.confirmation_sent' - %p=_'articles.conference_registration.paragraphs.email_confirm', :p + = columns(medium: 12) do + %h2=_'articles.permission_denied.headings.confirmation_sent' + %p=_'articles.conference_registration.paragraphs.email_confirm', :p diff --git a/app/views/application/_not_a_translator.html.haml b/app/views/application/_not_a_translator.html.haml index df40379..9cf5108 100644 --- a/app/views/application/_not_a_translator.html.haml +++ b/app/views/application/_not_a_translator.html.haml @@ -6,4 +6,4 @@ = columns(medium: 6) do = form_tag :translator_request do = text_area_tag :comment, nil, placeholder: true, required: true - = button_tag :translator_request + = button :translator_request diff --git a/app/views/application/_translator_login.html.haml b/app/views/application/_translator_login.html.haml index e2ec862..f49dced 100644 --- a/app/views/application/_translator_login.html.haml +++ b/app/views/application/_translator_login.html.haml @@ -8,4 +8,4 @@ .email-field.input-field = email_field_tag :email, nil, required: true = label_tag :email - = button_tag :login + = button :login diff --git a/app/views/application/contact.html.haml b/app/views/application/contact.html.haml index 513d449..5df755a 100644 --- a/app/views/application/contact.html.haml +++ b/app/views/application/contact.html.haml @@ -1,10 +1,10 @@ = render :partial => 'application/header', :locals => {:image_file => @banner_image || 'grafitti.jpg'} %article - = row do - = columns do - - if @sent - %h2=_'articles.contact.headings.sent', :t - %p=_'articles.contact.paragraphs.sent', :p - - else - %h2=_'articles.contact.headings.contact' - = render 'contact', cancel_btn: false \ No newline at end of file + = row do + = columns do + - if @sent + %h2=_'articles.contact.headings.sent', :t + %p=_'articles.contact.paragraphs.sent', :p + - else + %h2=_'articles.contact.headings.contact' + = render 'contact', cancel_btn: false \ No newline at end of file diff --git a/app/views/application/home.html.haml b/app/views/application/home.html.haml index 8e827c1..6163328 100644 --- a/app/views/application/home.html.haml +++ b/app/views/application/home.html.haml @@ -1,5 +1,6 @@ - content_for :og_image do - = @conference.poster.full.url || image_path('default_poster.jpg') + - if @conference.present? + = @conference.poster.full.url || image_path('default_poster.jpg') - if @conferences - @conferences.each do | conference | = render 'conferences/conference', conference: conference, links: [ :read_more, :register ] diff --git a/app/views/application/permission_denied.html.haml b/app/views/application/permission_denied.html.haml index dfbc3fa..8fdf1d1 100644 --- a/app/views/application/permission_denied.html.haml +++ b/app/views/application/permission_denied.html.haml @@ -1,10 +1,10 @@ = render :partial => 'application/header', :locals => {:image_file => @banner_image || '403.jpg'} %article - - if @template.present? - =render @template - - else - = row do - = columns do - %h2=_'error.403.title','Sorry, you currently don\'t have access to this page' - %p=_'error.403.description', :p - = render 'contact', cancel_btn: false, contact_reason: :website \ No newline at end of file + - if @template.present? + =render @template + - else + = row do + = columns do + %h2=_'error.403.title','Sorry, you currently don\'t have access to this page' + %p=_'error.403.description', :p + = render 'contact', cancel_btn: false, contact_reason: :website \ No newline at end of file diff --git a/app/views/application/update_user.html.haml b/app/views/application/update_user.html.haml index d107d68..fdf9c89 100644 --- a/app/views/application/update_user.html.haml +++ b/app/views/application/update_user.html.haml @@ -6,4 +6,4 @@ %p=_'articles.conference_registration.paragraphs.provide_email', :p = form_tag oauth_save_path, class: 'flex-form' do = emailfield :email, nil, required: true, big: true - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/application/user_settings.html.haml b/app/views/application/user_settings.html.haml index d34111f..3271df8 100644 --- a/app/views/application/user_settings.html.haml +++ b/app/views/application/user_settings.html.haml @@ -19,7 +19,7 @@ = radiobuttons :preferred_language, I18n.backend.enabled_locales, current_user.locale || I18n.locale, 'languages', heading: 'articles.conference_registration.headings.preferred_language' = checkbox :email_subscribe, current_user.is_subscribed != false, 'articles.user_settings.email_subscribe', heading: 'articles.user_settings.headings.email_subscribe', help: 'articles.user_settings.paragraphs.email_subscribe', inline: true, right_help: true .actions - = button_tag :save, value: :save + = button :save, value: :save - else %h2=_'forms.actions.generic.login' = render 'login' \ No newline at end of file diff --git a/app/views/conference_administration/_administrators.html.haml b/app/views/conference_administration/_administrators.html.haml index b8d65e5..36a2b04 100644 --- a/app/views/conference_administration/_administrators.html.haml +++ b/app/views/conference_administration/_administrators.html.haml @@ -4,7 +4,7 @@ = admin_update_form do = checkboxes :organizations, (@organizations.map { |org| [org.name, org.id] }), @this_conference.organizations.map(&:id), 'test.test', vertical: true, big: true .actions.right.small - = button_tag :save, value: :set_organizations + = button :save, value: :set_organizations - @this_conference.organizations.each do | organization | %h4=organization.name - if organization.users.present? @@ -16,11 +16,11 @@ = admin_update_form class: [:inline, :right] do = hidden_field_tag :user_id, user.id = hidden_field_tag :org_id, organization.id - = button_tag :remove_member, value: :remove_org_member, class: [:small, :delete] + = button :remove_member, value: :remove_org_member, class: [:small, :delete] = admin_update_form class: 'mini-flex-form' do = hidden_field_tag :org_id, organization.id = emailfield :email, nil, required: true - = button_tag :add_member, value: :add_org_member, class: :small + = button :add_member, value: :add_org_member, class: :small %h3=_'articles.admin.info.headings.External_Administrators' %p=_'articles.admin.info.descriptions.External_Administrators' @@ -32,10 +32,10 @@ - unless user.id == current_user.id && !current_user.administrator? = admin_update_form class: [:inline, :right] do = hidden_field_tag :user_id, user.id - = button_tag :remove_member, value: :remove_administrator, class: [:small, :delete] + = button :remove_member, value: :remove_administrator, class: [:small, :delete] = admin_update_form class: 'mini-flex-form' do = userfield :email, nil, required: true -#= emailfield :email, nil, required: true - = button_tag :add_member, value: :add_administrator, class: :small + = button :add_member, value: :add_administrator, class: :small = columns(large: 2) do   diff --git a/app/views/conference_administration/_broadcast.html.haml b/app/views/conference_administration/_broadcast.html.haml index 2990e25..c1cc9b0 100644 --- a/app/views/conference_administration/_broadcast.html.haml +++ b/app/views/conference_administration/_broadcast.html.haml @@ -12,12 +12,12 @@ %h3=@subject = richtext @body, 4 .actions.right - = button_tag :test, value: :test, class: :secondary if @broadcast_step == :preview + = button :test, value: :test, class: :secondary if @broadcast_step == :preview = button_with_confirmation :send, (_'modals.admin.broadcast.confirm', vars: { number: "#{(@send_to_count || 0)}".html_safe }), value: :send, class: :delete if @broadcast_step == :test - = button_tag :edit, value: :edit + = button :edit, value: :edit - else = selectfield :send_to, nil, broadcast_options, full: true = textfield :subject, @subject, required: true, big: true = textarea :body, @body, lang: @this_conference.locale, edit_on: :focus .actions.right - = button_tag :preview, value: :preview + = button :preview, value: :preview diff --git a/app/views/conference_administration/_dates.html.haml b/app/views/conference_administration/_dates.html.haml index b7345b5..aef2097 100644 --- a/app/views/conference_administration/_dates.html.haml +++ b/app/views/conference_administration/_dates.html.haml @@ -14,4 +14,4 @@ = row do = columns(medium: 6, push: { medium: 1 }) do .actions - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_description.html.haml b/app/views/conference_administration/_description.html.haml index 399ba00..a1a3303 100644 --- a/app/views/conference_administration/_description.html.haml +++ b/app/views/conference_administration/_description.html.haml @@ -2,4 +2,4 @@ = admin_update_form do = translate_textarea :info, @this_conference, label: 'articles.conference_registration.headings.admin.edit.info', help: 'articles.conference_registration.paragraphs.admin.edit.info', edit_on: :focus .actions.right - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_events.html.haml b/app/views/conference_administration/_events.html.haml index bc4363e..3f77d38 100644 --- a/app/views/conference_administration/_events.html.haml +++ b/app/views/conference_administration/_events.html.haml @@ -37,5 +37,5 @@ = length_select @length, small: true = translate_fields @event, { title: { type: :textfield, big: true, label: 'forms.labels.generic.title' }, info: { type: :textarea, label: 'forms.labels.generic.info', edit_on: :focus } } .actions.next-prev - = button_tag :save, value: :save - = button_tag :cancel, value: :cancel, class: :subdued, formnovalidate: true if @event.id.present? + = button :save, value: :save + = button :cancel, value: :cancel, class: :subdued, formnovalidate: true if @event.id.present? diff --git a/app/views/conference_administration/_locations.html.haml b/app/views/conference_administration/_locations.html.haml index 2594798..492b04f 100644 --- a/app/views/conference_administration/_locations.html.haml +++ b/app/views/conference_administration/_locations.html.haml @@ -36,7 +36,7 @@ = columns(medium: 12) do .actions.next-prev - if @location.present? - = button_tag :save, value: :save - = button_tag :cancel, value: :cancel, class: :subdued, formnovalidate: true + = button :save, value: :save + = button :cancel, value: :cancel, class: :subdued, formnovalidate: true - else - = button_tag :create, value: :create \ No newline at end of file + = button :create, value: :create \ No newline at end of file diff --git a/app/views/conference_administration/_meals.html.haml b/app/views/conference_administration/_meals.html.haml index 78eb924..242a5fa 100644 --- a/app/views/conference_administration/_meals.html.haml +++ b/app/views/conference_administration/_meals.html.haml @@ -20,7 +20,7 @@ %td.form = admin_update_form do = hidden_field_tag :meal, time - = button_tag :delete, value: :delete, class: [:small, :delete] + = button :delete, value: :delete, class: [:small, :delete] = admin_update_form do %h3=_'articles.admin.locations.headings.add_meal', :t .flex-inputs @@ -30,6 +30,6 @@ = textfield :title, nil, required: true, big: true, help: 'articles.admin.locations.paragraphs.meal_title' = textfield :info, nil, help: 'articles.admin.locations.paragraphs.meal_info' .actions.next-prev - = button_tag :add_meal, value: :add_meal + = button :add_meal, value: :add_meal - else .warning-info=_'articles.admin.meals.no_locations_warning' \ No newline at end of file diff --git a/app/views/conference_administration/_payment_message.html.haml b/app/views/conference_administration/_payment_message.html.haml index c902889..2605416 100644 --- a/app/views/conference_administration/_payment_message.html.haml +++ b/app/views/conference_administration/_payment_message.html.haml @@ -3,4 +3,4 @@ = translate_textarea :payment_message, @this_conference, default: 'articles.conference_registration.paragraphs.Payment', help: 'articles.conference_registration.paragraphs.admin.payment.message', edit_on: :focus, short: true .actions.right - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_paypal.html.haml b/app/views/conference_administration/_paypal.html.haml index 96ea5b4..4affb26 100644 --- a/app/views/conference_administration/_paypal.html.haml +++ b/app/views/conference_administration/_paypal.html.haml @@ -5,4 +5,4 @@ = passwordfield :paypal_password, @this_conference.paypal_password = textfield :paypal_signature, @this_conference.paypal_signature .actions.right - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_poster.html.haml b/app/views/conference_administration/_poster.html.haml index 97f254b..51ffea7 100644 --- a/app/views/conference_administration/_poster.html.haml +++ b/app/views/conference_administration/_poster.html.haml @@ -2,4 +2,4 @@ = form_tag administration_update_path(@this_conference.slug, :poster), multipart: true do = filefield :poster, @this_conference.poster, required: true, label: false, preview: true .actions.left - = button_tag :upload, value: :upload, id: 'upload-file' + = button :upload, value: :upload, id: 'upload-file' diff --git a/app/views/conference_administration/_providers.html.haml b/app/views/conference_administration/_providers.html.haml index 35e14fd..5d3e32b 100644 --- a/app/views/conference_administration/_providers.html.haml +++ b/app/views/conference_administration/_providers.html.haml @@ -7,4 +7,4 @@ = selectfield :distance_unit, @conditions['distance']['unit'], [:km, :mi], label: false, inline: true = columns(medium: 12) do .actions.right - = button_tag :save, value: :save_distance + = button :save, value: :save_distance diff --git a/app/views/conference_administration/_publish_schedule.html.haml b/app/views/conference_administration/_publish_schedule.html.haml index c2ed962..762ad70 100644 --- a/app/views/conference_administration/_publish_schedule.html.haml +++ b/app/views/conference_administration/_publish_schedule.html.haml @@ -2,7 +2,7 @@ = form_tag administration_update_path(@this_conference.slug, @admin_step) do - if @this_conference.workshop_schedule_published %p=_'articles.conference_registration.paragraphs.admin.schedule.published', :p - .actions= button_tag :un_publish, value: :publish, class: :delete + .actions= button :un_publish, value: :publish, class: :delete - else %p=_'articles.conference_registration.paragraphs.admin.schedule.un_published', :p - .actions= button_tag :publish, value: :publish + .actions= button :publish, value: :publish diff --git a/app/views/conference_administration/_registration_status.html.haml b/app/views/conference_administration/_registration_status.html.haml index 7558d58..fffc7eb 100644 --- a/app/views/conference_administration/_registration_status.html.haml +++ b/app/views/conference_administration/_registration_status.html.haml @@ -2,4 +2,4 @@ = form_tag administration_update_path(@this_conference.slug, @admin_step) do = selectfield :registration_status, @this_conference.registration_status || 'closed', registration_status_options_list, inline_label: true .actions.left - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_registrations.html.haml b/app/views/conference_administration/_registrations.html.haml index 25f07e0..3d97266 100644 --- a/app/views/conference_administration/_registrations.html.haml +++ b/app/views/conference_administration/_registrations.html.haml @@ -14,4 +14,4 @@ = html_edit_table @excel_data, registrations_edit_table_options .actions.right %a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel' - = button_tag :save, value: :save, class: :modify + = button :save, value: :save, class: :modify diff --git a/app/views/conference_administration/_schedule.html.haml b/app/views/conference_administration/_schedule.html.haml index 03ffdbe..1061070 100644 --- a/app/views/conference_administration/_schedule.html.haml +++ b/app/views/conference_administration/_schedule.html.haml @@ -1,85 +1,86 @@ -= columns(medium: 12) do - - conference = @this_conference || @conference - - if conference.event_locations.blank? && @entire_page - .warning-info=_'articles.admin.schedule.no_locations_warning' - - else - - add_inline_script :schedule if @entire_page - #schedule-preview - - @schedule.each do | day, data | - %h4=date(day, :weekday) - %table.schedule{class: [data[:locations].present? ? 'has-locations' : 'no-locations', "locations-#{data[:num_locations]}"]} - - if data[:locations].present? && data[:locations].values.first != :add - %thead - %tr - %th.corner - - data[:locations].each do | id, location | - %th=location.is_a?(Symbol) ? '' : location.title - %tbody - - data[:times].each do | time, time_data | - %tr - - rowspan = (time_data[:length] * 2).to_i - %th=time(time) - - if time_data[:type] == :workshop - - data[:locations].each do | id, location | - - if time_data[:item][:workshops][id].present? - - workshop = time_data[:item][:workshops][id][:workshop] - - status = time_data[:item][:workshops][id][:status] - - else - - workshop = status = nil - %td{class: [time_data[:type], workshop.present? ? :filled : :open], rowspan: rowspan, data: workshop.present? ? nil : { block: time_data[:item][:block], day: day, location: id }} - - if workshop.present? && workshop.event_location.present? - = link_to view_workshop_path(@conference.slug, workshop.id), class: 'event-detail-link' do - .details - .title=workshop.title - %template.event-details{data: { href: view_workshop_path(@conference.slug, workshop.id) }} - %h1.title=workshop.title - %p.address - = workshop.event_location.title + _!(': ') - = location_link workshop.event_location - .workshop-description= richtext workshop.info, 1 - - if @can_edit - = form_tag administration_update_path(conference.slug, @admin_step), class: 'deschedule-workshop' do - .status - .conflict-score - %span.title Conflicts: - %span.value="#{status[:conflict_score]} / #{workshop.interested.size}" - - if status[:errors].present? - .errors - - status[:errors].each do | error | - .error=_"errors.messages.schedule.#{error[:name].to_s}", vars: error[:i18nVars] - = hidden_field_tag :id, workshop.id - = button_tag :deschedule, value: :deschedule_workshop, class: [:delete, :small] - - elsif @can_edit - .title="Block #{time_data[:item][:block] + 1}" - - elsif time_data[:type] != :nil - %td{class: time_data[:type], rowspan: rowspan, colspan: data[:locations].present? ? data[:locations].size : 1} - - case time_data[:type] - - when :meal - - location = EventLocation.where(id: time_data[:item]['location'].to_i).first - - if location.present? - %a.event-detail-link += row do + = columns(medium: 12) do + - conference = @this_conference || @conference + - if conference.event_locations.blank? && @entire_page + .warning-info=_'articles.admin.schedule.no_locations_warning' + - else + - add_inline_script :schedule if @entire_page + #schedule-preview + - @schedule.each do |day, data| + %h4=date(day, :weekday) + %table.schedule{class: [data[:locations].present? ? 'has-locations' : 'no-locations', "locations-#{data[:num_locations]}"]} + - if data[:locations].present? && data[:locations].values.first != :add + %thead + %tr + %th.corner + - data[:locations].each do |id, location| + %th=location.is_a?(Symbol) ? '' : _!(location.title) + %tbody + - data[:times].each do |time, time_data| + %tr + - rowspan = (time_data[:length] * 2).to_i + %th=time(time) + - if time_data[:type] == :workshop + - data[:locations].each do |id, location| + - if time_data[:item][:workshops][id].present? + - workshop = time_data[:item][:workshops][id][:workshop] + - status = time_data[:item][:workshops][id][:status] + - else + - workshop = status = nil + %td{class: [time_data[:type], workshop.present? ? :filled : :open], rowspan: rowspan, data: workshop.present? ? nil : { block: time_data[:item][:block], day: day, location: id }} + - if workshop.present? && workshop.event_location.present? + = link_to view_workshop_path(@conference.slug, workshop.id), class: 'event-detail-link' do .details - .title= time_data[:item]['title'] - .location= location.title - %template.event-details - %h1.title=time_data[:item]['title'] + .title=_!workshop.title + %template.event-details{data: { href: view_workshop_path(@conference.slug, workshop.id) }} + %h1.title=_!workshop.title %p.address - = location.title + _!(': ') - = location_link location - - when :event - - if time_data[:item].event_location.present? - %a.event-detail-link - .details - .title= time_data[:item][:title] - .location= time_data[:item].event_location.title - %template.event-details - %h1.title=time_data[:item][:title] - %p.address - = time_data[:item].event_location.title + _!(': ') - = location_link time_data[:item].event_location - = richtext time_data[:item][:info], 1 - - if @entire_page - #workshop-selector - = form_tag administration_update_path(@this_conference.slug, @admin_step), class: 'workshop-dlg', id: 'workshop-table-form' do - %h3 Select a Workshop - #table + = _!("#{workshop.event_location.title}:") + = location_link workshop.event_location + .workshop-description= richtext workshop.info, 1 + - if @can_edit + = form_tag administration_update_path(conference.slug, @admin_step), class: 'deschedule-workshop' do + .status + .conflict-score + %span.title Conflicts: + %span.value="#{status[:conflict_score]} / #{workshop.interested.size}" + - if status[:errors].present? + .errors + - status[:errors].each do |error| + .error=_"errors.messages.schedule.#{error[:name].to_s}", vars: error[:i18nVars] + = hidden_field_tag :id, workshop.id + = button :deschedule, value: :deschedule_workshop, class: [:delete, :small] + - elsif @can_edit + .title="Block #{time_data[:item][:block] + 1}" + - elsif time_data[:type] != :nil + %td{class: time_data[:type], rowspan: rowspan, colspan: data[:locations].present? ? data[:locations].size : 1} + - case time_data[:type] + - when :meal + - location = EventLocation.where(id: time_data[:item]['location'].to_i).first + - if location.present? + %a.event-detail-link + .details + .title=_!(time_data[:item]['title']) + .location=_!location.title + %template.event-details + %h1.title=_!(time_data[:item]['title']) + %p.address + = _!("#{location.title}:") + = location_link location + - when :event + - if time_data[:item].event_location.present? + %a.event-detail-link + .details + .title=_!(time_data[:item][:title]) if time_data[:item][:title] + .location=_!(time_data[:item].event_location.title) + %template.event-details + %h1.title=_!(time_data[:item][:title]) if time_data[:item][:title] + %p.address + = _!("#{time_data[:item].event_location.title}:") + = location_link time_data[:item].event_location + = richtext time_data[:item][:info], 1 + - if @entire_page + #workshop-selector + = form_tag administration_update_path(@this_conference.slug, @admin_step), class: 'workshop-dlg', id: 'workshop-table-form' do + %h3 Select a Workshop + #table diff --git a/app/views/conference_administration/_suggested_amounts.html.haml b/app/views/conference_administration/_suggested_amounts.html.haml index 7cd7f5d..1af4ca0 100644 --- a/app/views/conference_administration/_suggested_amounts.html.haml +++ b/app/views/conference_administration/_suggested_amounts.html.haml @@ -5,4 +5,4 @@ - for i in 1..5 do = numberfield "payment_amounts[#{i - 1}]", payment_amounts[i - 1], step: 0.01, min: 0.00, label: false .actions.right - = button_tag :save, value: :save + = button :save, value: :save diff --git a/app/views/conference_administration/_workshop_times.html.haml b/app/views/conference_administration/_workshop_times.html.haml index c175b73..d0e0c71 100644 --- a/app/views/conference_administration/_workshop_times.html.haml +++ b/app/views/conference_administration/_workshop_times.html.haml @@ -15,5 +15,5 @@ .table-td=checkboxes :days, @block_days, info['days'].map(&:to_i), 'date.day_names', vertical: true, small: true .table-td.form = hidden_field_tag :workshop_block, block - = button_tag :delete_block, value: :delete_block, class: [:small, :delete] if block == @workshop_blocks.length - 2 - = button_tag (is_new ? :add_block : :update_block), value: :save_block, class: [:small, :add] + = button :delete_block, value: :delete_block, class: [:small, :delete] if block == @workshop_blocks.length - 2 + = button (is_new ? :add_block : :update_block), value: :save_block, class: [:small, :add] diff --git a/app/views/conferences/_administration.html.haml b/app/views/conferences/_administration.html.haml deleted file mode 100644 index 6cb87d9..0000000 --- a/app/views/conferences/_administration.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -= columns(medium: 3, large: 2) do - = admin_menu -= columns(medium: 9, large: 10) do - %h3.subtitle=_("menu.submenu.admin.#{@admin_step.titlecase.gsub(/\s/, '_')}") - %p=(_"articles.admin.#{@admin_step.gsub(/\s/, '_')}.description", :p) unless @hide_description === true - %div{id: "admin-#{@admin_step}"}= render "conferences/admin/#{@admin_step}" diff --git a/app/views/conferences/_conference.html.haml b/app/views/conferences/_conference.html.haml index eb41317..f1378fb 100644 --- a/app/views/conferences/_conference.html.haml +++ b/app/views/conferences/_conference.html.haml @@ -1,18 +1,17 @@ - links ||= [ :register ] - sections ||= [ :info ] += row(tag: :header) do + = columns(class: 'conference-banner') do + .title + %h1=_!conference.title + .details + %h2.primary=location(conference.city || conference.location) if conference.city_name.present? + - if conference.start_date.present? && conference.end_date.present? + .secondary + = date_span(conference.start_date.to_date, conference.end_date.to_date) + - if conference.poster.present? + %img{src: conference.poster.full.url, role: :presentation, alt: (_'images.conference.poster', vars: { conference_title: conference.title })} %article - = row(tag: :header) do - = columns(class: 'conference-banner') do - .title - %h1=_!conference.title - .details - %h2.primary=location(conference.city || conference.location) if conference.city_name.present? - - if conference.start_date.present? && conference.end_date.present? - .secondary - = date_span(conference.start_date.to_date, conference.end_date.to_date) - - if conference.poster.present? - %img{src: conference.poster.full.url || image_path('default_poster.jpg'), role: :presentation, alt: (_'images.conference.poster', vars: { conference_title: conference.title })} - = row(class: 'conference-details') do = columns(medium: 10, push: {medium: 1}) do %h2=_!conference.title if conference.poster.present? @@ -21,13 +20,13 @@ - if conference.workshop_schedule_published - add_inline_script :home_schedule %h3=_'articles.workshops.headings.Schedule' - = render 'conferences/admin/schedule' + = render 'conference_administration/schedule' - else %h3=_'articles.workshops.headings.Proposed_Workshops' %p=_'articles.workshops.paragraphs.Proposed_Workshops' - = render 'workshops/workshop_previews', :workshops => (conference.workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }) + = render 'workshops/workshop_previews', workshops: (conference.workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }) .links - = (link_to (_'forms.actions.generic.register'), register_path(conference.slug), class: [:button, :register]) if links.include?(:register) && conference.registration_status == :open + = (link_to (_'forms.actions.generic.register'), register_path(conference.slug), class: [:button, :register]) if links.include?(:register) && conference.can_register? = (link_to (_'articles.workshops.info.read_more'), conference_path(conference.slug), class: :button) if links.include?(:read_more) = (link_to (_'forms.actions.generic.administrate'), administrate_conference_path(conference.slug), class: [:button]) if links.include?(:administrate) = (link_to (_'forms.actions.generic.edit'), edit_conference_path(conference.slug), class: [:button, :subdued]) if links.include?(:edit) diff --git a/app/views/conferences/_confirm_email.html.haml b/app/views/conferences/_confirm_email.html.haml index f8270b2..6ada132 100644 --- a/app/views/conferences/_confirm_email.html.haml +++ b/app/views/conferences/_confirm_email.html.haml @@ -1,13 +1,13 @@ = columns(medium: 12) do - %p=_"articles.conference_registration.paragraphs.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" - %h3=_'articles.conference_registration.headings.Verify_Account' - %p=_'articles.conference_registration.paragraphs.Verify_Account' + %p=_"articles.conference_registration.paragraphs.#{@this_conference.registration_status == :open ? '': 'Pre_'}Registration_Details" + %h3=_'articles.conference_registration.headings.Verify_Account' + %p=_'articles.conference_registration.paragraphs.Verify_Account' - = form_tag register_path(@this_conference.slug), class: 'flex-form' do - .email-field.input-field.big - = email_field_tag :email, nil, required: true - = label_tag :email - = button_tag :continue, :value => :confirm_email + = form_tag register_path(@this_conference.slug), class: 'flex-form' do + .email-field.input-field.big + = email_field_tag :email, nil, required: true + = label_tag :email + = button :continue, value: :confirm_email = columns(medium: 12, class: 'flex-column') do - %p.stretch-item=_'articles.conference_registration.paragraphs.facebook_sign_in' - = link_to (_'forms.actions.generic.facebook_sign_in','Facebook Sign In'), auth_at_provider_path(:provider => :facebook), class: [:button, :facebook] + %p.stretch-item=_'articles.conference_registration.paragraphs.facebook_sign_in' + = link_to (_'forms.actions.generic.facebook_sign_in','Facebook Sign In'), auth_at_provider_path(:provider => :facebook), class: [:button, :facebook] diff --git a/app/views/conferences/_contact_info.html.haml b/app/views/conferences/_contact_info.html.haml index d8a69f8..405137f 100644 --- a/app/views/conferences/_contact_info.html.haml +++ b/app/views/conferences/_contact_info.html.haml @@ -5,5 +5,5 @@ = textfield :location, (params[:location] || location(@registration.city ||lookup_ip_location)), required: true, heading: 'articles.conference_registration.headings.location' = checkboxes :languages, User.AVAILABLE_LANGUAGES, (current_user.languages || [I18n.locale]).map(&:to_sym), 'languages', heading: 'articles.conference_registration.headings.languages' .actions.next-prev - = button_tag (params[:step] == :save ? :save : :next), value: :contact_info - = button_tag :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true + = button (params[:step] == :save ? :save : :next), value: :contact_info + = button :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true diff --git a/app/views/conferences/_done.html.haml b/app/views/conferences/_done.html.haml index 0bfc565..3cc74d1 100644 --- a/app/views/conferences/_done.html.haml +++ b/app/views/conferences/_done.html.haml @@ -48,4 +48,4 @@ - if @conference.registration_open = form_tag register_path(@this_conference.slug) do .actions - = button_tag :edit_registration, :value => :register + = button :edit_registration, :value => :register diff --git a/app/views/conferences/_email_confirm.html.haml b/app/views/conferences/_email_confirm.html.haml index 89b384b..d0256f0 100644 --- a/app/views/conferences/_email_confirm.html.haml +++ b/app/views/conferences/_email_confirm.html.haml @@ -1,6 +1,6 @@ %article - = row do - = columns(medium: 12) do - %h2=_'articles.conference_registration.headings.email_confirm','Please confirm your email address' - = columns(medium: 12) do - %p=_'articles.conference_registration.paragraphs.email_confirm', :p + = row do + = columns(medium: 12) do + %h2=_'articles.conference_registration.headings.email_confirm','Please confirm your email address' + = columns(medium: 12) do + %p=_'articles.conference_registration.paragraphs.email_confirm', :p diff --git a/app/views/conferences/_hosting.html.haml b/app/views/conferences/_hosting.html.haml index fbf596c..98c4004 100644 --- a/app/views/conferences/_hosting.html.haml +++ b/app/views/conferences/_hosting.html.haml @@ -16,5 +16,5 @@ = checkboxes :considerations, ConferenceRegistration.all_considerations, @hosting_data['considerations'], 'articles.conference_registration.host.considerations', heading: 'articles.conference_registration.headings.host.considerations', help: 'articles.conference_registration.paragraphs.host.considerations', vertical: true = textarea :notes, @hosting_data['notes'], help: 'articles.conference_registration.paragraphs.host.notes', edit_on: :focus .actions.next-prev - = button_tag (params[:step] == :save ? :save : :next), value: :hosting - = button_tag :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true + = button (params[:step] == :save ? :save : :next), value: :hosting + = button :previous, value: :prev_contact_info, class: :subdued, formnovalidate: true diff --git a/app/views/conferences/_payment.html.haml b/app/views/conferences/_payment.html.haml index 4a5143e..a76633d 100644 --- a/app/views/conferences/_payment.html.haml +++ b/app/views/conferences/_payment.html.haml @@ -9,21 +9,22 @@ - else %p=@this_conference.payment_message || (_'articles.conference_registration.paragraphs.Payment', :p) = columns(large: 9, push: 1) do + = show_errors :payment = form_tag register_path(@this_conference.slug), class: :payment do = hidden_field_tag :button, :payment - payment_amounts = @this_conference.payment_amounts.present? ? @this_conference.payment_amounts : Conference.default_payment_amounts .graded-options{class: "option-count-#{payment_amounts.size}"} - payment_amounts.each_with_index do | amount, i | - = button_tag :amount_25, name: :amount, value: amount.to_f.to_s, class: "option-#{i + 1}" do + = button :amount_25, name: :amount, value: amount.to_f.to_s, class: "option-#{i + 1}" do =_! (number_to_currency amount, unit: '$') = form_tag register_path(@this_conference.slug), class: ['custom-payment', :centered] do - %span.currency=_!'$' + %span.currency=_!'$' # TODO: this needs to be localized = numberfield :amount, nil, required: true, step: 0.01, min: 0.00, label: false - = button_tag :custom_amount, value: :payment + = button :custom_amount, value: :payment %p=_'articles.conference_registration.paragraphs.currency','(amounts are in $USD)' = form_tag register_path(@this_conference.slug), class: :payment do = hidden_field_tag :button, :payment .actions.skip - = button_tag :skip, name: :amount, value: '0.0' + = button :skip, name: :amount, value: '0.0' = columns(large: 2) diff --git a/app/views/conferences/_paypal_confirm.html.haml b/app/views/conferences/_paypal_confirm.html.haml index aa44e40..d3f5646 100644 --- a/app/views/conferences/_paypal_confirm.html.haml +++ b/app/views/conferences/_paypal_confirm.html.haml @@ -1,5 +1,5 @@ = columns(medium: 12) do - %h3=_'articles.conference_registration.headings.payment_confirm' - %p=_'articles.conference_registration.paragraphs.payment_confirm', vars: {:amount => @amount} - = form_tag register_path(@this_conference.slug), :class => :payment do - .actions= button_tag :confirm_amount, :value => :paypal_confirmed + %h3=_'articles.conference_registration.headings.payment_confirm' + %p=_'articles.conference_registration.paragraphs.payment_confirm', vars: {:amount => @amount} + = form_tag register_path(@this_conference.slug), :class => :payment do + .actions= button :confirm_amount, :value => :paypal_confirmed diff --git a/app/views/conferences/_policy.html.haml b/app/views/conferences/_policy.html.haml index 8a67c29..f614a5e 100644 --- a/app/views/conferences/_policy.html.haml +++ b/app/views/conferences/_policy.html.haml @@ -1,8 +1,8 @@ = columns(medium: 12) do - %p=_'articles.conference_registration.paragraphs.Policy_Agreement', :s, 2 + %p=_'articles.conference_registration.paragraphs.Policy_Agreement', :s, 2 = columns(medium: 10, push: 1) do - = render 'application/policy' + = render 'application/policy' = columns(medium: 12) do - %p=_'articles.conference_registration.paragraphs.Confirm_Agreement', :p - = form_tag register_path(@this_conference.slug) do - .actions= button_tag :agree, :value => :policy + %p=_'articles.conference_registration.paragraphs.Confirm_Agreement', :p + = form_tag register_path(@this_conference.slug) do + .actions= button :agree, value: :policy diff --git a/app/views/conferences/_questions.html.haml b/app/views/conferences/_questions.html.haml index dbce807..c019a55 100644 --- a/app/views/conferences/_questions.html.haml +++ b/app/views/conferences/_questions.html.haml @@ -11,4 +11,4 @@ = emailfield :companion, (@registration.housing_data['companions'] || [nil]).first, heading: 'articles.conference_registration.headings.companion', help: 'articles.conference_registration.paragraphs.companion', big: true = textfield :allergies, @registration.allergies, heading: 'articles.conference_registration.headings.allergies' = textarea :other, @registration.other, plain: true, heading: 'articles.conference_registration.headings.other' - = button_tag :register, :value => :questions + = button :register, value: :questions diff --git a/app/views/conferences/_register.html.haml b/app/views/conferences/_register.html.haml index ff3125b..e058daf 100644 --- a/app/views/conferences/_register.html.haml +++ b/app/views/conferences/_register.html.haml @@ -8,4 +8,4 @@ .email-field.input-field = email_field_tag :email, nil, required: true = label_tag :email - = button_tag :register, :value => :register + = button :register, value: :register diff --git a/app/views/conferences/_registration_register.html.haml b/app/views/conferences/_registration_register.html.haml deleted file mode 100644 index 3f60166..0000000 --- a/app/views/conferences/_registration_register.html.haml +++ /dev/null @@ -1,45 +0,0 @@ -.columns{:class => @host_privledges ? 'medium-8' : 'medium-12'} - = f.fields_for @user do |u| - .columns#step-1.registration-step= u.email_field :email - %button#submit-email.next=_'form.Next' - = f.fields_for @user do |u| - .columns#step-2.registration-step= u.text_field :username - -# .columns.medium-6 - = select_tag :is_attending, options_for_select(ConferenceRegistration::AttendingOptions, @conference_registration.try(:is_attending)), :label => true - %ol - - @conference.registration_form_fields.each do |ff| - %li - - response = @conference_registration ? ConferenceRegistrationResponse.find_by(conference_registration_id: @conference_registration.id, registration_form_field_id: ff.id) : nil - = form_field ff, response -= f.actions :register - -- content_for :scripts do - :plain - function updateForm(data, step) { - $('.registration-step').each(function() { - var $this = $(this); - var this_step = parseInt($this.attr('id').replace(/^step\-(\d+)$/, '$1')); - if (this_step > step) { - $this.find('input,select').each(function(){ - var $input = $(this); - var params = $input.attr('name').match(/\[(.*?)\]/g); - var val = data.conference; - for (key in params) { - var k = params[key].replace(/\[(.*)\]/, '$1'); - val = val[params[key].replace(/\[(.*)\]/, '$1')]; - } - $input.val(val) - }); - } - }); - } - -- dom_ready do - :plain - $('#submit-email').click(function(e){ - e.preventDefault(); - $.post('register/step/1', $('form.edit_conference').serialize(), function(data) { - //console.log(data.conference.user.username); - updateForm(data, 1); - }); - }); diff --git a/app/views/conferences/_workshops.html.haml b/app/views/conferences/_workshops.html.haml index a40b2fd..997f57d 100644 --- a/app/views/conferences/_workshops.html.haml +++ b/app/views/conferences/_workshops.html.haml @@ -1,22 +1,22 @@ = columns(medium: 12, class: 'major-group') do - - if @my_workshops.present? - %h3=_'articles.conference_registration.headings.Your_Workshops' - %p=_'articles.conference_registration.paragraphs.Your_Workshops', :p - = render 'workshops/workshop_previews', :workshops => @my_workshops - - else - %p=_'articles.conference_registration.paragraphs.Create_Workshop', :p - = link_to (_'articles.conference_registration.headings.Add_Workshop'), create_workshop_path(@this_conference.slug), class: :button + - if @my_workshops.present? + %h3=_'articles.conference_registration.headings.Your_Workshops' + %p=_'articles.conference_registration.paragraphs.Your_Workshops', :p + = render 'workshops/workshop_previews', :workshops => @my_workshops + - else + %p=_'articles.conference_registration.paragraphs.Create_Workshop', :p + = link_to (_'articles.conference_registration.headings.Add_Workshop'), create_workshop_path(@this_conference.slug), class: :button - if @requested_workshops.present? - = columns(medium: 12, class: 'major-group') do - %h3=_'articles.conference_registration.headings.Workshops_You_Have_Requested' - %p=_'articles.conference_registration.paragraphs.Workshops_You_Have_Requested', :p - = render 'workshops/workshop_previews', :workshops => @requested_workshops + = columns(medium: 12, class: 'major-group') do + %h3=_'articles.conference_registration.headings.Workshops_You_Have_Requested' + %p=_'articles.conference_registration.paragraphs.Workshops_You_Have_Requested', :p + = render 'workshops/workshop_previews', :workshops => @requested_workshops - if @workshops_in_need.present? - = columns(medium: 12, class: 'major-group') do - %h3=_'articles.conference_registration.headings.Workshops_Looking_For_Facilitators' - %p=_'articles.conference_registration.paragraphs.Workshops_Looking_For_Facilitators', :p - = render 'workshops/workshop_previews', :workshops => @workshops_in_need + = columns(medium: 12, class: 'major-group') do + %h3=_'articles.conference_registration.headings.Workshops_Looking_For_Facilitators' + %p=_'articles.conference_registration.paragraphs.Workshops_Looking_For_Facilitators', :p + = render 'workshops/workshop_previews', :workshops => @workshops_in_need - if @workshops.present? - = columns(medium: 12, class: 'major-group') do - %h3=_'articles.conference_registration.headings.All_Workshops' - = render 'workshops/workshop_previews', :workshops => @workshops + = columns(medium: 12, class: 'major-group') do + %h3=_'articles.conference_registration.headings.All_Workshops' + = render 'workshops/workshop_previews', :workshops => @workshops diff --git a/app/views/conferences/broadcast.html.haml b/app/views/conferences/broadcast.html.haml index e409886..b1adf42 100644 --- a/app/views/conferences/broadcast.html.haml +++ b/app/views/conferences/broadcast.html.haml @@ -19,17 +19,17 @@ - else .text-field.input-field.big = label_tag :subject - = text_field_tag :subject, @subject, :required => true + = text_field_tag :subject, @subject, required: true .select-field.input-field = label_tag :content - = text_area_tag :content, @content, :required => true + = text_area_tag :content, @content, required: true .actions.right - if @email_sent == :test - .note=_('articles.conference_registration.notes.Test_Email_Sent',"An email was sent to #{current_user.email}", vars: {:email_address => current_user.email}) + .note=_('articles.conference_registration.notes.Test_Email_Sent',"An email was sent to #{current_user.email}", vars: {email_address: current_user.email}) - if @email_sent == :preview # let the user send the - = button_tag :edit, :value => :edit - = button_tag :send, :value => :send + = button :edit, value: :edit + = button :send, value: :send - else - = button_tag :preview, :value => :preview - = button_tag :test, :value => :test + = button :preview, value: :preview + = button :test, value: :test diff --git a/app/views/conferences/edit.html.haml b/app/views/conferences/edit.html.haml index 7b1e2bd..910ed13 100644 --- a/app/views/conferences/edit.html.haml +++ b/app/views/conferences/edit.html.haml @@ -1,30 +1,30 @@ -= render 'page_header', :page_key => 'Edit' += render 'page_header', page_key: 'Edit' %article - = row do - = columns(medium: 12) do - = form_tag save_conference_path(@this_conference.slug), class: 'composition' do - .text-editor.input-field - = label_tag :info - = text_area_tag :info, @this_conference.info, :required => true - .actions.right - .left - - I18n.backend.enabled_locales.each do |locale| - = (link_to (_'actions.conference.Translate', "Edit #{language_name(locale)} version", :vars => {:language => language_name(locale)}), edit_conference_path(@this_conference.slug, url_params(locale)), :class => 'translate') if locale.to_sym != I18n.locale.to_sym + = row do + = columns(medium: 12) do + = form_tag save_conference_path(@this_conference.slug), class: 'composition' do + .text-editor.input-field + = label_tag :info + = text_area_tag :info, @this_conference.info, :required => true + .actions.right + .left + - I18n.backend.enabled_locales.each do |locale| + = (link_to (_'actions.conference.Translate', "Edit #{language_name(locale)} version", vars: {:language => language_name(locale)}), edit_conference_path(@this_conference.slug, url_params(locale)), class: 'translate') if locale.to_sym != I18n.locale.to_sym - = button_tag :save, :value => :save + = button :save, value: :save / :javascript -/ window.jQuery || document.write('