From b80bd744ad873f6fc43018bc4bfb90677de167bd Mon Sep 17 00:00:00 2001 From: Stefan Wintermeyer Date: Mon, 17 Dec 2012 12:01:45 +0100 Subject: Start of GS5. --- app/assets/images/amooma-logo.png | Bin 0 -> 1314 bytes app/assets/images/bg-body.png | Bin 0 -> 1768 bytes .../images/gradients/light-to-dark-blue-x63.png | Bin 0 -> 187 bytes .../images/gradients/white-gray-x29-reverse.png | Bin 0 -> 123 bytes app/assets/images/gradients/white-gray-x29.png | Bin 0 -> 126 bytes app/assets/images/gradients/white-texture-x63.png | Bin 0 -> 6927 bytes app/assets/images/icons/cellphone-32x.png | Bin 0 -> 1938 bytes app/assets/images/icons/clock-32x.png | Bin 0 -> 5968 bytes app/assets/images/icons/cross-16x.png | Bin 0 -> 3350 bytes app/assets/images/icons/facebook-32x.png | Bin 0 -> 402 bytes app/assets/images/icons/fax-32x.png | Bin 0 -> 5762 bytes app/assets/images/icons/headphones-16x.png | Bin 0 -> 3285 bytes app/assets/images/icons/headphones-32x.png | Bin 0 -> 5906 bytes app/assets/images/icons/house-32x.png | Bin 0 -> 5741 bytes app/assets/images/icons/mic-32x.png | Bin 0 -> 5837 bytes app/assets/images/icons/microphone-16x.png | Bin 0 -> 3239 bytes app/assets/images/icons/microphone-32x.png | Bin 0 -> 5837 bytes app/assets/images/icons/mute-16x.png | Bin 0 -> 3349 bytes app/assets/images/icons/phone-down-32x.png | Bin 0 -> 5879 bytes app/assets/images/icons/phone-mobile-32x.png | Bin 0 -> 5509 bytes app/assets/images/icons/phone-up-32x.png | Bin 0 -> 5861 bytes app/assets/images/icons/search-13x16.png | Bin 0 -> 849 bytes app/assets/images/icons/skype-32x.png | Bin 0 -> 625 bytes app/assets/images/icons/star-16x.png | Bin 0 -> 3379 bytes app/assets/images/icons/suitcase-32x.png | Bin 0 -> 5550 bytes app/assets/images/icons/tag-16x.png | Bin 0 -> 3286 bytes app/assets/images/icons/twitter-32x.png | Bin 0 -> 397 bytes app/assets/images/icons/unmute-16x.png | Bin 0 -> 3373 bytes app/assets/images/icons/user-16x.png | Bin 0 -> 3287 bytes app/assets/images/icons/user-female-16x.png | Bin 0 -> 1680 bytes app/assets/images/icons/user-male-16x.png | Bin 0 -> 1559 bytes app/assets/images/logo.png | Bin 0 -> 7490 bytes app/assets/images/rails.png | Bin 0 -> 6646 bytes app/assets/images/stubs/user-36x.jpg | Bin 0 -> 2093 bytes app/assets/images/user.png | Bin 0 -> 4800 bytes app/assets/javascripts/api/rows.js.coffee | 3 + app/assets/javascripts/application.js | 13 + app/assets/javascripts/config_siemens.js.coffee | 3 + app/assets/javascripts/core.coffee | 5 + app/assets/javascripts/page.js.coffee | 3 + app/assets/javascripts/softkeys.js.coffee | 25 + app/assets/javascripts/vendor/autoresize.jquery.js | 94 ++ .../vendor/fancybox/jquery.easing-1.3.pack.js | 205 ++++ .../vendor/fancybox/jquery.fancybox-1.3.4.pack.js | 46 + .../fancybox/jquery.mousewheel-3.0.4.pack.js | 14 + app/assets/javascripts/vendor/html5boilerplate.js | 20 + app/assets/javascripts/vendor/jquery-1.6.2.min.js | 18 + app/assets/javascripts/vendor/jquery.condom.js | 52 + .../javascripts/vendor/jquery.easy-slider-1.7.js | 225 ++++ .../javascripts/vendor/jquery.survival-kit.coffee | 36 + app/assets/javascripts/vendor/jquery.tmpl.js | 486 ++++++++ .../javascripts/vendor/modernizr-2.0.6.min.js | 1116 ++++++++++++++++++ app/assets/stylesheets/api/rows.css.scss | 3 + app/assets/stylesheets/app/layouts/_app.scss | 24 + .../stylesheets/app/layouts/_conference.scss | 136 +++ .../stylesheets/app/layouts/_phone-book-entry.scss | 176 +++ app/assets/stylesheets/app/pages/_phone_book.scss | 25 + app/assets/stylesheets/app/shared/_contents.scss | 374 ++++++ app/assets/stylesheets/app/shared/_footers.scss | 90 ++ app/assets/stylesheets/app/shared/_handheld.scss | 25 + app/assets/stylesheets/app/shared/_headers.scss | 145 +++ app/assets/stylesheets/app/shared/_ie.scss | 7 + app/assets/stylesheets/app/shared/_media.scss | 16 + app/assets/stylesheets/app/shared/_print.scss | 17 + app/assets/stylesheets/application.css.scss | 117 ++ app/assets/stylesheets/scaffolds.css.scss | 56 + app/assets/stylesheets/vendor/README | 1 + .../stylesheets/vendor/boilerplate-1.0/README | 15 + .../stylesheets/vendor/boilerplate-1.0/_reset.scss | 37 + .../vendor/boilerplate-1.0/_styles.scss | 171 +++ .../stylesheets/vendor/boilerplate-2.0/README | 16 + .../vendor/boilerplate-2.0/_styles.scss | 209 ++++ .../stylesheets/vendor/easy-slider/_numeric.scss | 44 + .../stylesheets/vendor/facebox/_facebox.scss | 85 ++ app/assets/stylesheets/vendor/fancy-box/README | 4 + .../stylesheets/vendor/fancy-box/_fancy-box.scss | 336 ++++++ app/assets/stylesheets/vendor/fancy-buttons/README | 3 + .../vendor/fancy-buttons/_fancy-buttons.scss | 195 +++ .../vendor/fancy-buttons/_fancy-gradient.scss | 28 + .../stylesheets/vendor/survival-kit/_blog.scss | 99 ++ .../stylesheets/vendor/survival-kit/_effects.scss | 97 ++ .../stylesheets/vendor/survival-kit/_forms.scss | 313 +++++ .../stylesheets/vendor/survival-kit/_headers.scss | 36 + .../stylesheets/vendor/survival-kit/_images.scss | 121 ++ .../stylesheets/vendor/survival-kit/_lists.scss | 37 + .../stylesheets/vendor/survival-kit/_loader.scss | 11 + .../vendor/survival-kit/_navigation.scss | 230 ++++ .../stylesheets/vendor/survival-kit/_secure.scss | 3 + .../stylesheets/vendor/survival-kit/_tools.scss | 267 +++++ .../access_authorizations_controller.rb | 68 ++ app/controllers/acd_agents_controller.rb | 73 ++ app/controllers/acd_callers_controller.rb | 41 + app/controllers/addresses_controller.rb | 41 + app/controllers/api/rows_controller.rb | 91 ++ app/controllers/application_controller.rb | 161 +++ .../automatic_call_distributors_controller.rb | 100 ++ app/controllers/call_forwards_controller.rb | 127 ++ app/controllers/call_histories_controller.rb | 100 ++ app/controllers/calls_controller.rb | 6 + app/controllers/callthroughs_controller.rb | 75 ++ app/controllers/conference_invitees_controller.rb | 93 ++ app/controllers/conferences_controller.rb | 82 ++ app/controllers/config_polycom_controller.rb | 330 ++++++ app/controllers/config_siemens_controller.rb | 1239 ++++++++++++++++++++ app/controllers/config_siemens_sort_controller.rb | 371 ++++++ app/controllers/config_snom_controller.rb | 1169 ++++++++++++++++++ app/controllers/fax_accounts_controller.rb | 82 ++ app/controllers/fax_documents_controller.rb | 82 ++ .../freeswitch_voicemail_msgs_controller.rb | 7 + app/controllers/gemeinschaft_setups_controller.rb | 61 + .../gs_cluster_sync_log_entries_controller.rb | 25 + app/controllers/gs_nodes_controller.rb | 170 +++ app/controllers/gui_functions_controller.rb | 73 ++ app/controllers/hunt_group_members_controller.rb | 67 ++ app/controllers/hunt_groups_controller.rb | 55 + app/controllers/manufacturers_controller.rb | 49 + app/controllers/page_controller.rb | 24 + app/controllers/phone_book_entries_controller.rb | 135 +++ app/controllers/phone_books_controller.rb | 105 ++ app/controllers/phone_models_controller.rb | 52 + app/controllers/phone_number_ranges_controller.rb | 56 + app/controllers/phone_numbers_controller.rb | 226 ++++ app/controllers/phone_sip_accounts_controller.rb | 60 + app/controllers/phones_controller.rb | 72 ++ app/controllers/ringtones_controller.rb | 67 ++ app/controllers/sessions_controller.rb | 44 + app/controllers/sip_accounts_controller.rb | 98 ++ app/controllers/sip_domains_controller.rb | 41 + app/controllers/softkeys_controller.rb | 91 ++ app/controllers/system_messages_controller.rb | 30 + app/controllers/tenants_controller.rb | 91 ++ .../user_group_memberships_controller.rb | 48 + app/controllers/user_groups_controller.rb | 69 ++ app/controllers/users_controller.rb | 85 ++ app/controllers/voicemail_messages_controller.rb | 140 +++ app/controllers/voicemail_settings_controller.rb | 91 ++ app/controllers/whitelists_controller.rb | 61 + app/helpers/access_authorizations_helper.rb | 2 + app/helpers/acd_agents_helper.rb | 2 + app/helpers/acd_callers_helper.rb | 2 + app/helpers/addresses_helper.rb | 2 + app/helpers/api/rows_helper.rb | 2 + app/helpers/application_helper.rb | 2 + app/helpers/automatic_call_distributors_helper.rb | 2 + app/helpers/call_forward_cases_helper.rb | 2 + app/helpers/call_forwards_helper.rb | 2 + app/helpers/callthroughs_helper.rb | 2 + app/helpers/conference_invitees_helper.rb | 2 + app/helpers/conferences_helper.rb | 2 + app/helpers/config_siemens_helper.rb | 2 + app/helpers/error_messages_helper.rb | 23 + app/helpers/fax_accounts_helper.rb | 2 + app/helpers/fax_documents_helper.rb | 2 + app/helpers/gemeinschaft_setups_helper.rb | 2 + app/helpers/gs_cluster_sync_log_entries_helper.rb | 2 + app/helpers/gs_nodes_helper.rb | 2 + app/helpers/gui_functions_helper.rb | 2 + app/helpers/hunt_group_members_helper.rb | 2 + app/helpers/hunt_groups_helper.rb | 2 + app/helpers/layout_helper.rb | 70 ++ app/helpers/manufacturers_helper.rb | 2 + app/helpers/page_helper.rb | 2 + app/helpers/phone_book_entries_helper.rb | 2 + app/helpers/phone_books_helper.rb | 2 + app/helpers/phone_models_helper.rb | 2 + app/helpers/phone_number_ranges_helper.rb | 2 + app/helpers/phone_numbers_helper.rb | 2 + app/helpers/phone_sip_accounts_helper.rb | 2 + app/helpers/phones_helper.rb | 2 + app/helpers/phones_sip_accounts_helper.rb | 2 + app/helpers/ringtones_helper.rb | 2 + app/helpers/sessions_helper.rb | 2 + app/helpers/sip_accounts_helper.rb | 2 + app/helpers/sip_domains_helper.rb | 2 + app/helpers/softkeys_helper.rb | 2 + app/helpers/system_messages_helper.rb | 2 + app/helpers/tenants_helper.rb | 2 + app/helpers/user_groups_helper.rb | 2 + app/helpers/users_helper.rb | 2 + app/helpers/whitelists_helper.rb | 2 + app/mailers/.gitkeep | 0 app/mailers/notifications.rb | 110 ++ app/models/.gitkeep | 0 app/models/ability.rb | 170 +++ app/models/access_authorization.rb | 41 + app/models/acd_agent.rb | 39 + app/models/acd_caller.rb | 6 + app/models/address.rb | 8 + app/models/api.rb | 5 + app/models/api/row.rb | 152 +++ app/models/area_code.rb | 22 + app/models/automatic_call_distributor.rb | 21 + app/models/call.rb | 36 + app/models/call_forward.rb | 262 +++++ app/models/call_forward_case.rb | 13 + app/models/call_history.rb | 199 ++++ app/models/callthrough.rb | 60 + app/models/conference.rb | 63 + app/models/conference_invitee.rb | 39 + app/models/country.rb | 21 + app/models/dial_in_number_store.rb | 16 + app/models/fax_account.rb | 77 ++ app/models/fax_document.rb | 82 ++ app/models/fax_resolution.rb | 15 + app/models/fax_thumbnail.rb | 8 + app/models/freeswitch_alias.rb | 23 + app/models/freeswitch_call.rb | 24 + app/models/freeswitch_cdr.rb | 4 + app/models/freeswitch_channel.rb | 24 + app/models/freeswitch_complete.rb | 23 + app/models/freeswitch_fifo_bridge.rb | 23 + app/models/freeswitch_fifo_caller.rb | 23 + app/models/freeswitch_fifo_outbound.rb | 23 + app/models/freeswitch_interface.rb | 23 + app/models/freeswitch_nat.rb | 23 + app/models/freeswitch_registration.rb | 23 + app/models/freeswitch_task.rb | 23 + app/models/freeswitch_voicemail_pref.rb | 23 + app/models/gemeinschaft_setup.rb | 8 + app/models/gs_cluster_sync_log_entry.rb | 99 ++ app/models/gs_node.rb | 30 + app/models/gui_function.rb | 40 + app/models/gui_function_membership.rb | 7 + app/models/hunt_group.rb | 43 + app/models/hunt_group_member.rb | 67 ++ app/models/language.rb | 11 + app/models/manufacturer.rb | 46 + app/models/oui.rb | 17 + app/models/phone.rb | 240 ++++ app/models/phone_book.rb | 33 + app/models/phone_book_entry.rb | 109 ++ app/models/phone_model.rb | 56 + app/models/phone_number.rb | 304 +++++ app/models/phone_number_range.rb | 16 + app/models/phone_sip_account.rb | 17 + .../remote_gs_node/gs_cluster_sync_log_entry.rb | 9 + app/models/ringtone.rb | 15 + app/models/sip_account.rb | 221 ++++ app/models/sip_domain.rb | 16 + app/models/softkey.rb | 100 ++ app/models/softkey_function.rb | 13 + app/models/system_message.rb | 7 + app/models/tenant.rb | 243 ++++ app/models/tenant_membership.rb | 25 + app/models/user.rb | 208 ++++ app/models/user_group.rb | 33 + app/models/user_group_membership.rb | 26 + app/models/voicemail_message.rb | 52 + app/models/voicemail_setting.rb | 12 + app/models/whitelist.rb | 23 + app/uploaders/audio_uploader.rb | 49 + app/uploaders/document_uploader.rb | 49 + app/uploaders/image_uploader.rb | 63 + app/uploaders/thumbnail_uploader.rb | 62 + app/uploaders/tiff_uploader.rb | 49 + app/views/access_authorizations/_form.html.haml | 7 + .../access_authorizations/_form_core.html.haml | 11 + .../access_authorizations/_index_core.html.haml | 21 + app/views/access_authorizations/edit.html.haml | 3 + app/views/access_authorizations/index.html.haml | 6 + app/views/access_authorizations/new.html.haml | 3 + app/views/access_authorizations/show.html.haml | 22 + app/views/acd_agents/_form.html.haml | 7 + app/views/acd_agents/_form_core.html.haml | 7 + app/views/acd_agents/_index_core.html.haml | 18 + app/views/acd_agents/_listing.html.haml | 8 + app/views/acd_agents/edit.html.haml | 3 + app/views/acd_agents/index.html.haml | 6 + app/views/acd_agents/new.html.haml | 3 + app/views/acd_agents/show.html.haml | 28 + app/views/acd_callers/_index_core.html.haml | 21 + app/views/acd_callers/index.html.haml | 6 + app/views/acd_callers/show.html.haml | 25 + app/views/addresses/_form.html.haml | 7 + app/views/addresses/_form_core.html.haml | 9 + app/views/addresses/_index_core.html.haml | 23 + app/views/addresses/edit.html.haml | 9 + app/views/addresses/index.html.haml | 6 + app/views/addresses/new.html.haml | 3 + app/views/addresses/show.html.haml | 28 + app/views/api/rows/_form.html.erb | 21 + app/views/api/rows/edit.html.erb | 3 + app/views/api/rows/index.html.erb | 29 + app/views/api/rows/new.html.erb | 3 + app/views/api/rows/show.html.erb | 56 + .../automatic_call_distributors/_form.html.haml | 8 + .../_form_core.html.haml | 16 + .../_index_core.html.haml | 39 + .../automatic_call_distributors/edit.html.haml | 3 + .../automatic_call_distributors/index.html.haml | 6 + .../automatic_call_distributors/new.html.haml | 3 + .../automatic_call_distributors/show.html.haml | 59 + app/views/call_forwards/_form.html.haml | 7 + app/views/call_forwards/_form_core.html.haml | 15 + app/views/call_forwards/_index_core.html.haml | 31 + app/views/call_forwards/edit.html.haml | 3 + app/views/call_forwards/index.html.haml | 6 + app/views/call_forwards/new.html.haml | 3 + app/views/call_forwards/show.html.haml | 33 + app/views/call_histories/_index_core.html.haml | 65 + app/views/call_histories/_navigation.html.haml | 11 + app/views/call_histories/index.html.haml | 6 + app/views/calls/_index_core.html.haml | 9 + app/views/calls/index.html.haml | 6 + app/views/callthroughs/_form.html.haml | 7 + app/views/callthroughs/_form_core.html.haml | 24 + app/views/callthroughs/_index_core.html.haml | 17 + app/views/callthroughs/edit.html.haml | 3 + app/views/callthroughs/index.html.haml | 6 + app/views/callthroughs/new.html.haml | 3 + app/views/callthroughs/show.html.haml | 27 + app/views/conference_invitees/_form.html.haml | 7 + app/views/conference_invitees/_form_core.html.haml | 7 + .../conference_invitees/_index_core.html.haml | 17 + app/views/conference_invitees/edit.html.haml | 3 + app/views/conference_invitees/index.html.haml | 6 + app/views/conference_invitees/new.html.haml | 3 + app/views/conference_invitees/show.html.haml | 20 + app/views/conferences/_form.html.haml | 7 + app/views/conferences/_form_core.html.haml | 11 + app/views/conferences/_index_core.html.haml | 53 + app/views/conferences/edit.html.haml | 3 + app/views/conferences/index.html.haml | 6 + app/views/conferences/new.html.haml | 3 + app/views/conferences/show.html.haml | 43 + app/views/config_polycom/_call_history.xml.haml | 18 + .../config_polycom/_call_history_menu.xml.haml | 13 + app/views/config_polycom/_phone_book.xml.haml | 18 + app/views/config_polycom/config_files.xml.builder | 12 + app/views/config_polycom/idle_screen.xml.haml | 7 + app/views/config_polycom/settings.xml.erb | 8 + .../config_polycom/settings_directory.xml.haml | 16 + app/views/config_siemens/_menu_list.xml.haml | 33 + app/views/config_siemens/clean-up.xml.erb | 5 + app/views/config_siemens/index.xml.erb | 5 + app/views/config_siemens/write.xml.erb | 10 + .../config_snom/_snom_phone_directory.xml.haml | 19 + app/views/config_snom/_snom_phone_input.xml.haml | 19 + app/views/config_snom/_snom_phone_menu.xml.haml | 17 + app/views/config_snom/_snom_phone_text.xml.haml | 16 + app/views/config_snom/call_history.xml.haml | 2 + app/views/config_snom/idle_screen.xml.haml | 33 + app/views/config_snom/log_in.xml.haml | 3 + app/views/config_snom/show.xml.haml | 151 +++ app/views/config_snom/state_settings.xml.haml | 49 + app/views/config_snom/switch_protocol.xml.builder | 18 + app/views/fax_accounts/_form.html.haml | 7 + app/views/fax_accounts/_form_core.html.haml | 11 + app/views/fax_accounts/_index_core.html.haml | 35 + app/views/fax_accounts/edit.html.haml | 3 + app/views/fax_accounts/index.html.haml | 6 + app/views/fax_accounts/new.html.haml | 3 + app/views/fax_accounts/show.html.haml | 21 + app/views/fax_documents/_form.html.haml | 7 + app/views/fax_documents/_form_core.html.haml | 7 + app/views/fax_documents/_index_core.html.haml | 33 + app/views/fax_documents/edit.html.haml | 9 + app/views/fax_documents/index.html.haml | 5 + app/views/fax_documents/new.html.haml | 3 + app/views/fax_documents/show.html.haml | 36 + .../_index_core.html.haml | 12 + .../freeswitch_voicemail_msgs/index.html.haml | 3 + app/views/gemeinschaft_setups/new.de.html.haml | 25 + app/views/gemeinschaft_setups/new.html.haml | 25 + .../gs_cluster_sync_log_entries/_form.html.haml | 7 + .../_form_core.html.haml | 6 + .../_index_core.html.haml | 17 + .../gs_cluster_sync_log_entries/edit.html.haml | 3 + .../gs_cluster_sync_log_entries/index.html.haml | 6 + .../gs_cluster_sync_log_entries/new.html.haml | 3 + .../gs_cluster_sync_log_entries/show.html.haml | 19 + app/views/gs_nodes/_form.html.haml | 7 + app/views/gs_nodes/_form_core.html.haml | 7 + app/views/gs_nodes/_index_core.html.haml | 19 + app/views/gs_nodes/edit.html.haml | 3 + app/views/gs_nodes/index.html.haml | 6 + app/views/gs_nodes/new.html.haml | 3 + app/views/gs_nodes/show.html.haml | 22 + app/views/gs_nodes/sync.xml.haml | 88 ++ app/views/gui_functions/_form.html.haml | 7 + app/views/gui_functions/_form_core.html.haml | 10 + app/views/gui_functions/_index_core.html.haml | 26 + app/views/gui_functions/edit.html.haml | 3 + app/views/gui_functions/index.html.haml | 6 + app/views/gui_functions/new.html.haml | 3 + app/views/gui_functions/show.html.haml | 18 + app/views/hunt_group_members/_form.html.haml | 7 + app/views/hunt_group_members/_form_core.html.haml | 4 + app/views/hunt_group_members/_index_core.html.haml | 20 + app/views/hunt_group_members/_listing.html.haml | 8 + app/views/hunt_group_members/edit.html.haml | 3 + app/views/hunt_group_members/index.html.haml | 6 + app/views/hunt_group_members/new.html.haml | 3 + app/views/hunt_group_members/show.html.haml | 19 + app/views/hunt_groups/_form.html.haml | 7 + app/views/hunt_groups/_form_core.html.haml | 4 + app/views/hunt_groups/_index_core.html.haml | 34 + app/views/hunt_groups/edit.html.haml | 3 + app/views/hunt_groups/index.html.haml | 6 + app/views/hunt_groups/new.html.haml | 3 + app/views/hunt_groups/show.html.haml | 26 + app/views/layouts/application.html.haml | 46 + app/views/manufacturers/_form.html.haml | 7 + app/views/manufacturers/_form_core.html.haml | 4 + app/views/manufacturers/_index_core.html.haml | 18 + app/views/manufacturers/edit.html.haml | 3 + app/views/manufacturers/index.html.haml | 5 + app/views/manufacturers/new.html.haml | 3 + app/views/manufacturers/show.html.haml | 18 + app/views/notifications/new_fax.text.erb | 8 + app/views/notifications/new_password.text.erb | 3 + app/views/notifications/new_pin.text.erb | 7 + app/views/notifications/new_voicemail.text.erb | 9 + app/views/page/conference.html.haml | 80 ++ app/views/page/index.de.html.haml | 16 + app/views/page/index.html.haml | 16 + app/views/phone_book_entries/_form.html.haml | 7 + app/views/phone_book_entries/_form_core.html.haml | 27 + .../phone_book_entries/_index_core.de.html.haml | 36 + app/views/phone_book_entries/_index_core.html.haml | 36 + app/views/phone_book_entries/_navigation.html.haml | 8 + app/views/phone_book_entries/edit.html.haml | 9 + app/views/phone_book_entries/index.html.haml | 19 + app/views/phone_book_entries/new.html.haml | 3 + app/views/phone_book_entries/show.html.haml | 146 +++ .../phone_book_entries/show.html.haml.examlple | 194 +++ app/views/phone_books/_form.html.haml | 7 + app/views/phone_books/_form_core.html.haml | 3 + app/views/phone_books/_index_core.html.haml | 16 + app/views/phone_books/edit.html.haml | 3 + app/views/phone_books/index.html.haml | 6 + app/views/phone_books/new.html.haml | 3 + app/views/phone_books/show.html.haml | 13 + app/views/phone_models/_form.html.haml | 7 + app/views/phone_models/_form_core.html.haml | 4 + app/views/phone_models/_index_core.html.haml | 19 + app/views/phone_models/edit.html.haml | 3 + app/views/phone_models/index.html.haml | 6 + app/views/phone_models/new.html.haml | 3 + app/views/phone_models/show.html.haml | 18 + app/views/phone_number_ranges/_form.html.haml | 7 + app/views/phone_number_ranges/_form_core.html.haml | 3 + .../phone_number_ranges/_index_core.html.haml | 21 + app/views/phone_number_ranges/edit.html.haml | 3 + app/views/phone_number_ranges/index.html.haml | 6 + app/views/phone_number_ranges/new.html.haml | 3 + app/views/phone_number_ranges/show.html.haml | 18 + app/views/phone_numbers/_form.html.haml | 7 + app/views/phone_numbers/_form_core.html.haml | 10 + app/views/phone_numbers/_index_core.html.haml | 13 + app/views/phone_numbers/_listing.html.haml | 8 + app/views/phone_numbers/edit.html.haml | 3 + app/views/phone_numbers/index.html.haml | 6 + app/views/phone_numbers/new.html.haml | 3 + app/views/phone_numbers/show.html.haml | 27 + app/views/phone_sip_accounts/_form.html.haml | 7 + app/views/phone_sip_accounts/_form_core.html.haml | 2 + app/views/phone_sip_accounts/_index_core.html.haml | 13 + app/views/phone_sip_accounts/index.html.haml | 6 + app/views/phone_sip_accounts/new.html.haml | 3 + app/views/phone_sip_accounts/show.html.haml | 13 + app/views/phones/_form.html.haml | 7 + app/views/phones/_form_core.html.haml | 8 + app/views/phones/_index_core.html.haml | 15 + app/views/phones/edit.html.haml | 3 + app/views/phones/index.html.haml | 6 + app/views/phones/new.html.haml | 3 + app/views/phones/show.html.haml | 31 + app/views/ringtones/_form.html.haml | 7 + app/views/ringtones/_form_core.html.haml | 3 + app/views/ringtones/_index_core.html.haml | 11 + app/views/ringtones/edit.html.haml | 3 + app/views/ringtones/index.html.haml | 6 + app/views/ringtones/new.html.haml | 3 + app/views/ringtones/show.html.haml | 12 + app/views/sessions/new.html.haml | 8 + app/views/shared/_create_link.html.haml | 11 + app/views/shared/_flash.html.haml | 19 + app/views/shared/_header.de.html.haml | 41 + app/views/shared/_header.html.haml | 41 + .../shared/_index_view_edit_destroy_part.html.haml | 29 + app/views/shared/_show_edit_destroy_part.html.haml | 16 + app/views/shared/_system_message.html.haml | 10 + app/views/sip_accounts/_form.html.haml | 7 + app/views/sip_accounts/_form_core.html.haml | 12 + app/views/sip_accounts/_index_core.html.haml | 28 + app/views/sip_accounts/edit.html.haml | 3 + app/views/sip_accounts/index.html.haml | 6 + app/views/sip_accounts/new.html.haml | 3 + app/views/sip_accounts/show.html.haml | 50 + app/views/sip_domains/_form.html.haml | 7 + app/views/sip_domains/_form_core.html.haml | 3 + app/views/sip_domains/_index_core.html.haml | 11 + app/views/sip_domains/edit.html.haml | 3 + app/views/sip_domains/index.html.haml | 6 + app/views/sip_domains/new.html.haml | 3 + app/views/sip_domains/show.html.haml | 10 + app/views/softkeys/_form.html.haml | 7 + app/views/softkeys/_form_core.html.haml | 12 + app/views/softkeys/_index_core.html.haml | 14 + app/views/softkeys/edit.html.haml | 3 + app/views/softkeys/index.html.haml | 6 + app/views/softkeys/new.html.haml | 3 + app/views/softkeys/show.html.haml | 7 + app/views/system_messages/_form.html.haml | 7 + app/views/system_messages/_form_core.html.haml | 2 + app/views/system_messages/_index_core.html.haml | 11 + app/views/system_messages/index.html.haml | 3 + app/views/system_messages/new.html.haml | 3 + app/views/system_messages/show.html.haml | 8 + app/views/tenants/_admin_area.de.html.haml | 118 ++ app/views/tenants/_admin_area.html.haml | 116 ++ app/views/tenants/_form.html.haml | 24 + app/views/tenants/_form_core.html.haml | 3 + app/views/tenants/_index_core.html.haml | 17 + app/views/tenants/edit.html.haml | 3 + app/views/tenants/index.html.haml | 6 + app/views/tenants/new.html.haml | 3 + app/views/tenants/show.html.haml | 14 + app/views/user_group_memberships/_form.html.haml | 7 + .../user_group_memberships/_form_core.html.haml | 2 + .../user_group_memberships/_index_core.html.haml | 13 + app/views/user_group_memberships/edit.html.haml | 3 + app/views/user_group_memberships/index.html.haml | 7 + app/views/user_group_memberships/new.html.haml | 3 + app/views/user_group_memberships/show.html.haml | 7 + app/views/user_groups/_form.html.haml | 7 + app/views/user_groups/_form_core.html.haml | 3 + app/views/user_groups/_index_core.html.haml | 24 + app/views/user_groups/edit.html.haml | 3 + app/views/user_groups/index.html.haml | 6 + app/views/user_groups/new.html.haml | 3 + app/views/user_groups/show.html.haml | 20 + app/views/users/_form.html.haml | 16 + app/views/users/_form_core.html.haml | 27 + app/views/users/_index_core.html.haml | 18 + app/views/users/_listing.html.haml | 8 + app/views/users/edit.html.haml | 3 + app/views/users/index.html.haml | 6 + app/views/users/new.html.haml | 3 + app/views/users/show.html.haml | 96 ++ app/views/voicemail_messages/_index_core.html.haml | 44 + app/views/voicemail_messages/_navigation.html.haml | 9 + app/views/voicemail_messages/index.html.haml | 6 + app/views/voicemail_settings/_form.html.haml | 7 + app/views/voicemail_settings/_form_core.html.haml | 11 + app/views/voicemail_settings/edit.html.haml | 3 + app/views/voicemail_settings/show.html.haml | 26 + app/views/whitelists/_form.html.haml | 7 + app/views/whitelists/_form_core.html.haml | 8 + app/views/whitelists/_index_core.html.haml | 15 + app/views/whitelists/edit.html.haml | 3 + app/views/whitelists/index.html.haml | 6 + app/views/whitelists/new.html.haml | 3 + app/views/whitelists/show.html.haml | 7 + 555 files changed, 21265 insertions(+) create mode 100644 app/assets/images/amooma-logo.png create mode 100644 app/assets/images/bg-body.png create mode 100644 app/assets/images/gradients/light-to-dark-blue-x63.png create mode 100644 app/assets/images/gradients/white-gray-x29-reverse.png create mode 100644 app/assets/images/gradients/white-gray-x29.png create mode 100644 app/assets/images/gradients/white-texture-x63.png create mode 100644 app/assets/images/icons/cellphone-32x.png create mode 100644 app/assets/images/icons/clock-32x.png create mode 100644 app/assets/images/icons/cross-16x.png create mode 100644 app/assets/images/icons/facebook-32x.png create mode 100644 app/assets/images/icons/fax-32x.png create mode 100644 app/assets/images/icons/headphones-16x.png create mode 100644 app/assets/images/icons/headphones-32x.png create mode 100644 app/assets/images/icons/house-32x.png create mode 100644 app/assets/images/icons/mic-32x.png create mode 100644 app/assets/images/icons/microphone-16x.png create mode 100644 app/assets/images/icons/microphone-32x.png create mode 100644 app/assets/images/icons/mute-16x.png create mode 100644 app/assets/images/icons/phone-down-32x.png create mode 100644 app/assets/images/icons/phone-mobile-32x.png create mode 100644 app/assets/images/icons/phone-up-32x.png create mode 100644 app/assets/images/icons/search-13x16.png create mode 100644 app/assets/images/icons/skype-32x.png create mode 100644 app/assets/images/icons/star-16x.png create mode 100644 app/assets/images/icons/suitcase-32x.png create mode 100644 app/assets/images/icons/tag-16x.png create mode 100644 app/assets/images/icons/twitter-32x.png create mode 100644 app/assets/images/icons/unmute-16x.png create mode 100644 app/assets/images/icons/user-16x.png create mode 100644 app/assets/images/icons/user-female-16x.png create mode 100644 app/assets/images/icons/user-male-16x.png create mode 100644 app/assets/images/logo.png create mode 100644 app/assets/images/rails.png create mode 100644 app/assets/images/stubs/user-36x.jpg create mode 100644 app/assets/images/user.png create mode 100644 app/assets/javascripts/api/rows.js.coffee create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/javascripts/config_siemens.js.coffee create mode 100644 app/assets/javascripts/core.coffee create mode 100644 app/assets/javascripts/page.js.coffee create mode 100644 app/assets/javascripts/softkeys.js.coffee create mode 100644 app/assets/javascripts/vendor/autoresize.jquery.js create mode 100755 app/assets/javascripts/vendor/fancybox/jquery.easing-1.3.pack.js create mode 100755 app/assets/javascripts/vendor/fancybox/jquery.fancybox-1.3.4.pack.js create mode 100755 app/assets/javascripts/vendor/fancybox/jquery.mousewheel-3.0.4.pack.js create mode 100644 app/assets/javascripts/vendor/html5boilerplate.js create mode 100755 app/assets/javascripts/vendor/jquery-1.6.2.min.js create mode 100644 app/assets/javascripts/vendor/jquery.condom.js create mode 100644 app/assets/javascripts/vendor/jquery.easy-slider-1.7.js create mode 100644 app/assets/javascripts/vendor/jquery.survival-kit.coffee create mode 100644 app/assets/javascripts/vendor/jquery.tmpl.js create mode 100755 app/assets/javascripts/vendor/modernizr-2.0.6.min.js create mode 100644 app/assets/stylesheets/api/rows.css.scss create mode 100644 app/assets/stylesheets/app/layouts/_app.scss create mode 100644 app/assets/stylesheets/app/layouts/_conference.scss create mode 100644 app/assets/stylesheets/app/layouts/_phone-book-entry.scss create mode 100644 app/assets/stylesheets/app/pages/_phone_book.scss create mode 100644 app/assets/stylesheets/app/shared/_contents.scss create mode 100644 app/assets/stylesheets/app/shared/_footers.scss create mode 100644 app/assets/stylesheets/app/shared/_handheld.scss create mode 100644 app/assets/stylesheets/app/shared/_headers.scss create mode 100644 app/assets/stylesheets/app/shared/_ie.scss create mode 100644 app/assets/stylesheets/app/shared/_media.scss create mode 100644 app/assets/stylesheets/app/shared/_print.scss create mode 100644 app/assets/stylesheets/application.css.scss create mode 100644 app/assets/stylesheets/scaffolds.css.scss create mode 100644 app/assets/stylesheets/vendor/README create mode 100644 app/assets/stylesheets/vendor/boilerplate-1.0/README create mode 100644 app/assets/stylesheets/vendor/boilerplate-1.0/_reset.scss create mode 100644 app/assets/stylesheets/vendor/boilerplate-1.0/_styles.scss create mode 100644 app/assets/stylesheets/vendor/boilerplate-2.0/README create mode 100644 app/assets/stylesheets/vendor/boilerplate-2.0/_styles.scss create mode 100644 app/assets/stylesheets/vendor/easy-slider/_numeric.scss create mode 100644 app/assets/stylesheets/vendor/facebox/_facebox.scss create mode 100644 app/assets/stylesheets/vendor/fancy-box/README create mode 100755 app/assets/stylesheets/vendor/fancy-box/_fancy-box.scss create mode 100644 app/assets/stylesheets/vendor/fancy-buttons/README create mode 100644 app/assets/stylesheets/vendor/fancy-buttons/_fancy-buttons.scss create mode 100644 app/assets/stylesheets/vendor/fancy-buttons/_fancy-gradient.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_blog.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_effects.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_forms.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_headers.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_images.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_lists.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_loader.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_navigation.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_secure.scss create mode 100644 app/assets/stylesheets/vendor/survival-kit/_tools.scss create mode 100644 app/controllers/access_authorizations_controller.rb create mode 100644 app/controllers/acd_agents_controller.rb create mode 100644 app/controllers/acd_callers_controller.rb create mode 100644 app/controllers/addresses_controller.rb create mode 100644 app/controllers/api/rows_controller.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/automatic_call_distributors_controller.rb create mode 100644 app/controllers/call_forwards_controller.rb create mode 100644 app/controllers/call_histories_controller.rb create mode 100644 app/controllers/calls_controller.rb create mode 100644 app/controllers/callthroughs_controller.rb create mode 100644 app/controllers/conference_invitees_controller.rb create mode 100644 app/controllers/conferences_controller.rb create mode 100644 app/controllers/config_polycom_controller.rb create mode 100644 app/controllers/config_siemens_controller.rb create mode 100644 app/controllers/config_siemens_sort_controller.rb create mode 100644 app/controllers/config_snom_controller.rb create mode 100644 app/controllers/fax_accounts_controller.rb create mode 100644 app/controllers/fax_documents_controller.rb create mode 100644 app/controllers/freeswitch_voicemail_msgs_controller.rb create mode 100644 app/controllers/gemeinschaft_setups_controller.rb create mode 100644 app/controllers/gs_cluster_sync_log_entries_controller.rb create mode 100644 app/controllers/gs_nodes_controller.rb create mode 100644 app/controllers/gui_functions_controller.rb create mode 100644 app/controllers/hunt_group_members_controller.rb create mode 100644 app/controllers/hunt_groups_controller.rb create mode 100644 app/controllers/manufacturers_controller.rb create mode 100644 app/controllers/page_controller.rb create mode 100644 app/controllers/phone_book_entries_controller.rb create mode 100644 app/controllers/phone_books_controller.rb create mode 100644 app/controllers/phone_models_controller.rb create mode 100644 app/controllers/phone_number_ranges_controller.rb create mode 100644 app/controllers/phone_numbers_controller.rb create mode 100644 app/controllers/phone_sip_accounts_controller.rb create mode 100644 app/controllers/phones_controller.rb create mode 100644 app/controllers/ringtones_controller.rb create mode 100644 app/controllers/sessions_controller.rb create mode 100644 app/controllers/sip_accounts_controller.rb create mode 100644 app/controllers/sip_domains_controller.rb create mode 100644 app/controllers/softkeys_controller.rb create mode 100644 app/controllers/system_messages_controller.rb create mode 100644 app/controllers/tenants_controller.rb create mode 100644 app/controllers/user_group_memberships_controller.rb create mode 100644 app/controllers/user_groups_controller.rb create mode 100644 app/controllers/users_controller.rb create mode 100644 app/controllers/voicemail_messages_controller.rb create mode 100644 app/controllers/voicemail_settings_controller.rb create mode 100644 app/controllers/whitelists_controller.rb create mode 100644 app/helpers/access_authorizations_helper.rb create mode 100644 app/helpers/acd_agents_helper.rb create mode 100644 app/helpers/acd_callers_helper.rb create mode 100644 app/helpers/addresses_helper.rb create mode 100644 app/helpers/api/rows_helper.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/helpers/automatic_call_distributors_helper.rb create mode 100644 app/helpers/call_forward_cases_helper.rb create mode 100644 app/helpers/call_forwards_helper.rb create mode 100644 app/helpers/callthroughs_helper.rb create mode 100644 app/helpers/conference_invitees_helper.rb create mode 100644 app/helpers/conferences_helper.rb create mode 100644 app/helpers/config_siemens_helper.rb create mode 100644 app/helpers/error_messages_helper.rb create mode 100644 app/helpers/fax_accounts_helper.rb create mode 100644 app/helpers/fax_documents_helper.rb create mode 100644 app/helpers/gemeinschaft_setups_helper.rb create mode 100644 app/helpers/gs_cluster_sync_log_entries_helper.rb create mode 100644 app/helpers/gs_nodes_helper.rb create mode 100644 app/helpers/gui_functions_helper.rb create mode 100644 app/helpers/hunt_group_members_helper.rb create mode 100644 app/helpers/hunt_groups_helper.rb create mode 100644 app/helpers/layout_helper.rb create mode 100644 app/helpers/manufacturers_helper.rb create mode 100644 app/helpers/page_helper.rb create mode 100644 app/helpers/phone_book_entries_helper.rb create mode 100644 app/helpers/phone_books_helper.rb create mode 100644 app/helpers/phone_models_helper.rb create mode 100644 app/helpers/phone_number_ranges_helper.rb create mode 100644 app/helpers/phone_numbers_helper.rb create mode 100644 app/helpers/phone_sip_accounts_helper.rb create mode 100644 app/helpers/phones_helper.rb create mode 100644 app/helpers/phones_sip_accounts_helper.rb create mode 100644 app/helpers/ringtones_helper.rb create mode 100644 app/helpers/sessions_helper.rb create mode 100644 app/helpers/sip_accounts_helper.rb create mode 100644 app/helpers/sip_domains_helper.rb create mode 100644 app/helpers/softkeys_helper.rb create mode 100644 app/helpers/system_messages_helper.rb create mode 100644 app/helpers/tenants_helper.rb create mode 100644 app/helpers/user_groups_helper.rb create mode 100644 app/helpers/users_helper.rb create mode 100644 app/helpers/whitelists_helper.rb create mode 100644 app/mailers/.gitkeep create mode 100644 app/mailers/notifications.rb create mode 100644 app/models/.gitkeep create mode 100644 app/models/ability.rb create mode 100644 app/models/access_authorization.rb create mode 100644 app/models/acd_agent.rb create mode 100644 app/models/acd_caller.rb create mode 100644 app/models/address.rb create mode 100644 app/models/api.rb create mode 100644 app/models/api/row.rb create mode 100644 app/models/area_code.rb create mode 100644 app/models/automatic_call_distributor.rb create mode 100644 app/models/call.rb create mode 100644 app/models/call_forward.rb create mode 100644 app/models/call_forward_case.rb create mode 100644 app/models/call_history.rb create mode 100644 app/models/callthrough.rb create mode 100644 app/models/conference.rb create mode 100644 app/models/conference_invitee.rb create mode 100644 app/models/country.rb create mode 100644 app/models/dial_in_number_store.rb create mode 100644 app/models/fax_account.rb create mode 100644 app/models/fax_document.rb create mode 100644 app/models/fax_resolution.rb create mode 100644 app/models/fax_thumbnail.rb create mode 100644 app/models/freeswitch_alias.rb create mode 100644 app/models/freeswitch_call.rb create mode 100644 app/models/freeswitch_cdr.rb create mode 100644 app/models/freeswitch_channel.rb create mode 100644 app/models/freeswitch_complete.rb create mode 100644 app/models/freeswitch_fifo_bridge.rb create mode 100644 app/models/freeswitch_fifo_caller.rb create mode 100644 app/models/freeswitch_fifo_outbound.rb create mode 100644 app/models/freeswitch_interface.rb create mode 100644 app/models/freeswitch_nat.rb create mode 100644 app/models/freeswitch_registration.rb create mode 100644 app/models/freeswitch_task.rb create mode 100644 app/models/freeswitch_voicemail_pref.rb create mode 100644 app/models/gemeinschaft_setup.rb create mode 100644 app/models/gs_cluster_sync_log_entry.rb create mode 100644 app/models/gs_node.rb create mode 100644 app/models/gui_function.rb create mode 100644 app/models/gui_function_membership.rb create mode 100644 app/models/hunt_group.rb create mode 100644 app/models/hunt_group_member.rb create mode 100644 app/models/language.rb create mode 100644 app/models/manufacturer.rb create mode 100644 app/models/oui.rb create mode 100644 app/models/phone.rb create mode 100644 app/models/phone_book.rb create mode 100644 app/models/phone_book_entry.rb create mode 100644 app/models/phone_model.rb create mode 100644 app/models/phone_number.rb create mode 100644 app/models/phone_number_range.rb create mode 100644 app/models/phone_sip_account.rb create mode 100644 app/models/remote_gs_node/gs_cluster_sync_log_entry.rb create mode 100644 app/models/ringtone.rb create mode 100644 app/models/sip_account.rb create mode 100644 app/models/sip_domain.rb create mode 100644 app/models/softkey.rb create mode 100644 app/models/softkey_function.rb create mode 100644 app/models/system_message.rb create mode 100644 app/models/tenant.rb create mode 100644 app/models/tenant_membership.rb create mode 100644 app/models/user.rb create mode 100644 app/models/user_group.rb create mode 100644 app/models/user_group_membership.rb create mode 100644 app/models/voicemail_message.rb create mode 100644 app/models/voicemail_setting.rb create mode 100644 app/models/whitelist.rb create mode 100644 app/uploaders/audio_uploader.rb create mode 100644 app/uploaders/document_uploader.rb create mode 100644 app/uploaders/image_uploader.rb create mode 100644 app/uploaders/thumbnail_uploader.rb create mode 100644 app/uploaders/tiff_uploader.rb create mode 100644 app/views/access_authorizations/_form.html.haml create mode 100644 app/views/access_authorizations/_form_core.html.haml create mode 100644 app/views/access_authorizations/_index_core.html.haml create mode 100644 app/views/access_authorizations/edit.html.haml create mode 100644 app/views/access_authorizations/index.html.haml create mode 100644 app/views/access_authorizations/new.html.haml create mode 100644 app/views/access_authorizations/show.html.haml create mode 100644 app/views/acd_agents/_form.html.haml create mode 100644 app/views/acd_agents/_form_core.html.haml create mode 100644 app/views/acd_agents/_index_core.html.haml create mode 100644 app/views/acd_agents/_listing.html.haml create mode 100644 app/views/acd_agents/edit.html.haml create mode 100644 app/views/acd_agents/index.html.haml create mode 100644 app/views/acd_agents/new.html.haml create mode 100644 app/views/acd_agents/show.html.haml create mode 100644 app/views/acd_callers/_index_core.html.haml create mode 100644 app/views/acd_callers/index.html.haml create mode 100644 app/views/acd_callers/show.html.haml create mode 100644 app/views/addresses/_form.html.haml create mode 100644 app/views/addresses/_form_core.html.haml create mode 100644 app/views/addresses/_index_core.html.haml create mode 100644 app/views/addresses/edit.html.haml create mode 100644 app/views/addresses/index.html.haml create mode 100644 app/views/addresses/new.html.haml create mode 100644 app/views/addresses/show.html.haml create mode 100644 app/views/api/rows/_form.html.erb create mode 100644 app/views/api/rows/edit.html.erb create mode 100644 app/views/api/rows/index.html.erb create mode 100644 app/views/api/rows/new.html.erb create mode 100644 app/views/api/rows/show.html.erb create mode 100644 app/views/automatic_call_distributors/_form.html.haml create mode 100644 app/views/automatic_call_distributors/_form_core.html.haml create mode 100644 app/views/automatic_call_distributors/_index_core.html.haml create mode 100644 app/views/automatic_call_distributors/edit.html.haml create mode 100644 app/views/automatic_call_distributors/index.html.haml create mode 100644 app/views/automatic_call_distributors/new.html.haml create mode 100644 app/views/automatic_call_distributors/show.html.haml create mode 100644 app/views/call_forwards/_form.html.haml create mode 100644 app/views/call_forwards/_form_core.html.haml create mode 100644 app/views/call_forwards/_index_core.html.haml create mode 100644 app/views/call_forwards/edit.html.haml create mode 100644 app/views/call_forwards/index.html.haml create mode 100644 app/views/call_forwards/new.html.haml create mode 100644 app/views/call_forwards/show.html.haml create mode 100644 app/views/call_histories/_index_core.html.haml create mode 100644 app/views/call_histories/_navigation.html.haml create mode 100644 app/views/call_histories/index.html.haml create mode 100644 app/views/calls/_index_core.html.haml create mode 100644 app/views/calls/index.html.haml create mode 100644 app/views/callthroughs/_form.html.haml create mode 100644 app/views/callthroughs/_form_core.html.haml create mode 100644 app/views/callthroughs/_index_core.html.haml create mode 100644 app/views/callthroughs/edit.html.haml create mode 100644 app/views/callthroughs/index.html.haml create mode 100644 app/views/callthroughs/new.html.haml create mode 100644 app/views/callthroughs/show.html.haml create mode 100644 app/views/conference_invitees/_form.html.haml create mode 100644 app/views/conference_invitees/_form_core.html.haml create mode 100644 app/views/conference_invitees/_index_core.html.haml create mode 100644 app/views/conference_invitees/edit.html.haml create mode 100644 app/views/conference_invitees/index.html.haml create mode 100644 app/views/conference_invitees/new.html.haml create mode 100644 app/views/conference_invitees/show.html.haml create mode 100644 app/views/conferences/_form.html.haml create mode 100644 app/views/conferences/_form_core.html.haml create mode 100644 app/views/conferences/_index_core.html.haml create mode 100644 app/views/conferences/edit.html.haml create mode 100644 app/views/conferences/index.html.haml create mode 100644 app/views/conferences/new.html.haml create mode 100644 app/views/conferences/show.html.haml create mode 100644 app/views/config_polycom/_call_history.xml.haml create mode 100644 app/views/config_polycom/_call_history_menu.xml.haml create mode 100644 app/views/config_polycom/_phone_book.xml.haml create mode 100644 app/views/config_polycom/config_files.xml.builder create mode 100644 app/views/config_polycom/idle_screen.xml.haml create mode 100644 app/views/config_polycom/settings.xml.erb create mode 100644 app/views/config_polycom/settings_directory.xml.haml create mode 100644 app/views/config_siemens/_menu_list.xml.haml create mode 100644 app/views/config_siemens/clean-up.xml.erb create mode 100644 app/views/config_siemens/index.xml.erb create mode 100644 app/views/config_siemens/write.xml.erb create mode 100644 app/views/config_snom/_snom_phone_directory.xml.haml create mode 100644 app/views/config_snom/_snom_phone_input.xml.haml create mode 100644 app/views/config_snom/_snom_phone_menu.xml.haml create mode 100644 app/views/config_snom/_snom_phone_text.xml.haml create mode 100644 app/views/config_snom/call_history.xml.haml create mode 100644 app/views/config_snom/idle_screen.xml.haml create mode 100644 app/views/config_snom/log_in.xml.haml create mode 100644 app/views/config_snom/show.xml.haml create mode 100644 app/views/config_snom/state_settings.xml.haml create mode 100644 app/views/config_snom/switch_protocol.xml.builder create mode 100644 app/views/fax_accounts/_form.html.haml create mode 100644 app/views/fax_accounts/_form_core.html.haml create mode 100644 app/views/fax_accounts/_index_core.html.haml create mode 100644 app/views/fax_accounts/edit.html.haml create mode 100644 app/views/fax_accounts/index.html.haml create mode 100644 app/views/fax_accounts/new.html.haml create mode 100644 app/views/fax_accounts/show.html.haml create mode 100644 app/views/fax_documents/_form.html.haml create mode 100644 app/views/fax_documents/_form_core.html.haml create mode 100644 app/views/fax_documents/_index_core.html.haml create mode 100644 app/views/fax_documents/edit.html.haml create mode 100644 app/views/fax_documents/index.html.haml create mode 100644 app/views/fax_documents/new.html.haml create mode 100644 app/views/fax_documents/show.html.haml create mode 100644 app/views/freeswitch_voicemail_msgs/_index_core.html.haml create mode 100644 app/views/freeswitch_voicemail_msgs/index.html.haml create mode 100644 app/views/gemeinschaft_setups/new.de.html.haml create mode 100644 app/views/gemeinschaft_setups/new.html.haml create mode 100644 app/views/gs_cluster_sync_log_entries/_form.html.haml create mode 100644 app/views/gs_cluster_sync_log_entries/_form_core.html.haml create mode 100644 app/views/gs_cluster_sync_log_entries/_index_core.html.haml create mode 100644 app/views/gs_cluster_sync_log_entries/edit.html.haml create mode 100644 app/views/gs_cluster_sync_log_entries/index.html.haml create mode 100644 app/views/gs_cluster_sync_log_entries/new.html.haml create mode 100644 app/views/gs_cluster_sync_log_entries/show.html.haml create mode 100644 app/views/gs_nodes/_form.html.haml create mode 100644 app/views/gs_nodes/_form_core.html.haml create mode 100644 app/views/gs_nodes/_index_core.html.haml create mode 100644 app/views/gs_nodes/edit.html.haml create mode 100644 app/views/gs_nodes/index.html.haml create mode 100644 app/views/gs_nodes/new.html.haml create mode 100644 app/views/gs_nodes/show.html.haml create mode 100644 app/views/gs_nodes/sync.xml.haml create mode 100644 app/views/gui_functions/_form.html.haml create mode 100644 app/views/gui_functions/_form_core.html.haml create mode 100644 app/views/gui_functions/_index_core.html.haml create mode 100644 app/views/gui_functions/edit.html.haml create mode 100644 app/views/gui_functions/index.html.haml create mode 100644 app/views/gui_functions/new.html.haml create mode 100644 app/views/gui_functions/show.html.haml create mode 100644 app/views/hunt_group_members/_form.html.haml create mode 100644 app/views/hunt_group_members/_form_core.html.haml create mode 100644 app/views/hunt_group_members/_index_core.html.haml create mode 100644 app/views/hunt_group_members/_listing.html.haml create mode 100644 app/views/hunt_group_members/edit.html.haml create mode 100644 app/views/hunt_group_members/index.html.haml create mode 100644 app/views/hunt_group_members/new.html.haml create mode 100644 app/views/hunt_group_members/show.html.haml create mode 100644 app/views/hunt_groups/_form.html.haml create mode 100644 app/views/hunt_groups/_form_core.html.haml create mode 100644 app/views/hunt_groups/_index_core.html.haml create mode 100644 app/views/hunt_groups/edit.html.haml create mode 100644 app/views/hunt_groups/index.html.haml create mode 100644 app/views/hunt_groups/new.html.haml create mode 100644 app/views/hunt_groups/show.html.haml create mode 100644 app/views/layouts/application.html.haml create mode 100644 app/views/manufacturers/_form.html.haml create mode 100644 app/views/manufacturers/_form_core.html.haml create mode 100644 app/views/manufacturers/_index_core.html.haml create mode 100644 app/views/manufacturers/edit.html.haml create mode 100644 app/views/manufacturers/index.html.haml create mode 100644 app/views/manufacturers/new.html.haml create mode 100644 app/views/manufacturers/show.html.haml create mode 100644 app/views/notifications/new_fax.text.erb create mode 100644 app/views/notifications/new_password.text.erb create mode 100644 app/views/notifications/new_pin.text.erb create mode 100644 app/views/notifications/new_voicemail.text.erb create mode 100644 app/views/page/conference.html.haml create mode 100644 app/views/page/index.de.html.haml create mode 100644 app/views/page/index.html.haml create mode 100644 app/views/phone_book_entries/_form.html.haml create mode 100644 app/views/phone_book_entries/_form_core.html.haml create mode 100644 app/views/phone_book_entries/_index_core.de.html.haml create mode 100644 app/views/phone_book_entries/_index_core.html.haml create mode 100644 app/views/phone_book_entries/_navigation.html.haml create mode 100644 app/views/phone_book_entries/edit.html.haml create mode 100644 app/views/phone_book_entries/index.html.haml create mode 100644 app/views/phone_book_entries/new.html.haml create mode 100644 app/views/phone_book_entries/show.html.haml create mode 100644 app/views/phone_book_entries/show.html.haml.examlple create mode 100644 app/views/phone_books/_form.html.haml create mode 100644 app/views/phone_books/_form_core.html.haml create mode 100644 app/views/phone_books/_index_core.html.haml create mode 100644 app/views/phone_books/edit.html.haml create mode 100644 app/views/phone_books/index.html.haml create mode 100644 app/views/phone_books/new.html.haml create mode 100644 app/views/phone_books/show.html.haml create mode 100644 app/views/phone_models/_form.html.haml create mode 100644 app/views/phone_models/_form_core.html.haml create mode 100644 app/views/phone_models/_index_core.html.haml create mode 100644 app/views/phone_models/edit.html.haml create mode 100644 app/views/phone_models/index.html.haml create mode 100644 app/views/phone_models/new.html.haml create mode 100644 app/views/phone_models/show.html.haml create mode 100644 app/views/phone_number_ranges/_form.html.haml create mode 100644 app/views/phone_number_ranges/_form_core.html.haml create mode 100644 app/views/phone_number_ranges/_index_core.html.haml create mode 100644 app/views/phone_number_ranges/edit.html.haml create mode 100644 app/views/phone_number_ranges/index.html.haml create mode 100644 app/views/phone_number_ranges/new.html.haml create mode 100644 app/views/phone_number_ranges/show.html.haml create mode 100644 app/views/phone_numbers/_form.html.haml create mode 100644 app/views/phone_numbers/_form_core.html.haml create mode 100644 app/views/phone_numbers/_index_core.html.haml create mode 100644 app/views/phone_numbers/_listing.html.haml create mode 100644 app/views/phone_numbers/edit.html.haml create mode 100644 app/views/phone_numbers/index.html.haml create mode 100644 app/views/phone_numbers/new.html.haml create mode 100644 app/views/phone_numbers/show.html.haml create mode 100644 app/views/phone_sip_accounts/_form.html.haml create mode 100644 app/views/phone_sip_accounts/_form_core.html.haml create mode 100644 app/views/phone_sip_accounts/_index_core.html.haml create mode 100644 app/views/phone_sip_accounts/index.html.haml create mode 100644 app/views/phone_sip_accounts/new.html.haml create mode 100644 app/views/phone_sip_accounts/show.html.haml create mode 100644 app/views/phones/_form.html.haml create mode 100644 app/views/phones/_form_core.html.haml create mode 100644 app/views/phones/_index_core.html.haml create mode 100644 app/views/phones/edit.html.haml create mode 100644 app/views/phones/index.html.haml create mode 100644 app/views/phones/new.html.haml create mode 100644 app/views/phones/show.html.haml create mode 100644 app/views/ringtones/_form.html.haml create mode 100644 app/views/ringtones/_form_core.html.haml create mode 100644 app/views/ringtones/_index_core.html.haml create mode 100644 app/views/ringtones/edit.html.haml create mode 100644 app/views/ringtones/index.html.haml create mode 100644 app/views/ringtones/new.html.haml create mode 100644 app/views/ringtones/show.html.haml create mode 100644 app/views/sessions/new.html.haml create mode 100644 app/views/shared/_create_link.html.haml create mode 100644 app/views/shared/_flash.html.haml create mode 100644 app/views/shared/_header.de.html.haml create mode 100644 app/views/shared/_header.html.haml create mode 100644 app/views/shared/_index_view_edit_destroy_part.html.haml create mode 100644 app/views/shared/_show_edit_destroy_part.html.haml create mode 100644 app/views/shared/_system_message.html.haml create mode 100644 app/views/sip_accounts/_form.html.haml create mode 100644 app/views/sip_accounts/_form_core.html.haml create mode 100644 app/views/sip_accounts/_index_core.html.haml create mode 100644 app/views/sip_accounts/edit.html.haml create mode 100644 app/views/sip_accounts/index.html.haml create mode 100644 app/views/sip_accounts/new.html.haml create mode 100644 app/views/sip_accounts/show.html.haml create mode 100644 app/views/sip_domains/_form.html.haml create mode 100644 app/views/sip_domains/_form_core.html.haml create mode 100644 app/views/sip_domains/_index_core.html.haml create mode 100644 app/views/sip_domains/edit.html.haml create mode 100644 app/views/sip_domains/index.html.haml create mode 100644 app/views/sip_domains/new.html.haml create mode 100644 app/views/sip_domains/show.html.haml create mode 100644 app/views/softkeys/_form.html.haml create mode 100644 app/views/softkeys/_form_core.html.haml create mode 100644 app/views/softkeys/_index_core.html.haml create mode 100644 app/views/softkeys/edit.html.haml create mode 100644 app/views/softkeys/index.html.haml create mode 100644 app/views/softkeys/new.html.haml create mode 100644 app/views/softkeys/show.html.haml create mode 100644 app/views/system_messages/_form.html.haml create mode 100644 app/views/system_messages/_form_core.html.haml create mode 100644 app/views/system_messages/_index_core.html.haml create mode 100644 app/views/system_messages/index.html.haml create mode 100644 app/views/system_messages/new.html.haml create mode 100644 app/views/system_messages/show.html.haml create mode 100644 app/views/tenants/_admin_area.de.html.haml create mode 100644 app/views/tenants/_admin_area.html.haml create mode 100644 app/views/tenants/_form.html.haml create mode 100644 app/views/tenants/_form_core.html.haml create mode 100644 app/views/tenants/_index_core.html.haml create mode 100644 app/views/tenants/edit.html.haml create mode 100644 app/views/tenants/index.html.haml create mode 100644 app/views/tenants/new.html.haml create mode 100644 app/views/tenants/show.html.haml create mode 100644 app/views/user_group_memberships/_form.html.haml create mode 100644 app/views/user_group_memberships/_form_core.html.haml create mode 100644 app/views/user_group_memberships/_index_core.html.haml create mode 100644 app/views/user_group_memberships/edit.html.haml create mode 100644 app/views/user_group_memberships/index.html.haml create mode 100644 app/views/user_group_memberships/new.html.haml create mode 100644 app/views/user_group_memberships/show.html.haml create mode 100644 app/views/user_groups/_form.html.haml create mode 100644 app/views/user_groups/_form_core.html.haml create mode 100644 app/views/user_groups/_index_core.html.haml create mode 100644 app/views/user_groups/edit.html.haml create mode 100644 app/views/user_groups/index.html.haml create mode 100644 app/views/user_groups/new.html.haml create mode 100644 app/views/user_groups/show.html.haml create mode 100644 app/views/users/_form.html.haml create mode 100644 app/views/users/_form_core.html.haml create mode 100644 app/views/users/_index_core.html.haml create mode 100644 app/views/users/_listing.html.haml create mode 100644 app/views/users/edit.html.haml create mode 100644 app/views/users/index.html.haml create mode 100644 app/views/users/new.html.haml create mode 100644 app/views/users/show.html.haml create mode 100644 app/views/voicemail_messages/_index_core.html.haml create mode 100644 app/views/voicemail_messages/_navigation.html.haml create mode 100644 app/views/voicemail_messages/index.html.haml create mode 100644 app/views/voicemail_settings/_form.html.haml create mode 100644 app/views/voicemail_settings/_form_core.html.haml create mode 100644 app/views/voicemail_settings/edit.html.haml create mode 100644 app/views/voicemail_settings/show.html.haml create mode 100644 app/views/whitelists/_form.html.haml create mode 100644 app/views/whitelists/_form_core.html.haml create mode 100644 app/views/whitelists/_index_core.html.haml create mode 100644 app/views/whitelists/edit.html.haml create mode 100644 app/views/whitelists/index.html.haml create mode 100644 app/views/whitelists/new.html.haml create mode 100644 app/views/whitelists/show.html.haml (limited to 'app') diff --git a/app/assets/images/amooma-logo.png b/app/assets/images/amooma-logo.png new file mode 100644 index 0000000..11096ff Binary files /dev/null and b/app/assets/images/amooma-logo.png differ diff --git a/app/assets/images/bg-body.png b/app/assets/images/bg-body.png new file mode 100644 index 0000000..777eff6 Binary files /dev/null and b/app/assets/images/bg-body.png differ diff --git a/app/assets/images/gradients/light-to-dark-blue-x63.png b/app/assets/images/gradients/light-to-dark-blue-x63.png new file mode 100644 index 0000000..7eb020f Binary files /dev/null and b/app/assets/images/gradients/light-to-dark-blue-x63.png differ diff --git a/app/assets/images/gradients/white-gray-x29-reverse.png b/app/assets/images/gradients/white-gray-x29-reverse.png new file mode 100644 index 0000000..7b7e879 Binary files /dev/null and b/app/assets/images/gradients/white-gray-x29-reverse.png differ diff --git a/app/assets/images/gradients/white-gray-x29.png b/app/assets/images/gradients/white-gray-x29.png new file mode 100644 index 0000000..a1671d0 Binary files /dev/null and b/app/assets/images/gradients/white-gray-x29.png differ diff --git a/app/assets/images/gradients/white-texture-x63.png b/app/assets/images/gradients/white-texture-x63.png new file mode 100644 index 0000000..1576e8f Binary files /dev/null and b/app/assets/images/gradients/white-texture-x63.png differ diff --git a/app/assets/images/icons/cellphone-32x.png b/app/assets/images/icons/cellphone-32x.png new file mode 100644 index 0000000..cfc41f5 Binary files /dev/null and b/app/assets/images/icons/cellphone-32x.png differ diff --git a/app/assets/images/icons/clock-32x.png b/app/assets/images/icons/clock-32x.png new file mode 100644 index 0000000..c076042 Binary files /dev/null and b/app/assets/images/icons/clock-32x.png differ diff --git a/app/assets/images/icons/cross-16x.png b/app/assets/images/icons/cross-16x.png new file mode 100644 index 0000000..e22ed6f Binary files /dev/null and b/app/assets/images/icons/cross-16x.png differ diff --git a/app/assets/images/icons/facebook-32x.png b/app/assets/images/icons/facebook-32x.png new file mode 100644 index 0000000..08fa0f7 Binary files /dev/null and b/app/assets/images/icons/facebook-32x.png differ diff --git a/app/assets/images/icons/fax-32x.png b/app/assets/images/icons/fax-32x.png new file mode 100644 index 0000000..b05ee59 Binary files /dev/null and b/app/assets/images/icons/fax-32x.png differ diff --git a/app/assets/images/icons/headphones-16x.png b/app/assets/images/icons/headphones-16x.png new file mode 100644 index 0000000..dee8346 Binary files /dev/null and b/app/assets/images/icons/headphones-16x.png differ diff --git a/app/assets/images/icons/headphones-32x.png b/app/assets/images/icons/headphones-32x.png new file mode 100644 index 0000000..89a5df7 Binary files /dev/null and b/app/assets/images/icons/headphones-32x.png differ diff --git a/app/assets/images/icons/house-32x.png b/app/assets/images/icons/house-32x.png new file mode 100644 index 0000000..b112915 Binary files /dev/null and b/app/assets/images/icons/house-32x.png differ diff --git a/app/assets/images/icons/mic-32x.png b/app/assets/images/icons/mic-32x.png new file mode 100644 index 0000000..30c4531 Binary files /dev/null and b/app/assets/images/icons/mic-32x.png differ diff --git a/app/assets/images/icons/microphone-16x.png b/app/assets/images/icons/microphone-16x.png new file mode 100644 index 0000000..b62422d Binary files /dev/null and b/app/assets/images/icons/microphone-16x.png differ diff --git a/app/assets/images/icons/microphone-32x.png b/app/assets/images/icons/microphone-32x.png new file mode 100644 index 0000000..30c4531 Binary files /dev/null and b/app/assets/images/icons/microphone-32x.png differ diff --git a/app/assets/images/icons/mute-16x.png b/app/assets/images/icons/mute-16x.png new file mode 100644 index 0000000..0656f3f Binary files /dev/null and b/app/assets/images/icons/mute-16x.png differ diff --git a/app/assets/images/icons/phone-down-32x.png b/app/assets/images/icons/phone-down-32x.png new file mode 100644 index 0000000..38c3560 Binary files /dev/null and b/app/assets/images/icons/phone-down-32x.png differ diff --git a/app/assets/images/icons/phone-mobile-32x.png b/app/assets/images/icons/phone-mobile-32x.png new file mode 100644 index 0000000..b373e1a Binary files /dev/null and b/app/assets/images/icons/phone-mobile-32x.png differ diff --git a/app/assets/images/icons/phone-up-32x.png b/app/assets/images/icons/phone-up-32x.png new file mode 100644 index 0000000..9b765c7 Binary files /dev/null and b/app/assets/images/icons/phone-up-32x.png differ diff --git a/app/assets/images/icons/search-13x16.png b/app/assets/images/icons/search-13x16.png new file mode 100644 index 0000000..16aa3c6 Binary files /dev/null and b/app/assets/images/icons/search-13x16.png differ diff --git a/app/assets/images/icons/skype-32x.png b/app/assets/images/icons/skype-32x.png new file mode 100644 index 0000000..c3b0978 Binary files /dev/null and b/app/assets/images/icons/skype-32x.png differ diff --git a/app/assets/images/icons/star-16x.png b/app/assets/images/icons/star-16x.png new file mode 100644 index 0000000..6b16932 Binary files /dev/null and b/app/assets/images/icons/star-16x.png differ diff --git a/app/assets/images/icons/suitcase-32x.png b/app/assets/images/icons/suitcase-32x.png new file mode 100644 index 0000000..f53daa9 Binary files /dev/null and b/app/assets/images/icons/suitcase-32x.png differ diff --git a/app/assets/images/icons/tag-16x.png b/app/assets/images/icons/tag-16x.png new file mode 100644 index 0000000..b4522d7 Binary files /dev/null and b/app/assets/images/icons/tag-16x.png differ diff --git a/app/assets/images/icons/twitter-32x.png b/app/assets/images/icons/twitter-32x.png new file mode 100644 index 0000000..51351a7 Binary files /dev/null and b/app/assets/images/icons/twitter-32x.png differ diff --git a/app/assets/images/icons/unmute-16x.png b/app/assets/images/icons/unmute-16x.png new file mode 100644 index 0000000..e9dfde0 Binary files /dev/null and b/app/assets/images/icons/unmute-16x.png differ diff --git a/app/assets/images/icons/user-16x.png b/app/assets/images/icons/user-16x.png new file mode 100644 index 0000000..909403a Binary files /dev/null and b/app/assets/images/icons/user-16x.png differ diff --git a/app/assets/images/icons/user-female-16x.png b/app/assets/images/icons/user-female-16x.png new file mode 100644 index 0000000..38dde34 Binary files /dev/null and b/app/assets/images/icons/user-female-16x.png differ diff --git a/app/assets/images/icons/user-male-16x.png b/app/assets/images/icons/user-male-16x.png new file mode 100644 index 0000000..e03fd0f Binary files /dev/null and b/app/assets/images/icons/user-male-16x.png differ diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png new file mode 100644 index 0000000..e4432b8 Binary files /dev/null and b/app/assets/images/logo.png differ diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png new file mode 100644 index 0000000..d5edc04 Binary files /dev/null and b/app/assets/images/rails.png differ diff --git a/app/assets/images/stubs/user-36x.jpg b/app/assets/images/stubs/user-36x.jpg new file mode 100644 index 0000000..8a391a0 Binary files /dev/null and b/app/assets/images/stubs/user-36x.jpg differ diff --git a/app/assets/images/user.png b/app/assets/images/user.png new file mode 100644 index 0000000..c0e33c7 Binary files /dev/null and b/app/assets/images/user.png differ diff --git a/app/assets/javascripts/api/rows.js.coffee b/app/assets/javascripts/api/rows.js.coffee new file mode 100644 index 0000000..7615679 --- /dev/null +++ b/app/assets/javascripts/api/rows.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000..2e0b0d8 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into including all the files listed below. +// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// be included in the compiled file accessible from http://example.com/assets/application.js +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// +//= require jquery +//= require jquery_ujs +//= require core +//= require vendor/modernizr-2.0.6.min.js +//= require vendor/jquery.condom.js +//= require vendor/jquery.survival-kit +//= require softkeys diff --git a/app/assets/javascripts/config_siemens.js.coffee b/app/assets/javascripts/config_siemens.js.coffee new file mode 100644 index 0000000..7615679 --- /dev/null +++ b/app/assets/javascripts/config_siemens.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/core.coffee b/app/assets/javascripts/core.coffee new file mode 100644 index 0000000..af12aa7 --- /dev/null +++ b/app/assets/javascripts/core.coffee @@ -0,0 +1,5 @@ +$(document).ready -> + $sk = $.ns('sk') + $sk('.search-box').searchBox() + $sk('.simple_form').simpleForms() + \ No newline at end of file diff --git a/app/assets/javascripts/page.js.coffee b/app/assets/javascripts/page.js.coffee new file mode 100644 index 0000000..7615679 --- /dev/null +++ b/app/assets/javascripts/page.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/assets/javascripts/softkeys.js.coffee b/app/assets/javascripts/softkeys.js.coffee new file mode 100644 index 0000000..23d5233 --- /dev/null +++ b/app/assets/javascripts/softkeys.js.coffee @@ -0,0 +1,25 @@ +jQuery -> + function_name = $('#softkey_softkey_function_id :selected').text() + + if function_name == call_forwarding_function_name + $('#softkey_call_forward_id').parent().show() + $('#softkey_number').parent().hide() + else + $('#softkey_call_forward_id').parent().hide() + if (function_name == hold_function_name || function_name == deactivated_function_name) + $('#softkey_number').parent().hide() + else + $('#softkey_number').parent().show() + + $('#softkey_softkey_function_id').change -> + $('#softkey_label').parent().show() + function_name = $('#softkey_softkey_function_id :selected').text() + if function_name == call_forwarding_function_name + $('#softkey_call_forward_id').parent().show("slow") + $('#softkey_number').parent().hide("slow") + else + $('#softkey_call_forward_id').parent().hide("slow") + if (function_name == hold_function_name || function_name == deactivated_function_name) + $('#softkey_number').parent().hide("slow") + else + $('#softkey_number').parent().show("slow") diff --git a/app/assets/javascripts/vendor/autoresize.jquery.js b/app/assets/javascripts/vendor/autoresize.jquery.js new file mode 100644 index 0000000..28cec5d --- /dev/null +++ b/app/assets/javascripts/vendor/autoresize.jquery.js @@ -0,0 +1,94 @@ +/* + * jQuery autoResize (textarea auto-resizer) + * @copyright James Padolsey http://james.padolsey.com + * @version 1.04 + */ + +(function($){ + $.fn.autoResize = function(options) { + // Just some abstracted details, + // to make plugin users happy: + var settings = $.extend({ + onResize : function(){}, + animate : true, + animateDuration : 150, + animateCallback : function(){}, + extraSpace : 20, + limit: 1000 + }, options); + + // Only textarea's auto-resize: + this.filter('textarea').each(function(){ + + // Get rid of scrollbars and disable WebKit resizing: + var textarea = $(this).css({resize:'none','overflow-y':'hidden'}), + + // Cache original height, for use later: + origHeight = textarea.height(), + + // Need clone of textarea, hidden off screen: + clone = (function(){ + + // Properties which may effect space taken up by chracters: + var props = ['height','width','lineHeight','textDecoration','letterSpacing'], + propOb = {}; + + // Create object of styles to apply: + $.each(props, function(i, prop){ + propOb[prop] = textarea.css(prop); + }); + + // Clone the actual textarea removing unique properties + // and insert before original textarea: + return textarea.clone().removeAttr('id').removeAttr('name').css({ + position: 'absolute', + top: 0, + left: -9999 + }).css(propOb).attr('tabIndex','-1').insertBefore(textarea); + + })(), + lastScrollTop = null, + updateSize = function() { + + // Prepare the clone: + clone.height(0).val($(this).val()).scrollTop(10000); + + // Find the height of text: + var scrollTop = Math.max(clone.scrollTop(), origHeight) + settings.extraSpace, + toChange = $(this).add(clone); + + // Don't do anything if scrollTip hasen't changed: + if (lastScrollTop === scrollTop) { return; } + lastScrollTop = scrollTop; + + // Check for limit: + if ( scrollTop >= settings.limit ) { + $(this).css('overflow-y',''); + return; + } + // Fire off callback: + settings.onResize.call(this); + + // Either animate or directly apply height: + settings.animate && textarea.css('display') === 'block' ? + toChange.stop().animate({height:scrollTop}, settings.animateDuration, settings.animateCallback) + : toChange.height(scrollTop); + }; + + // Bind namespaced handlers to appropriate events: + textarea + .unbind('.dynSiz') + .bind('keyup.dynSiz', updateSize) + .bind('keydown.dynSiz', updateSize) + .bind('change.dynSiz', updateSize); + + }); + + // Chain: + return this; + + }; + + + +})(jQuery); diff --git a/app/assets/javascripts/vendor/fancybox/jquery.easing-1.3.pack.js b/app/assets/javascripts/vendor/fancybox/jquery.easing-1.3.pack.js new file mode 100755 index 0000000..ef74321 --- /dev/null +++ b/app/assets/javascripts/vendor/fancybox/jquery.easing-1.3.pack.js @@ -0,0 +1,205 @@ +/* + * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ + * + * Uses the built in easing capabilities added In jQuery 1.1 + * to offer multiple easing options + * + * TERMS OF USE - jQuery Easing + * + * Open source under the BSD License. + * + * Copyright © 2008 George McGinley Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * +*/ + +// t: current time, b: begInnIng value, c: change In value, d: duration +jQuery.easing['jswing'] = jQuery.easing['swing']; + +jQuery.extend( jQuery.easing, +{ + def: 'easeOutQuad', + swing: function (x, t, b, c, d) { + //alert(jQuery.easing.default); + return jQuery.easing[jQuery.easing.def](x, t, b, c, d); + }, + easeInQuad: function (x, t, b, c, d) { + return c*(t/=d)*t + b; + }, + easeOutQuad: function (x, t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + }, + easeInOutQuad: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + }, + easeInCubic: function (x, t, b, c, d) { + return c*(t/=d)*t*t + b; + }, + easeOutCubic: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t + 1) + b; + }, + easeInOutCubic: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + }, + easeInQuart: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t + b; + }, + easeOutQuart: function (x, t, b, c, d) { + return -c * ((t=t/d-1)*t*t*t - 1) + b; + }, + easeInOutQuart: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t + b; + return -c/2 * ((t-=2)*t*t*t - 2) + b; + }, + easeInQuint: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t*t + b; + }, + easeOutQuint: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t*t*t + 1) + b; + }, + easeInOutQuint: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; + return c/2*((t-=2)*t*t*t*t + 2) + b; + }, + easeInSine: function (x, t, b, c, d) { + return -c * Math.cos(t/d * (Math.PI/2)) + c + b; + }, + easeOutSine: function (x, t, b, c, d) { + return c * Math.sin(t/d * (Math.PI/2)) + b; + }, + easeInOutSine: function (x, t, b, c, d) { + return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; + }, + easeInExpo: function (x, t, b, c, d) { + return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + }, + easeOutExpo: function (x, t, b, c, d) { + return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + }, + easeInOutExpo: function (x, t, b, c, d) { + if (t==0) return b; + if (t==d) return b+c; + if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; + return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + easeInCirc: function (x, t, b, c, d) { + return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; + }, + easeOutCirc: function (x, t, b, c, d) { + return c * Math.sqrt(1 - (t=t/d-1)*t) + b; + }, + easeInOutCirc: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; + return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; + }, + easeInElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + }, + easeOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; + }, + easeInOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; + }, + easeInBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*(t/=d)*t*((s+1)*t - s) + b; + }, + easeOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; + }, + easeInOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; + return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + }, + easeInBounce: function (x, t, b, c, d) { + return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b; + }, + easeOutBounce: function (x, t, b, c, d) { + if ((t/=d) < (1/2.75)) { + return c*(7.5625*t*t) + b; + } else if (t < (2/2.75)) { + return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; + } else if (t < (2.5/2.75)) { + return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; + } else { + return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; + } + }, + easeInOutBounce: function (x, t, b, c, d) { + if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b; + return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b; + } +}); + +/* + * + * TERMS OF USE - EASING EQUATIONS + * + * Open source under the BSD License. + * + * Copyright © 2001 Robert Penner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ \ No newline at end of file diff --git a/app/assets/javascripts/vendor/fancybox/jquery.fancybox-1.3.4.pack.js b/app/assets/javascripts/vendor/fancybox/jquery.fancybox-1.3.4.pack.js new file mode 100755 index 0000000..1373ed0 --- /dev/null +++ b/app/assets/javascripts/vendor/fancybox/jquery.fancybox-1.3.4.pack.js @@ -0,0 +1,46 @@ +/* + * FancyBox - jQuery Plugin + * Simple and fancy lightbox alternative + * + * Examples and documentation at: http://fancybox.net + * + * Copyright (c) 2008 - 2010 Janis Skarnelis + * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. + * + * Version: 1.3.4 (11/11/2010) + * Requires: jQuery v1.3+ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +;(function(b){var m,t,u,f,D,j,E,n,z,A,q=0,e={},o=[],p=0,d={},l=[],G=null,v=new Image,J=/\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,W=/[^\.]\.(swf)\s*$/i,K,L=1,y=0,s="",r,i,h=false,B=b.extend(b("
")[0],{prop:0}),M=b.browser.msie&&b.browser.version<7&&!window.XMLHttpRequest,N=function(){t.hide();v.onerror=v.onload=null;G&&G.abort();m.empty()},O=function(){if(false===e.onError(o,q,e)){t.hide();h=false}else{e.titleShow=false;e.width="auto";e.height="auto";m.html('

The requested content cannot be loaded.
Please try again later.

'); +F()}},I=function(){var a=o[q],c,g,k,C,P,w;N();e=b.extend({},b.fn.fancybox.defaults,typeof b(a).data("fancybox")=="undefined"?e:b(a).data("fancybox"));w=e.onStart(o,q,e);if(w===false)h=false;else{if(typeof w=="object")e=b.extend(e,w);k=e.title||(a.nodeName?b(a).attr("title"):a.title)||"";if(a.nodeName&&!e.orig)e.orig=b(a).children("img:first").length?b(a).children("img:first"):b(a);if(k===""&&e.orig&&e.titleFromAlt)k=e.orig.attr("alt");c=e.href||(a.nodeName?b(a).attr("href"):a.href)||null;if(/^(?:javascript)/i.test(c)|| +c=="#")c=null;if(e.type){g=e.type;if(!c)c=e.content}else if(e.content)g="html";else if(c)g=c.match(J)?"image":c.match(W)?"swf":b(a).hasClass("iframe")?"iframe":c.indexOf("#")===0?"inline":"ajax";if(g){if(g=="inline"){a=c.substr(c.indexOf("#"));g=b(a).length>0?"inline":"ajax"}e.type=g;e.href=c;e.title=k;if(e.autoDimensions)if(e.type=="html"||e.type=="inline"||e.type=="ajax"){e.width="auto";e.height="auto"}else e.autoDimensions=false;if(e.modal){e.overlayShow=true;e.hideOnOverlayClick=false;e.hideOnContentClick= +false;e.enableEscapeButton=false;e.showCloseButton=false}e.padding=parseInt(e.padding,10);e.margin=parseInt(e.margin,10);m.css("padding",e.padding+e.margin);b(".fancybox-inline-tmp").unbind("fancybox-cancel").bind("fancybox-change",function(){b(this).replaceWith(j.children())});switch(g){case "html":m.html(e.content);F();break;case "inline":if(b(a).parent().is("#fancybox-content")===true){h=false;break}b('
').hide().insertBefore(b(a)).bind("fancybox-cleanup",function(){b(this).replaceWith(j.children())}).bind("fancybox-cancel", +function(){b(this).replaceWith(m.children())});b(a).appendTo(m);F();break;case "image":h=false;b.fancybox.showActivity();v=new Image;v.onerror=function(){O()};v.onload=function(){h=true;v.onerror=v.onload=null;e.width=v.width;e.height=v.height;b("").attr({id:"fancybox-img",src:v.src,alt:e.title}).appendTo(m);Q()};v.src=c;break;case "swf":e.scrolling="no";C='';P="";b.each(e.swf,function(x,H){C+='';P+=" "+x+'="'+H+'"'});C+='";m.html(C);F();break;case "ajax":h=false;b.fancybox.showActivity();e.ajax.win=e.ajax.success;G=b.ajax(b.extend({},e.ajax,{url:c,data:e.ajax.data||{},error:function(x){x.status>0&&O()},success:function(x,H,R){if((typeof R=="object"?R:G).status==200){if(typeof e.ajax.win== +"function"){w=e.ajax.win(c,x,H,R);if(w===false){t.hide();return}else if(typeof w=="string"||typeof w=="object")x=w}m.html(x);F()}}}));break;case "iframe":Q()}}else O()}},F=function(){var a=e.width,c=e.height;a=a.toString().indexOf("%")>-1?parseInt((b(window).width()-e.margin*2)*parseFloat(a)/100,10)+"px":a=="auto"?"auto":a+"px";c=c.toString().indexOf("%")>-1?parseInt((b(window).height()-e.margin*2)*parseFloat(c)/100,10)+"px":c=="auto"?"auto":c+"px";m.wrapInner('
');e.width=m.width();e.height=m.height();Q()},Q=function(){var a,c;t.hide();if(f.is(":visible")&&false===d.onCleanup(l,p,d)){b.event.trigger("fancybox-cancel");h=false}else{h=true;b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");f.is(":visible")&&d.titlePosition!=="outside"&&f.css("height",f.height());l=o;p=q;d=e;if(d.overlayShow){u.css({"background-color":d.overlayColor, +opacity:d.overlayOpacity,cursor:d.hideOnOverlayClick?"pointer":"auto",height:b(document).height()});if(!u.is(":visible")){M&&b("select:not(#fancybox-tmp select)").filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one("fancybox-cleanup",function(){this.style.visibility="inherit"});u.show()}}else u.hide();i=X();s=d.title||"";y=0;n.empty().removeAttr("style").removeClass();if(d.titleShow!==false){if(b.isFunction(d.titleFormat))a=d.titleFormat(s,l,p,d);else a=s&&s.length? +d.titlePosition=="float"?'
'+s+'
':'
'+s+"
":false;s=a;if(!(!s||s==="")){n.addClass("fancybox-title-"+d.titlePosition).html(s).appendTo("body").show();switch(d.titlePosition){case "inside":n.css({width:i.width-d.padding*2,marginLeft:d.padding,marginRight:d.padding}); +y=n.outerHeight(true);n.appendTo(D);i.height+=y;break;case "over":n.css({marginLeft:d.padding,width:i.width-d.padding*2,bottom:d.padding}).appendTo(D);break;case "float":n.css("left",parseInt((n.width()-i.width-40)/2,10)*-1).appendTo(f);break;default:n.css({width:i.width-d.padding*2,paddingLeft:d.padding,paddingRight:d.padding}).appendTo(f)}}}n.hide();if(f.is(":visible")){b(E.add(z).add(A)).hide();a=f.position();r={top:a.top,left:a.left,width:f.width(),height:f.height()};c=r.width==i.width&&r.height== +i.height;j.fadeTo(d.changeFade,0.3,function(){var g=function(){j.html(m.contents()).fadeTo(d.changeFade,1,S)};b.event.trigger("fancybox-change");j.empty().removeAttr("filter").css({"border-width":d.padding,width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2});if(c)g();else{B.prop=0;b(B).animate({prop:1},{duration:d.changeSpeed,easing:d.easingChange,step:T,complete:g})}})}else{f.removeAttr("style");j.css("border-width",d.padding);if(d.transitionIn=="elastic"){r=V();j.html(m.contents()); +f.show();if(d.opacity)i.opacity=0;B.prop=0;b(B).animate({prop:1},{duration:d.speedIn,easing:d.easingIn,step:T,complete:S})}else{d.titlePosition=="inside"&&y>0&&n.show();j.css({width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2}).html(m.contents());f.css(i).fadeIn(d.transitionIn=="none"?0:d.speedIn,S)}}}},Y=function(){if(d.enableEscapeButton||d.enableKeyboardNav)b(document).bind("keydown.fb",function(a){if(a.keyCode==27&&d.enableEscapeButton){a.preventDefault();b.fancybox.close()}else if((a.keyCode== +37||a.keyCode==39)&&d.enableKeyboardNav&&a.target.tagName!=="INPUT"&&a.target.tagName!=="TEXTAREA"&&a.target.tagName!=="SELECT"){a.preventDefault();b.fancybox[a.keyCode==37?"prev":"next"]()}});if(d.showNavArrows){if(d.cyclic&&l.length>1||p!==0)z.show();if(d.cyclic&&l.length>1||p!=l.length-1)A.show()}else{z.hide();A.hide()}},S=function(){if(!b.support.opacity){j.get(0).style.removeAttribute("filter");f.get(0).style.removeAttribute("filter")}e.autoDimensions&&j.css("height","auto");f.css("height","auto"); +s&&s.length&&n.show();d.showCloseButton&&E.show();Y();d.hideOnContentClick&&j.bind("click",b.fancybox.close);d.hideOnOverlayClick&&u.bind("click",b.fancybox.close);b(window).bind("resize.fb",b.fancybox.resize);d.centerOnScroll&&b(window).bind("scroll.fb",b.fancybox.center);if(d.type=="iframe")b('').appendTo(j); +f.show();h=false;b.fancybox.center();d.onComplete(l,p,d);var a,c;if(l.length-1>p){a=l[p+1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}if(p>0){a=l[p-1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}},T=function(a){var c={width:parseInt(r.width+(i.width-r.width)*a,10),height:parseInt(r.height+(i.height-r.height)*a,10),top:parseInt(r.top+(i.top-r.top)*a,10),left:parseInt(r.left+(i.left-r.left)*a,10)};if(typeof i.opacity!=="undefined")c.opacity=a<0.5?0.5:a;f.css(c); +j.css({width:c.width-d.padding*2,height:c.height-y*a-d.padding*2})},U=function(){return[b(window).width()-d.margin*2,b(window).height()-d.margin*2,b(document).scrollLeft()+d.margin,b(document).scrollTop()+d.margin]},X=function(){var a=U(),c={},g=d.autoScale,k=d.padding*2;c.width=d.width.toString().indexOf("%")>-1?parseInt(a[0]*parseFloat(d.width)/100,10):d.width+k;c.height=d.height.toString().indexOf("%")>-1?parseInt(a[1]*parseFloat(d.height)/100,10):d.height+k;if(g&&(c.width>a[0]||c.height>a[1]))if(e.type== +"image"||e.type=="swf"){g=d.width/d.height;if(c.width>a[0]){c.width=a[0];c.height=parseInt((c.width-k)/g+k,10)}if(c.height>a[1]){c.height=a[1];c.width=parseInt((c.height-k)*g+k,10)}}else{c.width=Math.min(c.width,a[0]);c.height=Math.min(c.height,a[1])}c.top=parseInt(Math.max(a[3]-20,a[3]+(a[1]-c.height-40)*0.5),10);c.left=parseInt(Math.max(a[2]-20,a[2]+(a[0]-c.width-40)*0.5),10);return c},V=function(){var a=e.orig?b(e.orig):false,c={};if(a&&a.length){c=a.offset();c.top+=parseInt(a.css("paddingTop"), +10)||0;c.left+=parseInt(a.css("paddingLeft"),10)||0;c.top+=parseInt(a.css("border-top-width"),10)||0;c.left+=parseInt(a.css("border-left-width"),10)||0;c.width=a.width();c.height=a.height();c={width:c.width+d.padding*2,height:c.height+d.padding*2,top:c.top-d.padding-20,left:c.left-d.padding-20}}else{a=U();c={width:d.padding*2,height:d.padding*2,top:parseInt(a[3]+a[1]*0.5,10),left:parseInt(a[2]+a[0]*0.5,10)}}return c},Z=function(){if(t.is(":visible")){b("div",t).css("top",L*-40+"px");L=(L+1)%12}else clearInterval(K)}; +b.fn.fancybox=function(a){if(!b(this).length)return this;b(this).data("fancybox",b.extend({},a,b.metadata?b(this).metadata():{})).unbind("click.fb").bind("click.fb",function(c){c.preventDefault();if(!h){h=true;b(this).blur();o=[];q=0;c=b(this).attr("rel")||"";if(!c||c==""||c==="nofollow")o.push(this);else{o=b("a[rel="+c+"], area[rel="+c+"]");q=o.index(this)}I()}});return this};b.fancybox=function(a,c){var g;if(!h){h=true;g=typeof c!=="undefined"?c:{};o=[];q=parseInt(g.index,10)||0;if(b.isArray(a)){for(var k= +0,C=a.length;ko.length||q<0)q=0;I()}};b.fancybox.showActivity=function(){clearInterval(K);t.show();K=setInterval(Z,66)};b.fancybox.hideActivity=function(){t.hide()};b.fancybox.next=function(){return b.fancybox.pos(p+ +1)};b.fancybox.prev=function(){return b.fancybox.pos(p-1)};b.fancybox.pos=function(a){if(!h){a=parseInt(a);o=l;if(a>-1&&a1){q=a>=l.length?0:l.length-1;I()}}};b.fancybox.cancel=function(){if(!h){h=true;b.event.trigger("fancybox-cancel");N();e.onCancel(o,q,e);h=false}};b.fancybox.close=function(){function a(){u.fadeOut("fast");n.empty().hide();f.hide();b.event.trigger("fancybox-cleanup");j.empty();d.onClosed(l,p,d);l=e=[];p=q=0;d=e={};h=false}if(!(h||f.is(":hidden"))){h= +true;if(d&&false===d.onCleanup(l,p,d))h=false;else{N();b(E.add(z).add(A)).hide();b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");j.find("iframe").attr("src",M&&/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank");d.titlePosition!=="inside"&&n.empty();f.stop();if(d.transitionOut=="elastic"){r=V();var c=f.position();i={top:c.top,left:c.left,width:f.width(),height:f.height()};if(d.opacity)i.opacity=1;n.empty().hide();B.prop=1; +b(B).animate({prop:0},{duration:d.speedOut,easing:d.easingOut,step:T,complete:a})}else f.fadeOut(d.transitionOut=="none"?0:d.speedOut,a)}}};b.fancybox.resize=function(){u.is(":visible")&&u.css("height",b(document).height());b.fancybox.center(true)};b.fancybox.center=function(a){var c,g;if(!h){g=a===true?1:0;c=U();!g&&(f.width()>c[0]||f.height()>c[1])||f.stop().animate({top:parseInt(Math.max(c[3]-20,c[3]+(c[1]-j.height()-40)*0.5-d.padding)),left:parseInt(Math.max(c[2]-20,c[2]+(c[0]-j.width()-40)*0.5- +d.padding))},typeof a=="number"?a:200)}};b.fancybox.init=function(){if(!b("#fancybox-wrap").length){b("body").append(m=b('
'),t=b('
'),u=b('
'),f=b('
'));D=b('
').append('
').appendTo(f); +D.append(j=b('
'),E=b(''),n=b('
'),z=b(''),A=b(''));E.click(b.fancybox.close);t.click(b.fancybox.cancel);z.click(function(a){a.preventDefault();b.fancybox.prev()});A.click(function(a){a.preventDefault();b.fancybox.next()}); +b.fn.mousewheel&&f.bind("mousewheel.fb",function(a,c){if(h)a.preventDefault();else if(b(a.target).get(0).clientHeight==0||b(a.target).get(0).scrollHeight===b(a.target).get(0).clientHeight){a.preventDefault();b.fancybox[c>0?"prev":"next"]()}});b.support.opacity||f.addClass("fancybox-ie");if(M){t.addClass("fancybox-ie6");f.addClass("fancybox-ie6");b('').prependTo(D)}}}; +b.fn.fancybox.defaults={padding:10,margin:40,opacity:false,modal:false,cyclic:false,scrolling:"auto",width:560,height:340,autoScale:true,autoDimensions:true,centerOnScroll:false,ajax:{},swf:{wmode:"transparent"},hideOnOverlayClick:true,hideOnContentClick:false,overlayShow:true,overlayOpacity:0.7,overlayColor:"#777",titleShow:true,titlePosition:"float",titleFormat:null,titleFromAlt:false,transitionIn:"fade",transitionOut:"fade",speedIn:300,speedOut:300,changeSpeed:300,changeFade:"fast",easingIn:"swing", +easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,enableKeyboardNav:true,onStart:function(){},onCancel:function(){},onComplete:function(){},onCleanup:function(){},onClosed:function(){},onError:function(){}};b(document).ready(function(){b.fancybox.init()})})(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/vendor/fancybox/jquery.mousewheel-3.0.4.pack.js b/app/assets/javascripts/vendor/fancybox/jquery.mousewheel-3.0.4.pack.js new file mode 100755 index 0000000..cb66588 --- /dev/null +++ b/app/assets/javascripts/vendor/fancybox/jquery.mousewheel-3.0.4.pack.js @@ -0,0 +1,14 @@ +/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net) +* Licensed under the MIT License (LICENSE.txt). +* +* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. +* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. +* Thanks to: Seamus Leahy for adding deltaX and deltaY +* +* Version: 3.0.4 +* +* Requires: 1.2.2+ +*/ + +(function(d){function g(a){var b=a||window.event,i=[].slice.call(arguments,1),c=0,h=0,e=0;a=d.event.fix(b);a.type="mousewheel";if(a.wheelDelta)c=a.wheelDelta/120;if(a.detail)c=-a.detail/3;e=c;if(b.axis!==undefined&&b.axis===b.HORIZONTAL_AXIS){e=0;h=-1*c}if(b.wheelDeltaY!==undefined)e=b.wheelDeltaY/120;if(b.wheelDeltaX!==undefined)h=-1*b.wheelDeltaX/120;i.unshift(a,c,h,e);return d.event.handle.apply(this,i)}var f=["DOMMouseScroll","mousewheel"];d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a= +f.length;a;)this.addEventListener(f[--a],g,false);else this.onmousewheel=g},teardown:function(){if(this.removeEventListener)for(var a=f.length;a;)this.removeEventListener(f[--a],g,false);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/vendor/html5boilerplate.js b/app/assets/javascripts/vendor/html5boilerplate.js new file mode 100644 index 0000000..7cb21b1 --- /dev/null +++ b/app/assets/javascripts/vendor/html5boilerplate.js @@ -0,0 +1,20 @@ + +// usage: log('inside coolFunc', this, arguments); +// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/ +window.log = function(){ + log.history = log.history || []; // store logs to an array for reference + log.history.push(arguments); + if(this.console) { + arguments.callee = arguments.callee.caller; + var newarr = [].slice.call(arguments); + (typeof console.log === 'object' ? log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr)); + } +}; + +// make it safe to use console.log always +(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try +{console.log();return window.console;}catch(err){return window.console={};}})()); + + +// place any jQuery/helper plugins in here, instead of separate, slower script files. + diff --git a/app/assets/javascripts/vendor/jquery-1.6.2.min.js b/app/assets/javascripts/vendor/jquery-1.6.2.min.js new file mode 100755 index 0000000..48590ec --- /dev/null +++ b/app/assets/javascripts/vendor/jquery-1.6.2.min.js @@ -0,0 +1,18 @@ +/*! + * jQuery JavaScript Library v1.6.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Jun 30 14:16:56 2011 -0400 + */ +(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. +shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j +)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file diff --git a/app/assets/javascripts/vendor/jquery.condom.js b/app/assets/javascripts/vendor/jquery.condom.js new file mode 100644 index 0000000..5d24a7e --- /dev/null +++ b/app/assets/javascripts/vendor/jquery.condom.js @@ -0,0 +1,52 @@ +/* + * jQuery Condom (Use namespaces to protect your global integrity.) + * Version 0.0.3 + * + * Copyright (c) 2011 Mario "Kuroir" Ricalde (http://kuroir.com) + * & Micha Niskin (micha@thinkminimo.com) + * Licensed jointly under the GPL and MIT licenses, + * choose which one suits your project best! + */ +(function($) { + var methods = {}; + $.ns = function(ns) { + // Define namespace if it doesn't exist. + methods[ns] = methods[ns] || {}; + + // Get reference to a namespaced jQ object + function nsfun(selector, context) { + return $(selector, context).ns(ns); + } + + // Allows you to add methods ala jQuery.fn (useful to namespace premade plugins) + nsfun.fn = methods[ns]; + + // Add a method. + nsfun.add = function(fname, fn) { + var new_funcs = typeof fname == "object" ? fname : {}; + // One method. + if (new_funcs !== fname) + new_funcs[fname] = fn; + // Group of methods. + $.each(new_funcs, function(fname, fn) { + methods[ns][fname] = function() { + fn.apply(this, arguments); + return this; + }; + }); + return this; + }; + + // Get methods. + nsfun.methods = function() { + return $.extend({}, methods[ns]); + }; + + return nsfun; + }; + // The only function that touches $.fn + $.fn.ns = function(ns) { + if (methods[ns]) $.extend(this, methods[ns]); + return this; + }; +})(jQuery); diff --git a/app/assets/javascripts/vendor/jquery.easy-slider-1.7.js b/app/assets/javascripts/vendor/jquery.easy-slider-1.7.js new file mode 100644 index 0000000..ff5518f --- /dev/null +++ b/app/assets/javascripts/vendor/jquery.easy-slider-1.7.js @@ -0,0 +1,225 @@ +/* + * Easy Slider 1.7 - jQuery plugin + * written by Alen Grakalic + * http://cssglobe.com/post/4004/easy-slider-15-the-easiest-jquery-plugin-for-sliding + * + * Copyright (c) 2009 Alen Grakalic (http://cssglobe.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * Built for jQuery library + * http://jquery.com + * + */ + +/* + * markup example for $("#slider").easySlider(); + * + *
+ *
    + *
  • + *
  • + *
  • + *
  • + *
  • + *
+ *
+ * + */ + +(function($) { + + $.fn.easySlider = function(options){ + + // default configuration properties + var defaults = { + prevId: 'prevBtn', + prevText: 'Previous', + nextId: 'nextBtn', + nextText: 'Next', + controlsShow: true, + controlsBefore: '', + controlsAfter: '', + controlsFade: true, + firstId: 'firstBtn', + firstText: 'First', + firstShow: false, + lastId: 'lastBtn', + lastText: 'Last', + lastShow: false, + vertical: false, + speed: 800, + auto: false, + pause: 2000, + continuous: false, + numeric: false, + numericId: 'controls' + }; + + var options = $.extend(defaults, options); + + this.each(function() { + var obj = $(this); + var s = $("li", obj).length; + var w = $("li", obj).width(); + var h = $("li", obj).height(); + var clickable = true; + obj.width(w); + obj.height(h); + obj.css("overflow","hidden"); + var ts = s-1; + var t = 0; + $("ul", obj).css('width',s*w); + + if(options.continuous){ + $("ul", obj).prepend($("ul li:last-child", obj).clone().css("margin-left","-"+ w +"px")); + $("ul", obj).append($("ul li:nth-child(2)", obj).clone()); + $("ul", obj).css('width',(s+1)*w); + }; + + if(!options.vertical) $("li", obj).css('float','left'); + + if(options.controlsShow){ + var html = options.controlsBefore; + if(options.numeric){ + html += '
    '; + } else { + if(options.firstShow) html += ''+ options.firstText +''; + html += ' '+ options.prevText +''; + html += ' '+ options.nextText +''; + if(options.lastShow) html += ' '+ options.lastText +''; + }; + + html += options.controlsAfter; + $(obj).after(html); + }; + + if(options.numeric){ + for(var i=0;i'+ (i+1) +'') + .appendTo($("#"+ options.numericId)) + .click(function(){ + animate($("a",$(this)).attr('rel'),true); + }); + }; + } else { + $("a","#"+options.nextId).click(function(){ + animate("next",true); + }); + $("a","#"+options.prevId).click(function(){ + animate("prev",true); + }); + $("a","#"+options.firstId).click(function(){ + animate("first",true); + }); + $("a","#"+options.lastId).click(function(){ + animate("last",true); + }); + }; + + function setCurrent(i){ + i = parseInt(i)+1; + $("li", "#" + options.numericId).removeClass("current"); + $("li#" + options.numericId + i).addClass("current"); + }; + + function adjust(){ + if(t>ts) t=0; + if(t<0) t=ts; + if(!options.vertical) { + $("ul",obj).css("margin-left",(t*w*-1)); + } else { + $("ul",obj).css("margin-left",(t*h*-1)); + } + clickable = true; + if(options.numeric) setCurrent(t); + }; + + function animate(dir,clicked){ + if (clickable){ + clickable = false; + var ot = t; + switch(dir){ + case "next": + t = (ot>=ts) ? (options.continuous ? t+1 : ts) : t+1; + break; + case "prev": + t = (t<=0) ? (options.continuous ? t-1 : 0) : t-1; + break; + case "first": + t = 0; + break; + case "last": + t = ts; + break; + default: + t = dir; + break; + }; + var diff = Math.abs(ot-t); + var speed = diff*options.speed; + if(!options.vertical) { + p = (t*w*-1); + $("ul",obj).animate( + { marginLeft: p }, + { queue:false, duration:speed, complete:adjust } + ); + } else { + p = (t*h*-1); + $("ul",obj).animate( + { marginTop: p }, + { queue:false, duration:speed, complete:adjust } + ); + }; + + if(!options.continuous && options.controlsFade){ + if(t==ts){ + $("a","#"+options.nextId).hide(); + $("a","#"+options.lastId).hide(); + } else { + $("a","#"+options.nextId).show(); + $("a","#"+options.lastId).show(); + }; + if(t==0){ + $("a","#"+options.prevId).hide(); + $("a","#"+options.firstId).hide(); + } else { + $("a","#"+options.prevId).show(); + $("a","#"+options.firstId).show(); + }; + }; + + if(clicked) clearTimeout(timeout); + if(options.auto && dir=="next" && !clicked){; + timeout = setTimeout(function(){ + animate("next",false); + },diff*options.speed+options.pause); + }; + + }; + + }; + // init + var timeout; + if(options.auto){; + timeout = setTimeout(function(){ + animate("next",false); + },options.pause); + }; + + if(options.numeric) setCurrent(0); + + if(!options.continuous && options.controlsFade){ + $("a","#"+options.prevId).hide(); + $("a","#"+options.firstId).hide(); + }; + + }); + + }; + +})(jQuery); + + diff --git a/app/assets/javascripts/vendor/jquery.survival-kit.coffee b/app/assets/javascripts/vendor/jquery.survival-kit.coffee new file mode 100644 index 0000000..654e167 --- /dev/null +++ b/app/assets/javascripts/vendor/jquery.survival-kit.coffee @@ -0,0 +1,36 @@ +Array.prototype.empty = -> + if this.length <= 0 + return true + else + return false + +$.ns('sk').add { + # Search Box Helper + searchBox: -> + input = $('input.text', this) + default_mes = input.val() + input.focus(-> + if input.val() == default_mes + input.val '' + ).blur(-> + if input.val() == '' + input.val default_mes + ) + + # Simple Form Style Helper. + simpleForms: -> + max = 0 + labels = $("div:not(.boolean) > label", this) + hints = $("div:not(.boolean) > .hint", this) + labels.each -> + if $(this).width() > max + max = $(this).width() + $('> .hint.padded', this).css 'padding-left' : max + + # Get the horizontal-spacing (set on the css.) + horizontal_spacing = parseInt(labels.first().css('margin-right')) + + hints.css 'padding-left' : (max + horizontal_spacing) + $('.actions', this).css 'padding-left' : (max + horizontal_spacing) + labels.width(max) +} diff --git a/app/assets/javascripts/vendor/jquery.tmpl.js b/app/assets/javascripts/vendor/jquery.tmpl.js new file mode 100644 index 0000000..a819d61 --- /dev/null +++ b/app/assets/javascripts/vendor/jquery.tmpl.js @@ -0,0 +1,486 @@ +/* + * jQuery Templating Plugin + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + */ +(function( jQuery, undefined ){ + var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /, + newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = []; + + function newTmplItem( options, parentItem, fn, data ) { + // Returns a template item data structure for a new rendered instance of a template (a 'template item'). + // The content field is a hierarchical array of strings and nested items (to be + // removed and replaced by nodes field of dom elements, once inserted in DOM). + var newItem = { + data: data || (parentItem ? parentItem.data : {}), + _wrap: parentItem ? parentItem._wrap : null, + tmpl: null, + parent: parentItem || null, + nodes: [], + calls: tiCalls, + nest: tiNest, + wrap: tiWrap, + html: tiHtml, + update: tiUpdate + }; + if ( options ) { + jQuery.extend( newItem, options, { nodes: [], parent: parentItem } ); + } + if ( fn ) { + // Build the hierarchical content to be used during insertion into DOM + newItem.tmpl = fn; + newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem ); + newItem.key = ++itemKey; + // Keep track of new template item, until it is stored as jQuery Data on DOM element + (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem; + } + return newItem; + } + + // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core). + jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" + }, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems, + parent = this.length === 1 && this[0].parentNode; + + appendToTmplItems = newTmplItems || {}; + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + insert[ original ]( this[0] ); + ret = this; + } else { + for ( i = 0, l = insert.length; i < l; i++ ) { + cloneIndex = i; + elems = (i > 0 ? this.clone(true) : this).get(); + jQuery.fn[ original ].apply( jQuery(insert[i]), elems ); + ret = ret.concat( elems ); + } + cloneIndex = 0; + ret = this.pushStack( ret, name, insert.selector ); + } + tmplItems = appendToTmplItems; + appendToTmplItems = null; + jQuery.tmpl.complete( tmplItems ); + return ret; + }; + }); + + jQuery.fn.extend({ + // Use first wrapped element as template markup. + // Return wrapped set of template items, obtained by rendering template against data. + tmpl: function( data, options, parentItem ) { + return jQuery.tmpl( this[0], data, options, parentItem ); + }, + + // Find which rendered template item the first wrapped DOM element belongs to + tmplItem: function() { + return jQuery.tmplItem( this[0] ); + }, + + // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template. + template: function( name ) { + return jQuery.template( name, this[0] ); + }, + + domManip: function( args, table, callback, options ) { + // This appears to be a bug in the appendTo, etc. implementation + // it should be doing .call() instead of .apply(). See #6227 + if ( args[0] && args[0].nodeType ) { + var dmArgs = jQuery.makeArray( arguments ), argsLength = args.length, i = 0, tmplItem; + while ( i < argsLength && !(tmplItem = jQuery.data( args[i++], "tmplItem" ))) {} + if ( argsLength > 1 ) { + dmArgs[0] = [jQuery.makeArray( args )]; + } + if ( tmplItem && cloneIndex ) { + dmArgs[2] = function( fragClone ) { + // Handler called by oldManip when rendered template has been inserted into DOM. + jQuery.tmpl.afterManip( this, fragClone, callback ); + }; + } + oldManip.apply( this, dmArgs ); + } else { + oldManip.apply( this, arguments ); + } + cloneIndex = 0; + if ( !appendToTmplItems ) { + jQuery.tmpl.complete( newTmplItems ); + } + return this; + } + }); + + jQuery.extend({ + // Return wrapped set of template items, obtained by rendering template against data. + tmpl: function( tmpl, data, options, parentItem ) { + var ret, topLevel = !parentItem; + if ( topLevel ) { + // This is a top-level tmpl call (not from a nested template using {{tmpl}}) + parentItem = topTmplItem; + tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl ); + wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level + } else if ( !tmpl ) { + // The template item is already associated with DOM - this is a refresh. + // Re-evaluate rendered template for the parentItem + tmpl = parentItem.tmpl; + newTmplItems[parentItem.key] = parentItem; + parentItem.nodes = []; + if ( parentItem.wrapped ) { + updateWrapped( parentItem, parentItem.wrapped ); + } + // Rebuild, without creating a new template item + return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) )); + } + if ( !tmpl ) { + return []; // Could throw... + } + if ( typeof data === "function" ) { + data = data.call( parentItem || {} ); + } + if ( options && options.wrapped ) { + updateWrapped( options, options.wrapped ); + } + ret = jQuery.isArray( data ) ? + jQuery.map( data, function( dataItem ) { + return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null; + }) : + [ newTmplItem( options, parentItem, tmpl, data ) ]; + return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret; + }, + + // Return rendered template item for an element. + tmplItem: function( elem ) { + var tmplItem; + if ( elem instanceof jQuery ) { + elem = elem[0]; + } + while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {} + return tmplItem || topTmplItem; + }, + + // Set: + // Use $.template( name, tmpl ) to cache a named template, + // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc. + // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration. + + // Get: + // Use $.template( name ) to access a cached template. + // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString ) + // will return the compiled template, without adding a name reference. + // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent + // to $.template( null, templateString ) + template: function( name, tmpl ) { + if (tmpl) { + // Compile template and associate with name + if ( typeof tmpl === "string" ) { + // This is an HTML string being passed directly in. + tmpl = buildTmplFn( tmpl ) + } else if ( tmpl instanceof jQuery ) { + tmpl = tmpl[0] || {}; + } + if ( tmpl.nodeType ) { + // If this is a template block, use cached copy, or generate tmpl function and cache. + tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML )); + } + return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl; + } + // Return named compiled template + return name ? (typeof name !== "string" ? jQuery.template( null, name ): + (jQuery.template[name] || + // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec) + jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null; + }, + + encode: function( text ) { + // Do HTML encoding replacing < > & and ' and " by corresponding entities. + return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'"); + } + }); + + jQuery.extend( jQuery.tmpl, { + tag: { + "tmpl": { + _default: { $2: "null" }, + open: "if($notnull_1){_=_.concat($item.nest($1,$2));}" + // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions) + // This means that {{tmpl foo}} treats foo as a template (which IS a function). + // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}. + }, + "wrap": { + _default: { $2: "null" }, + open: "$item.calls(_,$1,$2);_=[];", + close: "call=$item.calls();_=call._.concat($item.wrap(call,_));" + }, + "each": { + _default: { $2: "$index, $value" }, + open: "if($notnull_1){$.each($1a,function($2){with(this){", + close: "}});}" + }, + "if": { + open: "if(($notnull_1) && $1a){", + close: "}" + }, + "else": { + _default: { $1: "true" }, + open: "}else if(($notnull_1) && $1a){" + }, + "html": { + // Unecoded expression evaluation. + open: "if($notnull_1){_.push($1a);}" + }, + "=": { + // Encoded expression evaluation. Abbreviated form is ${}. + _default: { $1: "$data" }, + open: "if($notnull_1){_.push($.encode($1a));}" + }, + "!": { + // Comment tag. Skipped by parser + open: "" + } + }, + + // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events + complete: function( items ) { + newTmplItems = {}; + }, + + // Call this from code which overrides domManip, or equivalent + // Manage cloning/storing template items etc. + afterManip: function afterManip( elem, fragClone, callback ) { + // Provides cloned fragment ready for fixup prior to and after insertion into DOM + var content = fragClone.nodeType === 11 ? + jQuery.makeArray(fragClone.childNodes) : + fragClone.nodeType === 1 ? [fragClone] : []; + + // Return fragment to original caller (e.g. append) for DOM insertion + callback.call( elem, fragClone ); + + // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data. + storeTmplItems( content ); + cloneIndex++; + } + }); + + //========================== Private helper functions, used by code above ========================== + + function build( tmplItem, nested, content ) { + // Convert hierarchical content into flat string array + // and finally return array of fragments ready for DOM insertion + var frag, ret = content ? jQuery.map( content, function( item ) { + return (typeof item === "string") ? + // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM. + (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) : + // This is a child template item. Build nested template. + build( item, tmplItem, item._ctnt ); + }) : + // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}. + tmplItem; + if ( nested ) { + return ret; + } + + // top-level template + ret = ret.join(""); + + // Support templates which have initial or final text nodes, or consist only of text + // Also support HTML entities within the HTML markup. + ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) { + frag = jQuery( middle ).get(); + + storeTmplItems( frag ); + if ( before ) { + frag = unencode( before ).concat(frag); + } + if ( after ) { + frag = frag.concat(unencode( after )); + } + }); + return frag ? frag : unencode( ret ); + } + + function unencode( text ) { + // Use createElement, since createTextNode will not render HTML entities correctly + var el = document.createElement( "div" ); + el.innerHTML = text; + return jQuery.makeArray(el.childNodes); + } + + // Generate a reusable function that will serve to render a template against data + function buildTmplFn( markup ) { + return new Function("jQuery","$item", + "var $=jQuery,call,_=[],$data=$item.data;" + + + // Introduce the data as local variables using with(){} + "with($data){_.push('" + + + // Convert the template into pure JavaScript + jQuery.trim(markup) + .replace( /([\\'])/g, "\\$1" ) + .replace( /[\r\t\n]/g, " " ) + .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" ) + .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, + function( all, slash, type, fnargs, target, parens, args ) { + var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect; + if ( !tag ) { + throw "Template command not found: " + type; + } + def = tag._default || []; + if ( parens && !/\w$/.test(target)) { + target += parens; + parens = ""; + } + if ( target ) { + target = unescape( target ); + args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : ""); + // Support for target being things like a.toLowerCase(); + // In that case don't call with template item as 'this' pointer. Just evaluate... + expr = parens ? (target.indexOf(".") > -1 ? target + parens : ("(" + target + ").call($item" + args)) : target; + exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))"; + } else { + exprAutoFnDetect = expr = def.$1 || "null"; + } + fnargs = unescape( fnargs ); + return "');" + + tag[ slash ? "close" : "open" ] + .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" ) + .split( "$1a" ).join( exprAutoFnDetect ) + .split( "$1" ).join( expr ) + .split( "$2" ).join( fnargs ? + fnargs.replace( /\s*([^\(]+)\s*(\((.*?)\))?/g, function( all, name, parens, params ) { + params = params ? ("," + params + ")") : (parens ? ")" : ""); + return params ? ("(" + name + ").call($item" + params) : all; + }) + : (def.$2||"") + ) + + "_.push('"; + }) + + "');}return _;" + ); + } + function updateWrapped( options, wrapped ) { + // Build the wrapped content. + options._wrap = build( options, true, + // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string. + jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()] + ).join(""); + } + + function unescape( args ) { + return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null; + } + function outerHtml( elem ) { + var div = document.createElement("div"); + div.appendChild( elem.cloneNode(true) ); + return div.innerHTML; + } + + // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance. + function storeTmplItems( content ) { + var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m; + for ( i = 0, l = content.length; i < l; i++ ) { + if ( (elem = content[i]).nodeType !== 1 ) { + continue; + } + elems = elem.getElementsByTagName("*"); + for ( m = elems.length - 1; m >= 0; m-- ) { + processItemKey( elems[m] ); + } + processItemKey( elem ); + } + function processItemKey( el ) { + var pntKey, pntNode = el, pntItem, tmplItem, key; + // Ensure that each rendered template inserted into the DOM has its own template item, + if ( (key = el.getAttribute( tmplItmAtt ))) { + while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { } + if ( pntKey !== key ) { + // The next ancestor with a _tmplitem expando is on a different key than this one. + // So this is a top-level element within this template item + // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment. + pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0; + if ( !(tmplItem = newTmplItems[key]) ) { + // The item is for wrapped content, and was copied from the temporary parent wrappedItem. + tmplItem = wrappedItems[key]; + tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode], null, true ); + tmplItem.key = ++itemKey; + newTmplItems[itemKey] = tmplItem; + } + if ( cloneIndex ) { + cloneTmplItem( key ); + } + } + el.removeAttribute( tmplItmAtt ); + } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) { + // This was a rendered element, cloned during append or appendTo etc. + // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem. + cloneTmplItem( tmplItem.key ); + newTmplItems[tmplItem.key] = tmplItem; + pntNode = jQuery.data( el.parentNode, "tmplItem" ); + pntNode = pntNode ? pntNode.key : 0; + } + if ( tmplItem ) { + pntItem = tmplItem; + // Find the template item of the parent element. + // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string) + while ( pntItem && pntItem.key != pntNode ) { + // Add this element as a top-level node for this rendered template item, as well as for any + // ancestor items between this item and the item of its parent element + pntItem.nodes.push( el ); + pntItem = pntItem.parent; + } + // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering... + delete tmplItem._ctnt; + delete tmplItem._wrap; + // Store template item as jQuery data on the element + jQuery.data( el, "tmplItem", tmplItem ); + } + function cloneTmplItem( key ) { + key = key + keySuffix; + tmplItem = newClonedItems[key] = + (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent, null, true )); + } + } + } + + //---- Helper functions for template item ---- + + function tiCalls( content, tmpl, data, options ) { + if ( !content ) { + return stack.pop(); + } + stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options }); + } + + function tiNest( tmpl, data, options ) { + // nested template, using {{tmpl}} tag + return jQuery.tmpl( jQuery.template( tmpl ), data, options, this ); + } + + function tiWrap( call, wrapped ) { + // nested template, using {{wrap}} tag + var options = call.options || {}; + options.wrapped = wrapped; + // Apply the template, which may incorporate wrapped content, + return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item ); + } + + function tiHtml( filter, textOnly ) { + var wrapped = this._wrap; + return jQuery.map( + jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ), + function(e) { + return textOnly ? + e.innerText || e.textContent : + e.outerHTML || outerHtml(e); + }); + } + + function tiUpdate() { + var coll = this.nodes; + jQuery.tmpl( null, null, null, this).insertBefore( coll[0] ); + jQuery( coll ).remove(); + } +})( jQuery ); \ No newline at end of file diff --git a/app/assets/javascripts/vendor/modernizr-2.0.6.min.js b/app/assets/javascripts/vendor/modernizr-2.0.6.min.js new file mode 100755 index 0000000..52cd7e1 --- /dev/null +++ b/app/assets/javascripts/vendor/modernizr-2.0.6.min.js @@ -0,0 +1,1116 @@ +/*! + * Modernizr v2.0.6 + * http://www.modernizr.com + * + * Copyright (c) 2009-2011 Faruk Ates, Paul Irish, Alex Sexton + * Dual-licensed under the BSD or MIT licenses: www.modernizr.com/license/ + */ + +/* + * Modernizr tests which native CSS3 and HTML5 features are available in + * the current UA and makes the results available to you in two ways: + * as properties on a global Modernizr object, and as classes on the + * element. This information allows you to progressively enhance + * your pages with a granular level of control over the experience. + * + * Modernizr has an optional (not included) conditional resource loader + * called Modernizr.load(), based on Yepnope.js (yepnopejs.com). + * To get a build that includes Modernizr.load(), as well as choosing + * which tests to include, go to www.modernizr.com/download/ + * + * Authors Faruk Ates, Paul Irish, Alex Sexton, + * Contributors Ryan Seddon, Ben Alman + */ + +window.Modernizr = (function( window, document, undefined ) { + + var version = '2.0.6', + + Modernizr = {}, + + // option for enabling the HTML classes to be added + enableClasses = true, + + docElement = document.documentElement, + docHead = document.head || document.getElementsByTagName('head')[0], + + /** + * Create our "modernizr" element that we do most feature tests on. + */ + mod = 'modernizr', + modElem = document.createElement(mod), + mStyle = modElem.style, + + /** + * Create the input element for various Web Forms feature tests. + */ + inputElem = document.createElement('input'), + + smile = ':)', + + toString = Object.prototype.toString, + + // List of property values to set for css tests. See ticket #21 + prefixes = ' -webkit- -moz- -o- -ms- -khtml- '.split(' '), + + // Following spec is to expose vendor-specific style properties as: + // elem.style.WebkitBorderRadius + // and the following would be incorrect: + // elem.style.webkitBorderRadius + + // Webkit ghosts their properties in lowercase but Opera & Moz do not. + // Microsoft foregoes prefixes entirely <= IE8, but appears to + // use a lowercase `ms` instead of the correct `Ms` in IE9 + + // More here: http://github.com/Modernizr/Modernizr/issues/issue/21 + domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), + + ns = {'svg': 'http://www.w3.org/2000/svg'}, + + tests = {}, + inputs = {}, + attrs = {}, + + classes = [], + + featureName, // used in testing loop + + + // Inject element with style element and some CSS rules + injectElementWithStyles = function( rule, callback, nodes, testnames ) { + + var style, ret, node, + div = document.createElement('div'); + + if ( parseInt(nodes, 10) ) { + // In order not to give false positives we create a node for each test + // This also allows the method to scale for unspecified uses + while ( nodes-- ) { + node = document.createElement('div'); + node.id = testnames ? testnames[nodes] : mod + (nodes + 1); + div.appendChild(node); + } + } + + // '].join(''); + div.id = mod; + div.innerHTML += style; + docElement.appendChild(div); + + ret = callback(div, rule); + div.parentNode.removeChild(div); + + return !!ret; + + }, + + + // adapted from matchMedia polyfill + // by Scott Jehl and Paul Irish + // gist.github.com/786768 + testMediaQuery = function( mq ) { + + if ( window.matchMedia ) { + return matchMedia(mq).matches; + } + + var bool; + + injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) { + bool = (window.getComputedStyle ? + getComputedStyle(node, null) : + node.currentStyle)['position'] == 'absolute'; + }); + + return bool; + + }, + + + /** + * isEventSupported determines if a given element supports the given event + * function from http://yura.thinkweb2.com/isEventSupported/ + */ + isEventSupported = (function() { + + var TAGNAMES = { + 'select': 'input', 'change': 'input', + 'submit': 'form', 'reset': 'form', + 'error': 'img', 'load': 'img', 'abort': 'img' + }; + + function isEventSupported( eventName, element ) { + + element = element || document.createElement(TAGNAMES[eventName] || 'div'); + eventName = 'on' + eventName; + + // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those + var isSupported = eventName in element; + + if ( !isSupported ) { + // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element + if ( !element.setAttribute ) { + element = document.createElement('div'); + } + if ( element.setAttribute && element.removeAttribute ) { + element.setAttribute(eventName, ''); + isSupported = is(element[eventName], 'function'); + + // If property was created, "remove it" (by setting value to `undefined`) + if ( !is(element[eventName], undefined) ) { + element[eventName] = undefined; + } + element.removeAttribute(eventName); + } + } + + element = null; + return isSupported; + } + return isEventSupported; + })(); + + // hasOwnProperty shim by kangax needed for Safari 2.0 support + var _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty; + if ( !is(_hasOwnProperty, undefined) && !is(_hasOwnProperty.call, undefined) ) { + hasOwnProperty = function (object, property) { + return _hasOwnProperty.call(object, property); + }; + } + else { + hasOwnProperty = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ + return ((property in object) && is(object.constructor.prototype[property], undefined)); + }; + } + + /** + * setCss applies given styles to the Modernizr DOM node. + */ + function setCss( str ) { + mStyle.cssText = str; + } + + /** + * setCssAll extrapolates all vendor-specific css strings. + */ + function setCssAll( str1, str2 ) { + return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); + } + + /** + * is returns a boolean for if typeof obj is exactly type. + */ + function is( obj, type ) { + return typeof obj === type; + } + + /** + * contains returns a boolean for if substr is found within str. + */ + function contains( str, substr ) { + return !!~('' + str).indexOf(substr); + } + + /** + * testProps is a generic CSS / DOM property test; if a browser supports + * a certain property, it won't return undefined for it. + * A supported CSS property returns empty string when its not yet set. + */ + function testProps( props, prefixed ) { + for ( var i in props ) { + if ( mStyle[ props[i] ] !== undefined ) { + return prefixed == 'pfx' ? props[i] : true; + } + } + return false; + } + + /** + * testPropsAll tests a list of DOM properties we want to check against. + * We specify literally ALL possible (known and/or likely) properties on + * the element including the non-vendor prefixed one, for forward- + * compatibility. + */ + function testPropsAll( prop, prefixed ) { + + var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1), + props = (prop + ' ' + domPrefixes.join(ucProp + ' ') + ucProp).split(' '); + + return testProps(props, prefixed); + } + + /** + * testBundle tests a list of CSS features that require element and style injection. + * By bundling them together we can reduce the need to touch the DOM multiple times. + */ + /*>>testBundle*/ + var testBundle = (function( styles, tests ) { + var style = styles.join(''), + len = tests.length; + + injectElementWithStyles(style, function( node, rule ) { + var style = document.styleSheets[document.styleSheets.length - 1], + // IE8 will bork if you create a custom build that excludes both fontface and generatedcontent tests. + // So we check for cssRules and that there is a rule available + // More here: https://github.com/Modernizr/Modernizr/issues/288 & https://github.com/Modernizr/Modernizr/issues/293 + cssText = style.cssRules && style.cssRules[0] ? style.cssRules[0].cssText : style.cssText || "", + children = node.childNodes, hash = {}; + + while ( len-- ) { + hash[children[len].id] = children[len]; + } + + /*>>touch*/ Modernizr['touch'] = ('ontouchstart' in window) || hash['touch'].offsetTop === 9; /*>>touch*/ + /*>>csstransforms3d*/ Modernizr['csstransforms3d'] = hash['csstransforms3d'].offsetLeft === 9; /*>>csstransforms3d*/ + /*>>generatedcontent*/Modernizr['generatedcontent'] = hash['generatedcontent'].offsetHeight >= 1; /*>>generatedcontent*/ + /*>>fontface*/ Modernizr['fontface'] = /src/i.test(cssText) && + cssText.indexOf(rule.split(' ')[0]) === 0; /*>>fontface*/ + }, len, tests); + + })([ + // Pass in styles to be injected into document + /*>>fontface*/ '@font-face {font-family:"font";src:url("https://")}' /*>>fontface*/ + + /*>>touch*/ ,['@media (',prefixes.join('touch-enabled),('),mod,')', + '{#touch{top:9px;position:absolute}}'].join('') /*>>touch*/ + + /*>>csstransforms3d*/ ,['@media (',prefixes.join('transform-3d),('),mod,')', + '{#csstransforms3d{left:9px;position:absolute}}'].join('')/*>>csstransforms3d*/ + + /*>>generatedcontent*/,['#generatedcontent:after{content:"',smile,'";visibility:hidden}'].join('') /*>>generatedcontent*/ + ], + [ + /*>>fontface*/ 'fontface' /*>>fontface*/ + /*>>touch*/ ,'touch' /*>>touch*/ + /*>>csstransforms3d*/ ,'csstransforms3d' /*>>csstransforms3d*/ + /*>>generatedcontent*/,'generatedcontent' /*>>generatedcontent*/ + + ]);/*>>testBundle*/ + + + /** + * Tests + * ----- + */ + + tests['flexbox'] = function() { + /** + * setPrefixedValueCSS sets the property of a specified element + * adding vendor prefixes to the VALUE of the property. + * @param {Element} element + * @param {string} property The property name. This will not be prefixed. + * @param {string} value The value of the property. This WILL be prefixed. + * @param {string=} extra Additional CSS to append unmodified to the end of + * the CSS string. + */ + function setPrefixedValueCSS( element, property, value, extra ) { + property += ':'; + element.style.cssText = (property + prefixes.join(value + ';' + property)).slice(0, -property.length) + (extra || ''); + } + + /** + * setPrefixedPropertyCSS sets the property of a specified element + * adding vendor prefixes to the NAME of the property. + * @param {Element} element + * @param {string} property The property name. This WILL be prefixed. + * @param {string} value The value of the property. This will not be prefixed. + * @param {string=} extra Additional CSS to append unmodified to the end of + * the CSS string. + */ + function setPrefixedPropertyCSS( element, property, value, extra ) { + element.style.cssText = prefixes.join(property + ':' + value + ';') + (extra || ''); + } + + var c = document.createElement('div'), + elem = document.createElement('div'); + + setPrefixedValueCSS(c, 'display', 'box', 'width:42px;padding:0;'); + setPrefixedPropertyCSS(elem, 'box-flex', '1', 'width:10px;'); + + c.appendChild(elem); + docElement.appendChild(c); + + var ret = elem.offsetWidth === 42; + + c.removeChild(elem); + docElement.removeChild(c); + + return ret; + }; + + // On the S60 and BB Storm, getContext exists, but always returns undefined + // http://github.com/Modernizr/Modernizr/issues/issue/97/ + + tests['canvas'] = function() { + var elem = document.createElement('canvas'); + return !!(elem.getContext && elem.getContext('2d')); + }; + + tests['canvastext'] = function() { + return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); + }; + + // This WebGL test may false positive. + // But really it's quite impossible to know whether webgl will succeed until after you create the context. + // You might have hardware that can support a 100x100 webgl canvas, but will not support a 1000x1000 webgl + // canvas. So this feature inference is weak, but intentionally so. + + // It is known to false positive in FF4 with certain hardware and the iPad 2. + + tests['webgl'] = function() { + return !!window.WebGLRenderingContext; + }; + + /* + * The Modernizr.touch test only indicates if the browser supports + * touch events, which does not necessarily reflect a touchscreen + * device, as evidenced by tablets running Windows 7 or, alas, + * the Palm Pre / WebOS (touch) phones. + * + * Additionally, Chrome (desktop) used to lie about its support on this, + * but that has since been rectified: http://crbug.com/36415 + * + * We also test for Firefox 4 Multitouch Support. + * + * For more info, see: http://modernizr.github.com/Modernizr/touch.html + */ + + tests['touch'] = function() { + return Modernizr['touch']; + }; + + /** + * geolocation tests for the new Geolocation API specification. + * This test is a standards compliant-only test; for more complete + * testing, including a Google Gears fallback, please see: + * http://code.google.com/p/geo-location-javascript/ + * or view a fallback solution using google's geo API: + * http://gist.github.com/366184 + */ + tests['geolocation'] = function() { + return !!navigator.geolocation; + }; + + // Per 1.6: + // This used to be Modernizr.crosswindowmessaging but the longer + // name has been deprecated in favor of a shorter and property-matching one. + // The old API is still available in 1.6, but as of 2.0 will throw a warning, + // and in the first release thereafter disappear entirely. + tests['postmessage'] = function() { + return !!window.postMessage; + }; + + // Web SQL database detection is tricky: + + // In chrome incognito mode, openDatabase is truthy, but using it will + // throw an exception: http://crbug.com/42380 + // We can create a dummy database, but there is no way to delete it afterwards. + + // Meanwhile, Safari users can get prompted on any database creation. + // If they do, any page with Modernizr will give them a prompt: + // http://github.com/Modernizr/Modernizr/issues/closed#issue/113 + + // We have chosen to allow the Chrome incognito false positive, so that Modernizr + // doesn't litter the web with these test databases. As a developer, you'll have + // to account for this gotcha yourself. + tests['websqldatabase'] = function() { + var result = !!window.openDatabase; + /* if (result){ + try { + result = !!openDatabase( mod + "testdb", "1.0", mod + "testdb", 2e4); + } catch(e) { + } + } */ + return result; + }; + + // Vendors had inconsistent prefixing with the experimental Indexed DB: + // - Webkit's implementation is accessible through webkitIndexedDB + // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB + // For speed, we don't test the legacy (and beta-only) indexedDB + tests['indexedDB'] = function() { + for ( var i = -1, len = domPrefixes.length; ++i < len; ){ + if ( window[domPrefixes[i].toLowerCase() + 'IndexedDB'] ){ + return true; + } + } + return !!window.indexedDB; + }; + + // documentMode logic from YUI to filter out IE8 Compat Mode + // which false positives. + tests['hashchange'] = function() { + return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); + }; + + // Per 1.6: + // This used to be Modernizr.historymanagement but the longer + // name has been deprecated in favor of a shorter and property-matching one. + // The old API is still available in 1.6, but as of 2.0 will throw a warning, + // and in the first release thereafter disappear entirely. + tests['history'] = function() { + return !!(window.history && history.pushState); + }; + + tests['draganddrop'] = function() { + return isEventSupported('dragstart') && isEventSupported('drop'); + }; + + // Mozilla is targeting to land MozWebSocket for FF6 + // bugzil.la/659324 + tests['websockets'] = function() { + for ( var i = -1, len = domPrefixes.length; ++i < len; ){ + if ( window[domPrefixes[i] + 'WebSocket'] ){ + return true; + } + } + return 'WebSocket' in window; + }; + + + // http://css-tricks.com/rgba-browser-support/ + tests['rgba'] = function() { + // Set an rgba() color and check the returned value + + setCss('background-color:rgba(150,255,150,.5)'); + + return contains(mStyle.backgroundColor, 'rgba'); + }; + + tests['hsla'] = function() { + // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, + // except IE9 who retains it as hsla + + setCss('background-color:hsla(120,40%,100%,.5)'); + + return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); + }; + + tests['multiplebgs'] = function() { + // Setting multiple images AND a color on the background shorthand property + // and then querying the style.background property value for the number of + // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! + + setCss('background:url(https://),url(https://),red url(https://)'); + + // If the UA supports multiple backgrounds, there should be three occurrences + // of the string "url(" in the return value for elemStyle.background + + return /(url\s*\(.*?){3}/.test(mStyle.background); + }; + + + // In testing support for a given CSS property, it's legit to test: + // `elem.style[styleName] !== undefined` + // If the property is supported it will return an empty string, + // if unsupported it will return undefined. + + // We'll take advantage of this quick test and skip setting a style + // on our modernizr element, but instead just testing undefined vs + // empty string. + + + tests['backgroundsize'] = function() { + return testPropsAll('backgroundSize'); + }; + + tests['borderimage'] = function() { + return testPropsAll('borderImage'); + }; + + + // Super comprehensive table about all the unique implementations of + // border-radius: http://muddledramblings.com/table-of-css3-border-radius-compliance + + tests['borderradius'] = function() { + return testPropsAll('borderRadius'); + }; + + // WebOS unfortunately false positives on this test. + tests['boxshadow'] = function() { + return testPropsAll('boxShadow'); + }; + + // FF3.0 will false positive on this test + tests['textshadow'] = function() { + return document.createElement('div').style.textShadow === ''; + }; + + + tests['opacity'] = function() { + // Browsers that actually have CSS Opacity implemented have done so + // according to spec, which means their return values are within the + // range of [0.0,1.0] - including the leading zero. + + setCssAll('opacity:.55'); + + // The non-literal . in this regex is intentional: + // German Chrome returns this value as 0,55 + // https://github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 + return /^0.55$/.test(mStyle.opacity); + }; + + + tests['cssanimations'] = function() { + return testPropsAll('animationName'); + }; + + + tests['csscolumns'] = function() { + return testPropsAll('columnCount'); + }; + + + tests['cssgradients'] = function() { + /** + * For CSS Gradients syntax, please see: + * http://webkit.org/blog/175/introducing-css-gradients/ + * https://developer.mozilla.org/en/CSS/-moz-linear-gradient + * https://developer.mozilla.org/en/CSS/-moz-radial-gradient + * http://dev.w3.org/csswg/css3-images/#gradients- + */ + + var str1 = 'background-image:', + str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', + str3 = 'linear-gradient(left top,#9f9, white);'; + + setCss( + (str1 + prefixes.join(str2 + str1) + prefixes.join(str3 + str1)).slice(0, -str1.length) + ); + + return contains(mStyle.backgroundImage, 'gradient'); + }; + + + tests['cssreflections'] = function() { + return testPropsAll('boxReflect'); + }; + + + tests['csstransforms'] = function() { + return !!testProps(['transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform']); + }; + + + tests['csstransforms3d'] = function() { + + var ret = !!testProps(['perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective']); + + // Webkit’s 3D transforms are passed off to the browser's own graphics renderer. + // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in + // some conditions. As a result, Webkit typically recognizes the syntax but + // will sometimes throw a false positive, thus we must do a more thorough check: + if ( ret && 'webkitPerspective' in docElement.style ) { + + // Webkit allows this media query to succeed only if the feature is enabled. + // `@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }` + ret = Modernizr['csstransforms3d']; + } + return ret; + }; + + + tests['csstransitions'] = function() { + return testPropsAll('transitionProperty'); + }; + + + /*>>fontface*/ + // @font-face detection routine by Diego Perini + // http://javascript.nwbox.com/CSSSupport/ + tests['fontface'] = function() { + return Modernizr['fontface']; + }; + /*>>fontface*/ + + // CSS generated content detection + tests['generatedcontent'] = function() { + return Modernizr['generatedcontent']; + }; + + + + // These tests evaluate support of the video/audio elements, as well as + // testing what types of content they support. + // + // We're using the Boolean constructor here, so that we can extend the value + // e.g. Modernizr.video // true + // Modernizr.video.ogg // 'probably' + // + // Codec values from : http://github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 + // thx to NielsLeenheer and zcorpan + + // Note: in FF 3.5.1 and 3.5.0, "no" was a return value instead of empty string. + // Modernizr does not normalize for that. + + tests['video'] = function() { + var elem = document.createElement('video'), + bool = false; + + // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 + try { + if ( bool = !!elem.canPlayType ) { + bool = new Boolean(bool); + bool.ogg = elem.canPlayType('video/ogg; codecs="theora"'); + + // Workaround required for IE9, which doesn't report video support without audio codec specified. + // bug 599718 @ msft connect + var h264 = 'video/mp4; codecs="avc1.42E01E'; + bool.h264 = elem.canPlayType(h264 + '"') || elem.canPlayType(h264 + ', mp4a.40.2"'); + + bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"'); + } + + } catch(e) { } + + return bool; + }; + + tests['audio'] = function() { + var elem = document.createElement('audio'), + bool = false; + + try { + if ( bool = !!elem.canPlayType ) { + bool = new Boolean(bool); + bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"'); + bool.mp3 = elem.canPlayType('audio/mpeg;'); + + // Mimetypes accepted: + // https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements + // http://bit.ly/iphoneoscodecs + bool.wav = elem.canPlayType('audio/wav; codecs="1"'); + bool.m4a = elem.canPlayType('audio/x-m4a;') || elem.canPlayType('audio/aac;'); + } + } catch(e) { } + + return bool; + }; + + + // Firefox has made these tests rather unfun. + + // In FF4, if disabled, window.localStorage should === null. + + // Normally, we could not test that directly and need to do a + // `('localStorage' in window) && ` test first because otherwise Firefox will + // throw http://bugzil.la/365772 if cookies are disabled + + // However, in Firefox 4 betas, if dom.storage.enabled == false, just mentioning + // the property will throw an exception. http://bugzil.la/599479 + // This looks to be fixed for FF4 Final. + + // Because we are forced to try/catch this, we'll go aggressive. + + // FWIW: IE8 Compat mode supports these features completely: + // http://www.quirksmode.org/dom/html5.html + // But IE8 doesn't support either with local files + + tests['localstorage'] = function() { + try { + return !!localStorage.getItem; + } catch(e) { + return false; + } + }; + + tests['sessionstorage'] = function() { + try { + return !!sessionStorage.getItem; + } catch(e){ + return false; + } + }; + + + tests['webworkers'] = function() { + return !!window.Worker; + }; + + + tests['applicationcache'] = function() { + return !!window.applicationCache; + }; + + + // Thanks to Erik Dahlstrom + tests['svg'] = function() { + return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; + }; + + // specifically for SVG inline in HTML, not within XHTML + // test page: paulirish.com/demo/inline-svg + tests['inlinesvg'] = function() { + var div = document.createElement('div'); + div.innerHTML = ''; + return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; + }; + + // Thanks to F1lt3r and lucideer, ticket #35 + tests['smil'] = function() { + return !!document.createElementNS && /SVG/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); + }; + + tests['svgclippaths'] = function() { + // Possibly returns a false positive in Safari 3.2? + return !!document.createElementNS && /SVG/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); + }; + + // input features and input types go directly onto the ret object, bypassing the tests loop. + // Hold this guy to execute in a moment. + function webforms() { + // Run through HTML5's new input attributes to see if the UA understands any. + // We're using f which is the element created early on + // Mike Taylr has created a comprehensive resource for testing these attributes + // when applied to all input types: + // http://miketaylr.com/code/input-type-attr.html + // spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary + + // Only input placeholder is tested while textarea's placeholder is not. + // Currently Safari 4 and Opera 11 have support only for the input placeholder + // Both tests are available in feature-detects/forms-placeholder.js + Modernizr['input'] = (function( props ) { + for ( var i = 0, len = props.length; i < len; i++ ) { + attrs[ props[i] ] = !!(props[i] in inputElem); + } + return attrs; + })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); + + // Run through HTML5's new input types to see if the UA understands any. + // This is put behind the tests runloop because it doesn't return a + // true/false like all the other tests; instead, it returns an object + // containing each input type with its corresponding true/false value + + // Big thanks to @miketaylr for the html5 forms expertise. http://miketaylr.com/ + Modernizr['inputtypes'] = (function(props) { + + for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { + + inputElem.setAttribute('type', inputElemType = props[i]); + bool = inputElem.type !== 'text'; + + // We first check to see if the type we give it sticks.. + // If the type does, we feed it a textual value, which shouldn't be valid. + // If the value doesn't stick, we know there's input sanitization which infers a custom UI + if ( bool ) { + + inputElem.value = smile; + inputElem.style.cssText = 'position:absolute;visibility:hidden;'; + + if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { + + docElement.appendChild(inputElem); + defaultView = document.defaultView; + + // Safari 2-4 allows the smiley as a value, despite making a slider + bool = defaultView.getComputedStyle && + defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && + // Mobile android web browser has false positive, so must + // check the height to see if the widget is actually there. + (inputElem.offsetHeight !== 0); + + docElement.removeChild(inputElem); + + } else if ( /^(search|tel)$/.test(inputElemType) ){ + // Spec doesnt define any special parsing or detectable UI + // behaviors so we pass these through as true + + // Interestingly, opera fails the earlier test, so it doesn't + // even make it here. + + } else if ( /^(url|email)$/.test(inputElemType) ) { + // Real url and email support comes with prebaked validation. + bool = inputElem.checkValidity && inputElem.checkValidity() === false; + + } else if ( /^color$/.test(inputElemType) ) { + // chuck into DOM and force reflow for Opera bug in 11.00 + // github.com/Modernizr/Modernizr/issues#issue/159 + docElement.appendChild(inputElem); + docElement.offsetWidth; + bool = inputElem.value != smile; + docElement.removeChild(inputElem); + + } else { + // If the upgraded input compontent rejects the :) text, we got a winner + bool = inputElem.value != smile; + } + } + + inputs[ props[i] ] = !!bool; + } + return inputs; + })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); + } + + + // End of test definitions + // ----------------------- + + + + // Run through all tests and detect their support in the current UA. + // todo: hypothetically we could be doing an array of tests and use a basic loop here. + for ( var feature in tests ) { + if ( hasOwnProperty(tests, feature) ) { + // run the test, throw the return value into the Modernizr, + // then based on that boolean, define an appropriate className + // and push it into an array of classes we'll join later. + featureName = feature.toLowerCase(); + Modernizr[featureName] = tests[feature](); + + classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); + } + } + + // input tests need to run. + Modernizr.input || webforms(); + + + /** + * addTest allows the user to define their own feature tests + * the result will be added onto the Modernizr object, + * as well as an appropriate className set on the html element + * + * @param feature - String naming the feature + * @param test - Function returning true if feature is supported, false if not + */ + Modernizr.addTest = function ( feature, test ) { + if ( typeof feature == "object" ) { + for ( var key in feature ) { + if ( hasOwnProperty( feature, key ) ) { + Modernizr.addTest( key, feature[ key ] ); + } + } + } else { + + feature = feature.toLowerCase(); + + if ( Modernizr[feature] !== undefined ) { + // we're going to quit if you're trying to overwrite an existing test + // if we were to allow it, we'd do this: + // var re = new RegExp("\\b(no-)?" + feature + "\\b"); + // docElement.className = docElement.className.replace( re, '' ); + // but, no rly, stuff 'em. + return; + } + + test = typeof test == "boolean" ? test : !!test(); + + docElement.className += ' ' + (test ? '' : 'no-') + feature; + Modernizr[feature] = test; + + } + + return Modernizr; // allow chaining. + }; + + + // Reset modElem.cssText to nothing to reduce memory footprint. + setCss(''); + modElem = inputElem = null; + + //>>BEGIN IEPP + // Enable HTML 5 elements for styling (and printing) in IE. + if ( window.attachEvent && (function(){ var elem = document.createElement('div'); + elem.innerHTML = ''; + return elem.childNodes.length !== 1; })() ) { + + // iepp v2 by @jon_neal & afarkas : github.com/aFarkas/iepp/ + (function(win, doc) { + win.iepp = win.iepp || {}; + var iepp = win.iepp, + elems = iepp.html5elements || 'abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video', + elemsArr = elems.split('|'), + elemsArrLen = elemsArr.length, + elemRegExp = new RegExp('(^|\\s)('+elems+')', 'gi'), + tagRegExp = new RegExp('<(\/*)('+elems+')', 'gi'), + filterReg = /^\s*[\{\}]\s*$/, + ruleRegExp = new RegExp('(^|[^\\n]*?\\s)('+elems+')([^\\n]*)({[\\n\\w\\W]*?})', 'gi'), + docFrag = doc.createDocumentFragment(), + html = doc.documentElement, + head = html.firstChild, + bodyElem = doc.createElement('body'), + styleElem = doc.createElement('style'), + printMedias = /print|all/, + body; + function shim(doc) { + var a = -1; + while (++a < elemsArrLen) + // Use createElement so IE allows HTML5-named elements in a document + doc.createElement(elemsArr[a]); + } + + iepp.getCSS = function(styleSheetList, mediaType) { + if(styleSheetList+'' === undefined){return '';} + var a = -1, + len = styleSheetList.length, + styleSheet, + cssTextArr = []; + while (++a < len) { + styleSheet = styleSheetList[a]; + //currently no test for disabled/alternate stylesheets + if(styleSheet.disabled){continue;} + mediaType = styleSheet.media || mediaType; + // Get css from all non-screen stylesheets and their imports + if (printMedias.test(mediaType)) cssTextArr.push(iepp.getCSS(styleSheet.imports, mediaType), styleSheet.cssText); + //reset mediaType to all with every new *not imported* stylesheet + mediaType = 'all'; + } + return cssTextArr.join(''); + }; + + iepp.parseCSS = function(cssText) { + var cssTextArr = [], + rule; + while ((rule = ruleRegExp.exec(cssText)) != null){ + // Replace all html5 element references with iepp substitute classnames + cssTextArr.push(( (filterReg.exec(rule[1]) ? '\n' : rule[1]) +rule[2]+rule[3]).replace(elemRegExp, '$1.iepp_$2')+rule[4]); + } + return cssTextArr.join('\n'); + }; + + iepp.writeHTML = function() { + var a = -1; + body = body || doc.body; + while (++a < elemsArrLen) { + var nodeList = doc.getElementsByTagName(elemsArr[a]), + nodeListLen = nodeList.length, + b = -1; + while (++b < nodeListLen) + if (nodeList[b].className.indexOf('iepp_') < 0) + // Append iepp substitute classnames to all html5 elements + nodeList[b].className += ' iepp_'+elemsArr[a]; + } + docFrag.appendChild(body); + html.appendChild(bodyElem); + // Write iepp substitute print-safe document + bodyElem.className = body.className; + bodyElem.id = body.id; + // Replace HTML5 elements with which is print-safe and shouldn't conflict since it isn't part of html5 + bodyElem.innerHTML = body.innerHTML.replace(tagRegExp, '<$1font'); + }; + + + iepp._beforePrint = function() { + // Write iepp custom print CSS + styleElem.styleSheet.cssText = iepp.parseCSS(iepp.getCSS(doc.styleSheets, 'all')); + iepp.writeHTML(); + }; + + iepp.restoreHTML = function(){ + // Undo everything done in onbeforeprint + bodyElem.innerHTML = ''; + html.removeChild(bodyElem); + html.appendChild(body); + }; + + iepp._afterPrint = function(){ + // Undo everything done in onbeforeprint + iepp.restoreHTML(); + styleElem.styleSheet.cssText = ''; + }; + + + + // Shim the document and iepp fragment + shim(doc); + shim(docFrag); + + // + if(iepp.disablePP){return;} + + // Add iepp custom print style element + head.insertBefore(styleElem, head.firstChild); + styleElem.media = 'print'; + styleElem.className = 'iepp-printshim'; + win.attachEvent( + 'onbeforeprint', + iepp._beforePrint + ); + win.attachEvent( + 'onafterprint', + iepp._afterPrint + ); + })(window, document); + } + //>>END IEPP + + // Assign private properties to the return object with prefix + Modernizr._version = version; + + // expose these for the plugin API. Look in the source for how to join() them against your input + Modernizr._prefixes = prefixes; + Modernizr._domPrefixes = domPrefixes; + + // Modernizr.mq tests a given media query, live against the current state of the window + // A few important notes: + // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false + // * A max-width or orientation query will be evaluated against the current state, which may change later. + // * You must specify values. Eg. If you are testing support for the min-width media query use: + // Modernizr.mq('(min-width:0)') + // usage: + // Modernizr.mq('only screen and (max-width:768)') + Modernizr.mq = testMediaQuery; + + // Modernizr.hasEvent() detects support for a given event, with an optional element to test on + // Modernizr.hasEvent('gesturestart', elem) + Modernizr.hasEvent = isEventSupported; + + // Modernizr.testProp() investigates whether a given style property is recognized + // Note that the property names must be provided in the camelCase variant. + // Modernizr.testProp('pointerEvents') + Modernizr.testProp = function(prop){ + return testProps([prop]); + }; + + // Modernizr.testAllProps() investigates whether a given style property, + // or any of its vendor-prefixed variants, is recognized + // Note that the property names must be provided in the camelCase variant. + // Modernizr.testAllProps('boxSizing') + Modernizr.testAllProps = testPropsAll; + + + + // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards + // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) + Modernizr.testStyles = injectElementWithStyles; + + + // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input + // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' + + // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style. + // Return values will also be the camelCase variant, if you need to translate that to hypenated style use: + // + // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); + + // If you're trying to ascertain which transition end event to bind to, you might do something like... + // + // var transEndEventNames = { + // 'WebkitTransition' : 'webkitTransitionEnd', + // 'MozTransition' : 'transitionend', + // 'OTransition' : 'oTransitionEnd', + // 'msTransition' : 'msTransitionEnd', // maybe? + // 'transition' : 'transitionEnd' + // }, + // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; + + Modernizr.prefixed = function(prop){ + return testPropsAll(prop, 'pfx'); + }; + + + + // Remove "no-js" class from element, if it exists: + docElement.className = docElement.className.replace(/\bno-js\b/, '') + + // Add the new classes to the element. + + (enableClasses ? ' js ' + classes.join(' ') : ''); + + return Modernizr; + +})(this, this.document); \ No newline at end of file diff --git a/app/assets/stylesheets/api/rows.css.scss b/app/assets/stylesheets/api/rows.css.scss new file mode 100644 index 0000000..4fad3d5 --- /dev/null +++ b/app/assets/stylesheets/api/rows.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the api/rows controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/app/layouts/_app.scss b/app/assets/stylesheets/app/layouts/_app.scss new file mode 100644 index 0000000..abbe8b1 --- /dev/null +++ b/app/assets/stylesheets/app/layouts/_app.scss @@ -0,0 +1,24 @@ +// Application Layout +// ---------------------------------------- +// TODO: This generates huge selectors.. +.app { + + #main { + @include center-container(1000px, 0, 0 8px); + @include clearfix; + @include debug(gray); + } + + $sidebar: 215px; + $content: 758px; + + .container { + & > aside { + @include float(left, $sidebar, green); + } + + & > .content { + @include float(right, $content, blue); + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/app/layouts/_conference.scss b/app/assets/stylesheets/app/layouts/_conference.scss new file mode 100644 index 0000000..18fb232 --- /dev/null +++ b/app/assets/stylesheets/app/layouts/_conference.scss @@ -0,0 +1,136 @@ +// Conference +// ---------------------------------------- + +// The Usual Box with Mute, Cross +.actor { + @include clearfix; + @include gradient(#FDFDFD, #EFEFEF); + @include size(auto, 50px,6px, 1px 0 1px 0); + border-bottom:1px solid #E3E3E8; + border-top:1px solid #FFF; + img { + @extend .ext-bradius-inner; + float:left; + margin-right:6px; + } + .info { + .name { + display:block; + font-size:size(15px); + width:188px; + } + .status { + color:#818181; + } + float:left; + } + .voice-actions { + float:right; + } +} + +// Audio Controls. +.voice-actions { + padding:10px 0; + text-align:right; + width:100px; + .make.speaker { + @include image-replace('icons/microphone-16x.png'); + display:inline-block; + } + .make.listener { + @include image-replace('icons/headphones-16x.png'); + display:inline-block; + } + + .voice { + &.muted { + @include image-replace('icons/mute-16x.png'); + display:inline-block; + } + &.unmuted { + @include image-replace('icons/unmute-16x.png'); + display:inline-block; + } + } + .remove { + @include image-replace('icons/cross-16x.png'); + display:inline-block; + } + a { + margin:0 5px; + opacity:0.6; + &:hover { opacity:1.0;} + } +} + + +.conference { + @include pie-clearfix(); + .panel { + @include box-shadow(1px 1px 0px #FFF inset, 1px 1px 1px #EDEDED); + background:#F7F7F7; + border:1px solid #E3E3E8; + float:left; + margin: 0 10px; + width: 303px; + &.speakers { + header { @include gradient(#FFF, #F3F3DE); } + h3 { background:transparent image-url('icons/microphone-32x.png') left top no-repeat; } + } + &.listeners { + header { @include gradient(#FFF, #DCEAF2); } + h3 { background:transparent image-url('icons/headphones-32x.png') left top no-repeat; } + } + &.log { + header { @include gradient(#FFF, #E7E7E7); } + h3 { background:transparent image-url('icons/clock-32x.png') left top no-repeat; } + } + } + .first { margin-left:0;} + .last { margin-right:0;} + header { + @include box-shadow(1px 1px 0px #FFF inset, 0px 1px 0px #FFF); + border-bottom:1px solid #E3E3E8; + padding:10px; + } + h3 { + font-size:size(24px); + font-weight:normal; + margin:0; + opacity:0.8; + padding:2px 2px 2px 43px; + } + .message { + @include gradient( #EEEEEE, #fff); + border-bottom:1px solid #E3E3E8; + padding:10px; + input { + @include input-effects; + @include size(283px, auto, 5px, 1px solid #E3E3E8 ); + color:#696969; + } + } + .actors, .messages { + height:290px; // This should be X times the amount of items + overflow:auto; + } +} + +.log { + .messages { + color:#484848; + div { + background:#FFF; + border-bottom:1px solid #E3E3E8; + border-top:1px solid #FFF; + padding:5px 10px; + } + .status { + background:#F5F5F5; + } + .name { + font-weight:bold; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/app/layouts/_phone-book-entry.scss b/app/assets/stylesheets/app/layouts/_phone-book-entry.scss new file mode 100644 index 0000000..f9e2345 --- /dev/null +++ b/app/assets/stylesheets/app/layouts/_phone-book-entry.scss @@ -0,0 +1,176 @@ +// Phone Book Entry Show +// ---------------------------------------- +// Icons: http://www.iconfinder.com/search/?q=iconset%3Acc_mono_icon_set +//http://www.iconfinder.com/search/?q=iconset%3AHelveticons_Social +section.phone-book-entry { + @include clearfix; + a { + @include link-colors(#49494D, $link-color); + } + .sidebar { + @include size(220px, auto, none, 0 1px 0 0); + border-right:1px solid #E3E3E8; + float:left; + } + .content { + float:right; + width:716px; + + } + + .username { + font-size: 46px; + font-weight:normal; + margin: 0 0 10px 0; + } + .personal { + margin-top:5px; + span { + margin-right:10px; + padding-left:22px; + } + .nickname { + background:transparent image-url('icons/user-16x.png') left 1px no-repeat; + } + .birthday { + background:transparent image-url('icons/star-16x.png') left -1px no-repeat;; + } + } + + .work { + font-size: 18px; + } + + .tags { + background:transparent image-url('icons/tag-16x.png') left 0px no-repeat; + margin-top:10px; + padding-left:22px; + } + + + .activity { + & > h2 { + font-size: 27px; + font-weight: normal; + } + textarea { + border-color: #E3E3E8; + height: 15px; + padding: 12px; + width: 689px; + } + .entry { + @include clearfix; + background-position: 12px 9px; + border-bottom: 1px solid #E3E3E8; + font-size: 16px; + + padding: 12px 0px 15px 55px; + .motive { + + display: block; + float: left; + line-height: 25px; + width: 529px; + + } + .timestamp { + color: #929298; + float: right; + font-size: 12px; + line-height: 26px; + } + } + } + + + // Icons + .home { + background:transparent image-url('icons/house-32x.png') left 2px no-repeat; + } + .office { + background:transparent image-url('icons/suitcase-32x.png') left 2px no-repeat; + } + .cellphone { + background:transparent image-url('icons/phone-mobile-32x.png') left 2px no-repeat; + } + .phone { + background:transparent image-url('icons/phone-up-32x.png') left 2px no-repeat; + } + .phone-down { + background:transparent image-url('icons/phone-down-32x.png') left 2px no-repeat; + } + .fax { + background:transparent image-url('icons/fax-32x.png') left 2px no-repeat; + } + .skype { + background:transparent image-url('icons/skype-32x.png') left 2px no-repeat; + } + .twitter { + background:transparent image-url('icons/twitter-32x.png') left 2px no-repeat; + } + .voice-message { + background:transparent image-url('icons/mic-32x.png') left 2px no-repeat; + } + + + + .comment { + @include clearfix; + border-bottom:1px solid #E3E3E8; + padding: 11px; + .display { + float:left; + overflow:hidden; + } + .info { + margin-bottom:4px; + } + .info,.body { + padding-left:47px; + } + .commenter { + font-size: 17px; + font-weight: bold; + } + .time { + color:#929298; + } + .info { + display:block; + } + } + + + .display { + @include border-radius(10px 0 10px 0); + } + .description { + margin:10px 0; + } + .widget { + border-top:1px solid #E3E3E8; + padding:23px 0; + width:200px; // Width of the image + div { + margin:10px 0; + padding-left:51px; + a { + color:#4B4B4B; + display:block; + font-size:size(17px); + font-weight:bold; + } + span { + color:#E3E3E8; + font-size:size(12px); + } + } + + &.adresses { + strong { + display:block; + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/app/pages/_phone_book.scss b/app/assets/stylesheets/app/pages/_phone_book.scss new file mode 100644 index 0000000..3670e8e --- /dev/null +++ b/app/assets/stylesheets/app/pages/_phone_book.scss @@ -0,0 +1,25 @@ +header.tabular-nav { @extend .ext-bradius-t; } +footer.tabular-nav { @extend .ext-bradius-b; } + +.tabular-nav { + @include clearfix; + line-height: 41px; + padding: 0 14px; + @include gradient(#69ABFB, #75AAEC); + .pagination { + float:right; + } + nav { + float:left; + margin-top: 1px; + @include size(395px, auto); + ol { + @include inline-list; + a { + font-weight:bold; + color:#fff; + font-size: 14px; + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/app/shared/_contents.scss b/app/assets/stylesheets/app/shared/_contents.scss new file mode 100644 index 0000000..8f88802 --- /dev/null +++ b/app/assets/stylesheets/app/shared/_contents.scss @@ -0,0 +1,374 @@ +// Contents +// ----------------------------------------´ + +// Wrap Everything +#container { + margin:0 auto; + width:$site-total-width; +} + +// Global Layout. +#content { + @extend .ext-bradius; + @include gradient(#fff, #FAF9FA); + border:1px solid #C0C0CA; + .light { + @extend .ext-bradius; + border:1px solid #ECECEF; + padding:20px; + } +} + + +// Content Header Object +// ---------------------------------------- + +.breadcrumbs { + color:$border-dark-color; + font-size:size(12px); + margin-bottom: 5px; + a { + @include link-colors(#8A8A91, $link-hover-color, #8A8A91, #8A8A91); + } +} + +header.main { + border-bottom:1px solid $border-dark-color; + margin-bottom:20px; + h1,h2 { + color:#49494D; + margin-top:0px; + } +} + + +// Flash Notice Object +// ---------------------------------------- +.flash { + @extend .ext-bradius; + @include box-shadow(0px 1px 2px darken(#F0F0F3, 20%)); + font-weight:bold; + margin-bottom:$vertical-margin; + position:relative; + .sign { + $sign-height: 26px; + @include pos-y-center($sign-height); + font-family:Georgia; + font-size:23px; + height:$sign-height; + } + .light { + @extend .ext-texture; + background-position:left bottom; + padding:15px 21px; + } + .message { + line-height:18px; + padding-left:26px; + } + + &.notice, &.flash { + @include text-shadow(1px 1px 1px #EDF3FA); + background:#CCE4FF; + color:#37547B; + } + + &.warning, &.alert { + $border-bottom: #EE9C00; + $border-light:#EDCC37; + @include text-shadow(1px 1px 1px #FEF6D7); + background:#FFE070; + border:{ + bottom:1px solid $border-bottom; + top:1px solid lighten(saturate($border-light, 5%), 9%); + }; + color:#7A2300; + } +} + + +// Scaffolding Tables +// ---------------------------------------- +table { + th { + color: #6A747B; + font-size: 14px; + font-weight: bold; + padding: 10px 8px; + } + .odd { + background:#F4F6FB; + border-bottom:1px solid #EDEFF8; + } + td { + padding:8px; + } +} + + +// Pagination Object +// ---------------------------------------- +.pagination { @include pagination(); } + + +// Entries Object. +// ---------------------------------------- +header.entries-nav { @extend .ext-bradius-inner-t; } +footer.entries-nav { @extend .ext-bradius-inner-b; } + +.entries-nav { + @include box-shadow( + inset 0px 1px 0px #aad2ff, // Light Top + 0 -1px 0px #A3C9E7, // Shadow Top + inset 0 -1px 2px #518CBC // Shadow Bottom + ); + @include clearfix; + @include gradient(#69ABFB, #75AAEC); + line-height: 41px; + padding: 0 14px; + nav { + color:#5490C3; + float:left; + margin-top: 1px; + width:540px; + } + // ABC + ol { + @include inline-list; + a { + @include link-colors(#fff, #FFFFC2); + @include text-shadow(0 2px 0px #5D96CC); + font-size: 14px; + font-weight:bold; + padding:0 3px; + } + } + .pagination { + float:right; + } +} + + +// Phone Book Entry +// ---------------------------------------- + +// Title Extension +.ext-pbe-big { display:block; font-size:size(18px); font-weight:bold; } + +.phone-book-entries { + table { margin:0;} +} +// Listing Version +tr.phone-book-entry { + @include clearfix; + color:#7F7F7F; + td { + vertical-align:middle; + } + &.odd { + background:#F4F6FB; + border-bottom:1px solid #EDEFF8; + } + img { + opacity:0.8; + } + // All Link colors + a { + @include link-colors(#577DA2, $link-color); + } + .name { + @extend .ext-pbe-big; + } + .company { + font-size:size(16px); + } + .user { + @include size(34%); + } + .phone { + @extend .ext-pbe-big; + } + .contact { + @include size(28%); + } + .extra { + @include size(38%); + } +} + +// Call History Entry +// ---------------------------------------- + +.call-history-entries { + table { margin:0;} +} + +// Listing Version +tr.call-history-entry { + @include clearfix; + color:#7F7F7F; + td { + vertical-align:middle; + } + &.odd { + background:#F4F6FB; + border-bottom:1px solid #EDEFF8; + } + img { + opacity:0.8; + } + // All Link colors + a { + @include link-colors(#577DA2, $link-color); + } + .select_box { + @include size(2%); + } + .thumbnail { + @include size(5%); + } + .time { + @include size(15%); + } + .user { + @include size(45%); + } + .status { + @include size(15%); + } + .actions { + @include size(5%); + } + .name { + @extend .ext-pbe-big; + } + .phone { + @extend .ext-pbe-big; + } + .call-forwarded { + background:transparent image-url('icons/gs_forward_16x.png') no-repeat; + padding-left: 20px; + } + .call-placed { + background:transparent image-url('icons/gs_placed_16x.png') no-repeat; + padding-left: 20px; + } + .call-received { + background:transparent image-url('icons/gs_received_16x.png') no-repeat; + padding-left: 20px; + } + .call-missed { + background:transparent image-url('icons/gs_missed_16x.png') no-repeat; + padding-left: 20px; + } + .voicemail { + background:transparent image-url('icons/gs_envelope_16x.png') no-repeat; + padding-left: 20px; + } + .duration { + @extend .ext-pbe-big; + } +} + +// Voicemail Messages Entry +// ---------------------------------------- + +.voicemail-messages-entries { + table { margin:0;} +} + +// Listing Version +tr.voicemail-messages-entry { + @include clearfix; + color:#7F7F7F; + td { + vertical-align:middle; + } + &.odd { + background:#F4F6FB; + border-bottom:1px solid #EDEFF8; + } + img { + opacity:0.8; + } + // All Link colors + a { + @include link-colors(#577DA2, $link-color); + } + .select_box { + @include size(2%); + } + .time { + @include size(15%); + } + .folder { + @include size(10%); + } + .user { + @include size(40%); + } + .status { + @include size(5%); + } + .actions { + @include size(5%); + } + .name { + @extend .ext-pbe-big; + } + .phone { + @extend .ext-pbe-big; + } + .voicemail-received { + background:transparent image-url('icons/gs_received_16x.png') no-repeat; + padding-left: 20px; + } + .voicemail-read { + background:transparent image-url('icons/gs_envelope_16x.png') no-repeat; + padding-left: 20px; + } + .duration { + @extend .ext-pbe-big; + } +} + + +// users#show +// ---------------------------------------- +#user-show { + @include clearfix; + .display { + float:left; + } + aside { + float:left; + padding-top:5px; + width: 250px; + p { + margin-left:70+26px; + } + } + section { + float:right; + width:650px; + h2:first-child { + margin-top:0; + } + } +} + + +// Simple Form Error Messgaes +// ----------------------------------------´ +p.error_notification { + @include gradient(mix(#EB6565, #994F5D, 60%), #994F5D); + border: 1px solid #7B404A; + border-radius: 4px; + box-shadow: inset 1px 1px 0px darken(#f3a0a0, 10%),inset -1px -1px 0px darken(#eb6565, 3%); + color:#FFF; + font-weight: bold; + margin: 0 0 0 158px; + padding: 10px 15px; + text-shadow: 2px 2px 1px #82434E; + width: 282px; +} \ No newline at end of file diff --git a/app/assets/stylesheets/app/shared/_footers.scss b/app/assets/stylesheets/app/shared/_footers.scss new file mode 100644 index 0000000..be0f54f --- /dev/null +++ b/app/assets/stylesheets/app/shared/_footers.scss @@ -0,0 +1,90 @@ +// Footers +// ---------------------------------------- + +//@include sticky-footer(54px, "#container", "#footer-spacing", "footer"); + +footer#main { + @include clearfix; + position:relative; + margin:$vertical-margin 0; + @include gradient(#fff, #FAF9FA); + border:1px solid #C0C0CA; + @extend .ext-bradius; + .light { + padding:10px 20px; + border:1px solid #ECECEF; + @extend .ext-bradius; + } + + ul { + @include clearfix; + list-style:none; + float:left; + margin:0; + li { + float:left; + margin-bottom: 0px; + } + a { + @include debug; + line-height:43px; + padding: 10px 20px; + border:{ + left:1px solid $border-dark-color; + }; + } + li:first-child a{ + border-left:none; + } +// @include horizontal-navigation( +// /*$height : */ 32px, +// /*$color : */ red, +// /*$hover-color : */ red, +// /*$active-color : */ red, +// /*$text-shadow : */ #530008, +// /*$bg : */ none, +// /*$bg-hover : */ none, +// /*$bg-active : */ none, +// /*$box-shadow : */ none, +// /*$box-shadow-hover : */ none, +// /*$box-shadow-active : */ none, +// /*$border-light : */ #E3E3E8, +// /*$border-shadow : */ lighten(#E3E3E8,5%), +// /*$padding : */ 0 10px, +// /*$margin : */ none, +// /*\border-radius : */ none, +// /*$font-weight : */ none, +// /*$font-size : */ 14px, +// /*$tab-space : */ none +// ); + } + +} + +.amooma-logo { + background:#FAF9FA; + @include debug; + float:right; + padding:7px 7px 7px 16px; + @include border-radius(0 $global-border-radius $global-border-radius 0); + border-left:1px solid $border-dark-color; + @include box-shadow(-1px -1px 1px #FFFFFF); + a { + @include image-replace('amooma-logo.png'); + float:right; + opacity:0.6; + margin: 5px 5px 0 0; + &:hover { + opacity:1; + } + } + span { + @include debug; + font-size:11px; + color:#74748B; + float:left; + line-height:30px; + margin-right:10px; + } + +} \ No newline at end of file diff --git a/app/assets/stylesheets/app/shared/_handheld.scss b/app/assets/stylesheets/app/shared/_handheld.scss new file mode 100644 index 0000000..1efc5e9 --- /dev/null +++ b/app/assets/stylesheets/app/shared/_handheld.scss @@ -0,0 +1,25 @@ +// +// Media queries for responsive design. +// +// These follow after primary styles so they will successfully override. +// + +@media all and (orientation:portrait) { + // Style adjustments for portrait mode goes here + +} + +@media all and (orientation:landscape) { + // Style adjustments for landscape mode goes here + +} + +// Grade-A Mobile Browsers (Opera Mobile, Mobile Safari, Android Chrome) +// consider this: www.cloudfour.com/css-media-query-for-mobile-is-fools-gold/ +@media screen and (max-device-width: 480px) { + + + // Uncomment if you don't want iOS and WinMobile to mobile-optimize the text for you: j.mp/textsizeadjust + // html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } +} + diff --git a/app/assets/stylesheets/app/shared/_headers.scss b/app/assets/stylesheets/app/shared/_headers.scss new file mode 100644 index 0000000..6f8f0fe --- /dev/null +++ b/app/assets/stylesheets/app/shared/_headers.scss @@ -0,0 +1,145 @@ +// Headers +// ---------------------------------------- + +// -- File Variables ---------------------- + +$border-light:#74B8EA; +$background: #F0F0F3; +$user-context-width:227px; + + +// ---------------------------------------- + +.ext-texture { + background:transparent image-url('gradients/white-texture-x63.png') left top repeat-x; +} + +.ext-header { + max-width:$site-total-width - 2px; // Minus the borders + height:63px; + @extend .ext-bradius; +} + + +// Header Object +// ---------------------------------------- + +header#main { + @extend .ext-header; + background:$background image-url('gradients/light-to-dark-blue-x63.png') left top repeat-x; + margin:$vertical-margin 0; + border:1px solid $border-light; + border-bottom:1px solid #518CBC; + border-top:1px solid lighten(saturate($border-light, 5%), 9%); + @include box-shadow(0px 1px 2px darken(#F0F0F3, 20%)); + position:relative; + overflow:hidden; + .light { + @extend .ext-header; + @extend .ext-texture; + height:62px; + border-bottom:1px solid $border-light; + + // Used as a spacer for the width + padding:0 $user-context-width 0 0; + } + + // Resizable Navigation Items. + span { + @include debug; + float:left; + margin-left:15px; + font-size:size(17px); + line-height:62px; + display:inline; + float:left; + margin-right:7px; + } + + .message { + margin-left:206px; + } + + a { + @include text-shadow(1px 1px 0px darken(#518CBC, 12%)); + color:#fff; + &:active, &:hover { + color:#FFFF70; + text-decoration:none; + } + } +} + +// User Context Object +// ---------------------------------------- + +.user-context { + @include pos(0 10px); + @include size(200px); + padding-left:12px; + text-align:center; + font-size:size(17px); + line-height:62px; + color:#DDDDDD; + border-left:1px solid $border-light; + @include box-shadow(-1px -1px 0px #3A91DE); + // Logged out version of this little guy. + .display { + @extend .ext-bradius-inner; + @include box-shadow(0 -1px 0px #518CBC, 0 1px 0 #74B8EA); + vertical-align: middle; + margin: -2px 7px 0 0; + } + .logged-out { + @include box-shadow(none); + } + .user { + width: 170px; + display: block; + text-align:left; + } + .logout { + display: block; + position: absolute; + top: 0; + right: 0; + } +} + + +// Logo Object +// ---------------------------------------- + +.gemeinschaft-logo { + @include logo('logo.png'); + @include pos(2px 0 0 10px); + @include debug; +} + + +// Search Box Object +// ---------------------------------------- + +.search-box { + @extend .ext-bradius-inner; + float: left; + margin-left: 200px; + margin-top: 18px; + border-bottom: 1px solid #74B7EB; + position: relative; + input.text { + z-index: 0; + @extend .ext-bradius-inner; + background: #fff image-url('gradients/white-gray-x29-reverse.png') left top repeat-x; + line-height: 25px; + height: 25px; + border: 1px solid #3472B2; + @include size(160px, 25px, 0 23px 0 12px); + } + input[type="submit"] { + @include pos(5px 6px 0 0); + @include image-replace('icons/search-13x16.png'); + border:0; + } +} + diff --git a/app/assets/stylesheets/app/shared/_ie.scss b/app/assets/stylesheets/app/shared/_ie.scss new file mode 100644 index 0000000..afbe7e4 --- /dev/null +++ b/app/assets/stylesheets/app/shared/_ie.scss @@ -0,0 +1,7 @@ +// Internet Explorer Hate File. +// ---------------------------------------- +// Here you'll find all css which it's focused at a specific browser. +@if in-compatibility-mode() { + .ie7 {} + .ie8 {} +} \ No newline at end of file diff --git a/app/assets/stylesheets/app/shared/_media.scss b/app/assets/stylesheets/app/shared/_media.scss new file mode 100644 index 0000000..c528a1f --- /dev/null +++ b/app/assets/stylesheets/app/shared/_media.scss @@ -0,0 +1,16 @@ +// Survival ✚ Kit +// Normalize.css + +//PLACEHOLDER Media Queries for Responsive Design. +//These override the primary ('mobile first') styles +//Modify as content requires. + +@media only screen and (min-width: 480px) { + /* Style adjustments for viewports 480px and over go here */ + +} + +@media only screen and (min-width: 768px) { + /* Style adjustments for viewports 768px and over go here */ + +} \ No newline at end of file diff --git a/app/assets/stylesheets/app/shared/_print.scss b/app/assets/stylesheets/app/shared/_print.scss new file mode 100644 index 0000000..c8594e4 --- /dev/null +++ b/app/assets/stylesheets/app/shared/_print.scss @@ -0,0 +1,17 @@ +// Survival ✚ Kit +// Normalize.css + +@media print { + * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */ + a, a:visited { text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */ + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } /* h5bp.com/t */ + tr, img { page-break-inside: avoid; } + img { max-width: 100% !important; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3 { page-break-after: avoid; } +} \ No newline at end of file diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss new file mode 100644 index 0000000..32e54f4 --- /dev/null +++ b/app/assets/stylesheets/application.css.scss @@ -0,0 +1,117 @@ +// Survival ✚ Kit [ Bootstrapper File ] + +// Dependencies +// ---------------------------------------- +@import "vendor/survival-kit/secure"; +@import "compass"; +@import "vendor/boilerplate-1.0/reset"; +@import "vendor/survival-kit/loader"; +@import "vendor/fancy-buttons/fancy-buttons"; + + +// Project Variables +// ---------------------------------------- +// Use @include debug; to show a color overlay on the element when this is set to true. +$debug : false; +// Typography +$base-font-family : "Helvetica Neue", Arial, Helvetica, sans-serif; +$base-font-size : 13px; +$base-line-height : 1.231; +$font-color : #222; +$link-color : #00e; +$link-hover-color : #06e; +$link-visited-color : #551a8b; +// ETC +$hr-color : #ccc; +// Selection +$selected-font-color : #fff; +$selected-background-color : #0084AC; +// Lists +$list-margin : 1em 0; +$list-padding : 0 0 0 2em; +// Container Width +$container-width : 1000px; +// Use @if in-compatibility-mode() to add conditional CSS (useful for mixins). +$compatibility-mode : true, ie7 ie8 ie9 ff2 chrome9; + +// -- Project Variables Overrides --------- + +$site-total-width : 1000px; +$vertical-margin : 15px; +$global-border-radius : 8px; +$global-inner-border-radius : 6px; + + +// -- Colors ------------------------------ +$link-color:#388DDA; +$border-dark-color: #E3E3E8; + +// Hooks +// ---------------------------------------- +// Mixins that are called from the Library files to add some extra styling. + +@mixin sk-html() { } + +@mixin sk-body() { + background:#F0F3F3 image-url('bg-body.png') left top repeat; +} + +// h1, h2, h3, h4, h5, h6 +@mixin sk-header-tags() { + font-weight:bold; +} + + +// Global Styling +// ---------------------------------------- +// Calculate all H# Tags. +@include htags-sizes($base-font-size + 20); +@include simple-forms(auto, block-hints no-stars ); +ul { + ul { + margin:0; + } +} + +// Global Classes for extension. +// ---------------------------------------- +// Here you place classes which are used as extensions across all the project. +// Prefix them with .ext- + + +// The default fancy button, used across the SK. +// @TODO: Turn this into a mixin @include sk-button(small/medium/big); +.sk-button, .button { + @include fancy-button(#1E81D5); +} +a.button { margin:10px 0;} + +// Border Radius +// ---------------------------------------- +.ext-bradius { @include border-radius($global-border-radius); } +.ext-bradius-inner-t { @include border-radius($global-inner-border-radius $global-inner-border-radius 0 0); } +.ext-bradius-inner-b { @include border-radius(0 0 $global-inner-border-radius $global-inner-border-radius); } +.ext-bradius-inner { @include border-radius($global-inner-border-radius); } + + +// The Partials +// ---------------------------------------- +// Base styles thanks to html5boilerplate. This one uses the Hooks defined before. +@import "vendor/boilerplate-1.0/styles"; + +// -- Shared Partials + // Headers and related material go here. + @import "app/shared/headers"; + // Footers and related material go here. + @import "app/shared/footers"; + // Content and Related Material go here. + @import "app/shared/contents"; + +// -- Layouts Partials + // The most general one goes first + @import "app/layouts/app"; + @import "app/layouts/phone-book-entry"; + @import "app/layouts/conference"; + + // Compatibility.. oh jeez. + @import "app/shared/ie"; \ No newline at end of file diff --git a/app/assets/stylesheets/scaffolds.css.scss b/app/assets/stylesheets/scaffolds.css.scss new file mode 100644 index 0000000..05188f0 --- /dev/null +++ b/app/assets/stylesheets/scaffolds.css.scss @@ -0,0 +1,56 @@ +body { + background-color: #fff; + color: #333; + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; } + +p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; } + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; } + +a { + color: #000; + &:visited { + color: #666; } + &:hover { + color: #fff; + background-color: #000; } } + +div { + &.field, &.actions { + margin-bottom: 10px; } } + +#notice { + color: green; } + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; } + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px; + padding-bottom: 0; + margin-bottom: 20px; + background-color: #f0f0f0; + h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + margin-bottom: 0px; + background-color: #c00; + color: #fff; } + ul li { + font-size: 12px; + list-style: square; } } diff --git a/app/assets/stylesheets/vendor/README b/app/assets/stylesheets/vendor/README new file mode 100644 index 0000000..016b5fa --- /dev/null +++ b/app/assets/stylesheets/vendor/README @@ -0,0 +1 @@ +Here you should place the files that are not part of your project, but you use them at some point. \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/boilerplate-1.0/README b/app/assets/stylesheets/vendor/boilerplate-1.0/README new file mode 100644 index 0000000..a5aa5b1 --- /dev/null +++ b/app/assets/stylesheets/vendor/boilerplate-1.0/README @@ -0,0 +1,15 @@ +HTML5 ✰ Boilerplate (ac92ae7a) + +style.css contains a reset, font normalization and some base styles. + +Credit is left where credit is due. +Much inspiration was taken from these projects: +- yui.yahooapis.com/2.8.1/build/base/base.css +- camendesign.com/design/ +- praegnanz.de/weblog/htmlcssjs-kickstart + +Implementation to Compass as part of Survival Kit by Mario "Kuroir" Ricalde. + +Notes: + + Not implementing Non-semantic helper classes. Use Compass builts-in. \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/boilerplate-1.0/_reset.scss b/app/assets/stylesheets/vendor/boilerplate-1.0/_reset.scss new file mode 100644 index 0000000..efd1ac6 --- /dev/null +++ b/app/assets/stylesheets/vendor/boilerplate-1.0/_reset.scss @@ -0,0 +1,37 @@ +// html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline) +// v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark +// html5doctor.com/html-5-reset-stylesheet/ +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video{ + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +blockquote, q { quotes: none; } + +blockquote:before, blockquote:after, +q:before, q:after { content: ""; content: none; } + +del { text-decoration: line-through; } + +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } + +table { border-collapse: collapse; border-spacing: 0; } + +input, select { vertical-align: middle; } diff --git a/app/assets/stylesheets/vendor/boilerplate-1.0/_styles.scss b/app/assets/stylesheets/vendor/boilerplate-1.0/_styles.scss new file mode 100644 index 0000000..3852329 --- /dev/null +++ b/app/assets/stylesheets/vendor/boilerplate-1.0/_styles.scss @@ -0,0 +1,171 @@ +// HTML5 ✰ Boilerplate +// +// style.css contains a reset, font normalization and some base styles. +// +// Credit is left where credit is due. +// Much inspiration was taken from these projects: +// - yui.yahooapis.com/2.8.1/build/base/base.css +// - camendesign.com/design/ +// - praegnanz.de/weblog/htmlcssjs-kickstart +// +// Modified to fit Survival ✚ Kit + + html { + @include sk-html; + overflow-y: scroll; + } + + +// Sections (body, section, nav, article, aside, h1..6, header, footer, address) +// ---------------------------------------- + + body, select, input, textarea { color: $font-color; font-family: $base-font-family; } + + body { @include sk-body; font-size: $base-font-size; line-height: $base-line-height; } + + +// Grouping Content (p, hr, pre, blockquote, ol, ul, li, dl, dt, dt, dd, figure, figcaption, div) +// ---------------------------------------- + + p { margin: 0 0 1em 0;} + li { margin-bottom: (1em / 2);} + + hr { border: 0; border-top: 1px solid $hr-color; display: block; height: 1px; margin: 1em 0; padding: 0; } + + blockquote { color: #666; font-style: italic; margin: 1.5em; } + + // normalize monospace sizing + // meyerweb.com/eric/thoughts/2010/02/12/fixed-monospace-sizing/ + // en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome + pre { + // www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ + white-space: pre; white-space: pre-wrap; word-wrap: break-word; + padding: 15px; + } + + pre, code, kbd, samp { font-family: monospace, sans-serif; } + + // Lists + ul, ol { margin:$list-margin; padding:$list-padding;} + + ol { list-style-type: decimal; } + + // Remove margin from navigation lists. + nav ul, + nav li { list-style:none; list-style-image: none; margin: 0; } + + // Lists + dl { margin: 0 0 1.5em 0; } + + dl dt { font-weight: bold; } + + dd { margin-left: 1.5em;} + + +// Text Level Semantics (a, em, strong, small, s, cite, q, dfn, abbr, time, code, var, samp, kbd, sub, i, b, u, mark, ruby, rt, rp, bdi, bdo, span, br, wbr) +// ---------------------------------------- + + // Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test + a {text-decoration:none;} + + a:hover, a:active { outline: none; text-decoration:underline;} + + a, a:active, a:visited { color: $link-color; } + + a:hover { color: $link-hover-color; } + + // Headers (h1, h2, etc) have no default font-size or margin; define those yourself + h1, h2, h3, h4, h5, h6 { @include sk-header-tags; } + + // j.mp/webkit-tap-highlight-color + a:link { -webkit-tap-highlight-color: #FF5E99; } + + small { font-size: 85%; } + + strong, b, th, dfn { font-weight: bold; } + + em, i { font-style:italic; } + + mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } + + abbr, + acronym { border-bottom: 1px dotted #666; } + + address { font-style: italic; margin: 0 0 1.5em; } + + // Set sub, sup without affecting line-height: gist.github.com/413930 + sub, sup { font-size: 75%; line-height: 0; position: relative; } + + sup { top: -0.5em; } + + sub { bottom: -0.25em; } + + +// Embedded Content (img) +// ---------------------------------------- + + // Bicubic resizing for non-native sized IMG: + // code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ + .ie7 img { -ms-interpolation-mode: bicubic; } + + +// Tabular Data (table, caption, colgroup, col, tbody, thead, tfoot, tr, td, th) +// ---------------------------------------- + + table { margin-bottom: 1.4em; width:100%; } + + th { font-weight: bold; } + + th,td,caption { padding: 4px 10px 4px 5px; text-align: left; } + + +// Edits (ins, del) +// ---------------------------------------- + + ins { background-color: #ff9; color: #000; text-decoration: none; } + + del { color:#666; } + + +// Forms +// ---------------------------------------- + + select, input, textarea, button { font: 99% $base-font-family; outline:none;} + + td { vertical-align: top; } + + textarea { overflow: auto; } + + // Align checkboxes, radios, text inputs with their label by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css + input[type="radio"] { vertical-align: text-bottom; } + + input[type="checkbox"] { vertical-align: bottom; } + + // Hand cursor on clickable input elements + + label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } + + // Webkit browsers add a 2px margin outside the chrome of form elements + button, input, select, textarea { margin: 0; } + + + // required:valid and required:invalid moved to form.scss + + // Make buttons play nice in IE: + // www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ + button { overflow: visible; width: auto; } + + @if in-compatibility-mode() { + .ie7 input[type="checkbox"] { vertical-align: baseline; } + + .ie6 input { vertical-align: text-bottom; } + + .ie6 legend, .ie7 legend { margin-left: -7px; } + } + + +// Etc. +// ---------------------------------------- + + ::-moz-selection{ background: $selected-background-color; color:$selected-font-color; text-shadow: none; } + ::selection { background:$selected-background-color; color:$selected-font-color; text-shadow: none; } diff --git a/app/assets/stylesheets/vendor/boilerplate-2.0/README b/app/assets/stylesheets/vendor/boilerplate-2.0/README new file mode 100644 index 0000000..c9cd066 --- /dev/null +++ b/app/assets/stylesheets/vendor/boilerplate-2.0/README @@ -0,0 +1,16 @@ +HTML5 ✰ Boilerplate 2.0 (7467f9c0417a0c1f9863e2d000aad73f34836ef2) + +style.css contains a reset, font normalization and some base styles. + +Credit is left where credit is due. +Much inspiration was taken from these projects: +- yui.yahooapis.com/2.8.1/build/base/base.css +- camendesign.com/design/ +- praegnanz.de/weblog/htmlcssjs-kickstart + +Implementation to Compass as part of Survival Kit by Mario "Kuroir" Ricalde. + +Notes: + + - Not implementing Non-semantic helper classes. Use Compass builts-in. + - 1.0 and 2.0 are very similar. With 2.0 you save a couple of bytes.. maybe not worth the change? \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/boilerplate-2.0/_styles.scss b/app/assets/stylesheets/vendor/boilerplate-2.0/_styles.scss new file mode 100644 index 0000000..6268a35 --- /dev/null +++ b/app/assets/stylesheets/vendor/boilerplate-2.0/_styles.scss @@ -0,0 +1,209 @@ +// +// HTML5 ✰ Boilerplate +// +// What follows is the result of much research on cross-browser styling. +// Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, +// Kroc Camen, and the H5BP dev community and team. +// +// Detailed information about this CSS: h5bp.com/css +// +// ==|== normalize ========================================================== +// + + +// ========================================================================== +// HTML5 display definitions +// ========================================================================== + +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } +audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } +audio:not([controls]) { display: none; } +[hidden] { display: none; } + +// ========================================================================== +// Base +// ========================================================================== + +// +// 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units +// 2. Force vertical scrollbar in non-IE +// 3. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g +// + +html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + +body { margin: 0; font-size: $base-font-size; line-height: $base-line-height; } + +body, button, input, select, textarea { font-family: $base-font-family; color: $font-color; } + +// +// Remove text-shadow in selection highlight: h5bp.com/i +// These selection declarations have to be separate +// Also: hot pink! (or customize the background color to match your design) +// + +::-moz-selection { background: $selected-background-color; color: $selected-font-color; text-shadow: none; } +::selection { background: $selected-background-color; color: $selected-font-color; text-shadow: none; } + + +// ========================================================================== +// Links +// ========================================================================== + +a { color: $link-color; } +a:visited { color: $link-visited-color; } +a:hover { color: $link-hover-color; } +a:focus { outline: thin dotted; } + +/* Improve readability when focused and hovered in all browsers: h5bp.com/h */ +a:hover, a:active { outline: 0; } + + +// ========================================================================== +// Typography +// ========================================================================== + +abbr[title] { border-bottom: 1px dotted; } + +b, strong { font-weight: bold; } + +i, em { font-style:italic;} + +blockquote { margin: 1em 40px; } + +dfn { font-style: italic; } + +hr { display: block; height: 1px; border: 0; border-top: 1px solid $hr-color; margin: 1em 0; padding: 0; } + +ins { background: #ff9; color: #000; text-decoration: none; } + +mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } + +// Redeclare monospace font family: h5bp.com/j +pre, code, kbd, samp { font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 1em; } + +// Improve readability of pre-formatted text in all browsers +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } + +q { quotes: none; } +q:before, q:after { content: ""; content: none; } + +small { font-size: 85%; } + +// Position subscript and superscript content without affecting line-height: h5bp.com/k +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + + +// ========================================================================== +// Lists +// ========================================================================== +dl {margin:$list-margin;} +ul, ol { margin: $list-margin; padding: $list-padding; } +dd { margin: 0 0 0 40px; } +nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; } + + +// ========================================================================== +// Embedded content +// ========================================================================== + +// +// 1. Improve image quality when scaled in IE7: h5bp.com/d +// 2. Remove the gap between images and borders on image containers: h5bp.com/e +// + +img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } + +// +// Correct overflow not hidden in IE9 +// + +svg:not(:root) { overflow: hidden; } + + +// ========================================================================== +// Figures +// ========================================================================== + +figure { margin: 0; } + + +// ========================================================================== +// Forms +// ========================================================================== + +form { margin: 0; } +fieldset { border: 0; margin: 0; padding: 0; } + +// Indicate that 'label' will shift focus to the associated form element +label { cursor: pointer; } + +// +// 1. Correct color not inheriting in IE6/7/8/9 +// 2. Correct alignment displayed oddly in IE6/7 +// + +legend { border: 0; *margin-left: -7px; padding: 0; } + +// +// 1. Correct font-size not inheriting in all browsers +// 2. Remove margins in FF3/4 S5 Chrome +// 3. Define consistent vertical alignment display in all browsers +// + +button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } + +// +// 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet) +// 2. Correct inner spacing displayed oddly in IE6/7 +// + +button, input { line-height: normal; *overflow: visible; } + +// +// Reintroduce inner spacing in 'table' to avoid overlap and whitespace issues in IE6/7 +// + +table button, table input { *overflow: auto; } + +// +// 1. Display hand cursor for clickable form elements +// 2. Allow styling of clickable form elements in iOS +// + +button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; } + +// +// Consistent box sizing and appearance +// + +input[type="checkbox"], input[type="radio"] { box-sizing: border-box; } +input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } +input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } + +// +// Remove inner padding and border in FF3/4: h5bp.com/l +// + +button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } + +// +// 1. Remove default vertical scrollbar in IE6/7/8/9 +// 2. Allow only vertical resizing +// + +textarea { overflow: auto; vertical-align: top; resize: vertical; } + +// Colors for form validity +input:valid, textarea:valid { } +input:invalid, textarea:invalid { background-color: #f0dddd; } + + +// ========================================================================== +// Tables +// ========================================================================== + +table { border-collapse: collapse; border-spacing: 0; } +td { vertical-align: top; } \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/easy-slider/_numeric.scss b/app/assets/stylesheets/vendor/easy-slider/_numeric.scss new file mode 100644 index 0000000..db61e78 --- /dev/null +++ b/app/assets/stylesheets/vendor/easy-slider/_numeric.scss @@ -0,0 +1,44 @@ +// +// @TODO: Add docs to easy-slider-numeric! +// +@mixin easy-slider-numeric($width, $height, $selector:'#slider') { + #{$selector} { + & ul, & li { + margin:0; + padding:0; + list-style:none; + } + & li { + width:$width; + height:$height; + overflow:hidden; + } + } + @include _numeric-controls(); +} + +// You can override this function to alter the appearance of the numeric controls. +@mixin _numeric-controls() { + #controls{ + margin:10px 0; + line-height:28px; + list-style:none; + text-align:right; + li { + @include inline-block; + margin:0 0 0 10px; + } + .current a { + background:#FFFFFF; + color:#C80111; + @include box-shadow(0px 0px 3px #B2B2B2); + padding:6px 11px; // Simulate "hover" + } + a { + padding:5px 10px; + background:#F5F5F5; + border: 1px solid #AEAEAE; + color: #7F7F7F; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/facebox/_facebox.scss b/app/assets/stylesheets/vendor/facebox/_facebox.scss new file mode 100644 index 0000000..53e612d --- /dev/null +++ b/app/assets/stylesheets/vendor/facebox/_facebox.scss @@ -0,0 +1,85 @@ +$facebox-overlay: #000 !default; +#facebox { + left: 0; + position: fixed; + text-align: left; + top: 0; + z-index: 100; +} + + +#facebox .popup{ + border:9px solid rgba(0, 157, 214, 0.8); + border-radius:5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; + box-shadow:0 0 18px rgba(0,0,0,0.4); + -moz-box-shadow:0 0 18px rgba(0,0,0,0.4); + -webkit-box-shadow:0 0 18px rgba(0,0,0,0.4); + position:relative; +} + +#facebox .content { + background: #fff; + border-radius:4px; + -moz-border-radius:4px; + -webkit-border-radius:4px; + display:table; + min-width: 370px; + padding: 10px; +} + +#facebox .content > p:first-child{ + margin-top:0; +} +#facebox .content > p:last-child{ + margin-bottom:0; +} + +#facebox .close{ + padding:2px; + position:absolute; + right:5px; + top:5px; + z-index:101; +} +#facebox .close img{ + opacity:0.3; +} +#facebox .close:hover img{ + opacity:1.0; +} + +#facebox .loading { + text-align: center; +} + +#facebox .image { + text-align: center; +} + +#facebox img { + border: 0; + margin: 0; +} + +#facebox_overlay { + height:100%; + left: 0px; + position: fixed; + top: 0px; + width:100%; +} + +.facebox_hide { + z-index:-100; +} + +.facebox_overlayBG { + background-color: $facebox-overlay; + z-index: 99; +} + +#facebox h1{ + margin: 0 0 10px 0; +} diff --git a/app/assets/stylesheets/vendor/fancy-box/README b/app/assets/stylesheets/vendor/fancy-box/README new file mode 100644 index 0000000..70212dd --- /dev/null +++ b/app/assets/stylesheets/vendor/fancy-box/README @@ -0,0 +1,4 @@ +Fancybox 1.3.4 (2010/11/11) +Licensed under both MIT and GPL licenses + +http://fancybox.net/ \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/fancy-box/_fancy-box.scss b/app/assets/stylesheets/vendor/fancy-box/_fancy-box.scss new file mode 100755 index 0000000..7ec2644 --- /dev/null +++ b/app/assets/stylesheets/vendor/fancy-box/_fancy-box.scss @@ -0,0 +1,336 @@ +// +// FancyBox - jQuery Plugin +// Simple and fancy lightbox alternative +// +// Examples and documentation at: http://fancybox.net +// +// Copyright (c) 2008 - 2010 Janis Skarnelis +// That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. +// +// Version: 1.3.4 (11/11/2010) +// Requires: jQuery v1.3+ +// +// Dual licensed under the MIT and GPL licenses: +// http://www.opensource.org/licenses/mit-license.php +// http://www.gnu.org/licenses/gpl.html +// + +#fancybox-loading { + position: fixed; + top: 50%; + left: 50%; + width: 40px; + height: 40px; + margin-top: -20px; + margin-left: -20px; + cursor: pointer; + overflow: hidden; + z-index: 1104; + display: none; +} + +#fancybox-loading div { + position: absolute; + top: 0; + left: 0; + width: 40px; + height: 480px; + background-image: image-url('vendor/fancy-box/fancybox.png'); +} + +#fancybox-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 1100; + display: none; +} + +#fancybox-tmp { + padding: 0; + margin: 0; + border: 0; + overflow: auto; + display: none; +} + +#fancybox-wrap { + position: absolute; + top: 0; + left: 0; + padding: 20px; + z-index: 1101; + outline: none; + display: none; +} + +#fancybox-outer { + position: relative; + width: 100%; + height: 100%; + background: #fff; +} + +#fancybox-content { + width: 0; + height: 0; + padding: 0; + outline: none; + position: relative; + overflow: hidden; + z-index: 1102; + border: 0px solid #fff; +} + +#fancybox-hide-sel-frame { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: transparent; + z-index: 1101; +} + +#fancybox-close { + position: absolute; + top: -15px; + right: -15px; + width: 30px; + height: 30px; + background: transparent image-url('vendor/fancy-box/fancybox.png') -40px 0px; + cursor: pointer; + z-index: 1103; + display: none; +} + +#fancybox-error { + color: #444; + font: normal 12px/20px Arial; + padding: 14px; + margin: 0; +} + +#fancybox-img { + width: 100%; + height: 100%; + padding: 0; + margin: 0; + border: none; + outline: none; + line-height: 0; + vertical-align: top; +} + +#fancybox-frame { + width: 100%; + height: 100%; + border: none; + display: block; +} + +#fancybox-left, #fancybox-right { + position: absolute; + bottom: 0px; + height: 100%; + width: 35%; + cursor: pointer; + outline: none; + background: transparent image-url('vendor/fancy-box/blank.gif'); + z-index: 1102; + display: none; +} + +#fancybox-left { + left: 0px; +} + +#fancybox-right { + right: 0px; +} + +#fancybox-left-ico, #fancybox-right-ico { + position: absolute; + top: 50%; + left: -9999px; + width: 30px; + height: 30px; + margin-top: -15px; + cursor: pointer; + z-index: 1102; + display: block; +} + +#fancybox-left-ico { + background-image: image-url('vendor/fancy-box/fancybox.png'); + background-position: -40px -30px; +} + +#fancybox-right-ico { + background-image: image-url('vendor/fancy-box/fancybox.png'); + background-position: -40px -60px; +} + +#fancybox-left:hover, #fancybox-right:hover { + visibility: visible; /* IE6 */ +} + +#fancybox-left:hover span { + left: 20px; +} + +#fancybox-right:hover span { + left: auto; + right: 20px; +} + +.fancybox-bg { + position: absolute; + padding: 0; + margin: 0; + border: 0; + width: 20px; + height: 20px; + z-index: 1001; +} + +#fancybox-bg-n { + top: -20px; + left: 0; + width: 100%; + background-image: image-url('vendor/fancy-box/fancybox-x.png'); +} + +#fancybox-bg-ne { + top: -20px; + right: -20px; + background-image: image-url('vendor/fancy-box/fancybox.png'); + background-position: -40px -162px; +} + +#fancybox-bg-e { + top: 0; + right: -20px; + height: 100%; + background-image: image-url('vendor/fancy-box/fancybox-y.png'); + background-position: -20px 0px; +} + +#fancybox-bg-se { + bottom: -20px; + right: -20px; + background-image: image-url('vendor/fancy-box/fancybox.png'); + background-position: -40px -182px; +} + +#fancybox-bg-s { + bottom: -20px; + left: 0; + width: 100%; + background-image: image-url('vendor/fancy-box/fancybox-x.png'); + background-position: 0px -20px; +} + +#fancybox-bg-sw { + bottom: -20px; + left: -20px; + background-image: image-url('vendor/fancy-box/fancybox.png'); + background-position: -40px -142px; +} + +#fancybox-bg-w { + top: 0; + left: -20px; + height: 100%; + background-image: image-url('vendor/fancy-box/fancybox-y.png'); +} + +#fancybox-bg-nw { + top: -20px; + left: -20px; + background-image: image-url('vendor/fancy-box/fancybox.png'); + background-position: -40px -122px; +} + +#fancybox-title { + font-family: Helvetica; + font-size: 12px; + z-index: 1102; +} + +.fancybox-title-inside { + padding-bottom: 10px; + text-align: center; + color: #333; + background: #fff; + position: relative; +} + +.fancybox-title-outside { + padding-top: 10px; + color: #fff; +} + +.fancybox-title-over { + position: absolute; + bottom: 0; + left: 0; + color: #FFF; + text-align: left; +} + +#fancybox-title-over { + padding: 10px; + background-image: image-url('vendor/fancy-box/fancy_title_over.png'); + display: block; +} + +.fancybox-title-float { + position: absolute; + left: 0; + bottom: -20px; + height: 32px; +} + +#fancybox-title-float-wrap { + border: none; + border-collapse: collapse; + width: auto; +} + +#fancybox-title-float-wrap td { + border: none; + white-space: nowrap; +} + +#fancybox-title-float-left { + padding: 0 0 0 15px; + background: image-url('vendor/fancy-box/fancybox.png') -40px -90px no-repeat; +} + +#fancybox-title-float-main { + color: #FFF; + line-height: 29px; + font-weight: bold; + padding: 0 0 3px 0; + background: image-url('vendor/fancy-box/fancybox-x.png') 0px -40px; +} + +#fancybox-title-float-right { + padding: 0 0 0 15px; + background: image-url('vendor/fancy-box/fancybox.png') -55px -90px no-repeat; +} + +/* IE6, IE7, IE8 */ + +.fancybox-ie .fancybox-bg { background: transparent !important; } + +.fancybox-ie #fancybox-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_n.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_ne.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_e.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_se.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_s.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_sw.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_w.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_nw.png', sizingMethod='scale'); } \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/fancy-buttons/README b/app/assets/stylesheets/vendor/fancy-buttons/README new file mode 100644 index 0000000..9ee6cc3 --- /dev/null +++ b/app/assets/stylesheets/vendor/fancy-buttons/README @@ -0,0 +1,3 @@ + Fancy Buttons by imathis + https://github.com/imathis/fancy-buttons + License: MIT \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/fancy-buttons/_fancy-buttons.scss b/app/assets/stylesheets/vendor/fancy-buttons/_fancy-buttons.scss new file mode 100644 index 0000000..2e85caf --- /dev/null +++ b/app/assets/stylesheets/vendor/fancy-buttons/_fancy-buttons.scss @@ -0,0 +1,195 @@ +@import "compass/css3/gradient"; +@import "compass/css3/border-radius"; +@import "compass/css3/opacity"; +@import "compass/css3/text-shadow"; +@import "compass/css3/box-shadow"; +@import "compass/css3/background-clip"; +@import "fancy-gradient"; + +$fb-gradient-style: glossy !default; +$fb-invert-on-click: 1 !default; +$fb-font-size: 18px !default; +$fb-color: #444444 !default; +$fb-font-weight: bold !default; +$fb-border-width: 1px !default; +$fb-radius: 6px !default; +$fb-light-text: white !default; +$fb-dark-text: #222222 !default; +$fb-gradient: 1 !default; +$fb-image-path: image-url("vendor/fancy-buttons/button_bg.png") !default; +$fb-allow-disabled: false !default; +$fb-line-height: 1.2em !default; + +// Make a fancy button. +@mixin fancy-button($color: $fb-color, $font-size: $fb-font-size, $radius: $fb-radius, $border-width: $fb-border-width) { + @include fancy-button-structure($font-size, $radius, $border-width); + @include fancy-button-colors($color); +} + +// Style the button's colors, picking the most appropriate color set for the base color. +@mixin fancy-button-colors($color: $fb-color, $hover: 0, $active: 0, $fb-allow-disabled: $fb-allow-disabled) { + @include fb-color($color, "default"); + &:hover, &:focus { + @if $hover == 0 { + @include fb-color(darken($color, 3), "hover", $color); } + @else { + @include fb-color($hover, "hover"); } } + &:active { + @if $active == 0 { + @include fb-color(darken($color, 6), "active", $color); + @include box-shadow(darken($color, 15) 0 0.08em 0.2em 1px inset); } + @else { + @include fb-color($active, "active"); + @include box-shadow(darken($active, 9) 0 0.08em 0.1em 1px inset); } } + @include box-shadow(rgba(white, lightness($color) / 100) 0 0 0.1em 1px inset); + @include background-clip(padding-box); + @if $fb-allow-disabled { + &.disabled, &[disabled] { + @include disable-fancy-button($color); + } + } +} + +@mixin fancy-button-allow-disable($color: $fb-color, $font-size: $fb-font-size, $radius: $fb-radius, $border-width: $fb-border-width) { + $fb-disable-allowed: $fb-allow-disabled; + $fb-allow-disabled: true; + @include fancy-button-structure($font-size, $radius, $border-width); + @include fancy-button-colors-matte($color); + $fb-allow-disabled: $fb-disable-allowed; +} + +@mixin fancy-button-matte($color: $fb-color, $font-size: $fb-font-size, $radius: $fb-radius, $border-width: $fb-border-width) { + @include fancy-button-structure($font-size, $radius, $border-width); + @include fancy-button-colors-matte($color); +} + +@mixin fancy-button-custom($color: $fb-color, $font-size: $fb-font-size, $radius: $fb-radius, $border-width: $fb-border-width) { + @include fancy-button-structure($font-size, $radius, $border-width); + @include fancy-button-colors-custom($color, $font-size, $radius, $border-width); +} + +@mixin fancy-button-colors-matte($color: $fb-color, $hover: 0, $active: 0) { + $fb-current-style: $fb-gradient-style; + $fb-gradient-style: matte; + @include fancy-button-colors($color, $hover, $active); + $fb-gradient-style: $fb-current-style; +} + +@mixin fancy-button-colors-custom($color: $fb-color, $hover: 0, $active: 0) { + $fb-current-style: $fb-gradient-style; + $fb-gradient-style: custom; + @include fancy-button-colors($color, $hover, $active); + $fb-gradient-style: $fb-current-style; +} + +// Default state color settings +@mixin fb-color($color, $state, $lumins: $color) { + $gradient-top: lighten($color, 15); + $gradient-bottom: darken($color, 6); + $border-color: darken($color, 8); + @if $fb-invert-on-click != 0 { + $border-color: darken($color, 15); } + @if saturation($color) > 0 { + $color: saturate($color, 40); } + @else if lightness($lumins) >= lightness(#aaaaaa) { + $color: lighten($color, 20); } + @include fb-state-colors($color, $gradient-top, $gradient-bottom, $border-color, $state, $lumins); +} + +// Apply the button colors specified for the button state into which it is mixed. +@mixin fb-state-colors($color, $gradient-top, $gradient-bottom, $border, $state, $lumins: $color) { + background-color: $color; + @if $fb-gradient != 0 { + @if $fb-gradient-style == "glossy" { + @if $state == "active" { + @include fancy-gradient-active($gradient-top, $gradient-bottom); } + @else { + @include fancy-gradient($gradient-top, $gradient-bottom); } } + @else if $fb-gradient-style == "matte" { + @if $state == "active" { + @include fancy-matte-gradient-active($gradient-top, $gradient-bottom); } + @else { + @include fancy-matte-gradient($gradient-top, $gradient-bottom); } } + @else if $fb-gradient-style == "custom" { + @if $state == "active" { + @include custom-fancy-gradient-active($gradient-top, $gradient-bottom); } + @else { + @include custom-fancy-gradient($gradient-top, $gradient-bottom); } } } + border: { + color: $border; }; + $text-shadow-settings: unquote("0px 1px 1px"); + @if $fb-invert-on-click != 0 and $state == "active" { + $text-shadow-settings: unquote("0px -1px -1px"); } + @if lightness($lumins) < lightness(#aaaaaa) { + text-shadow: darken($color, 25) $text-shadow-settings; + &, &:visited { + color: $fb-light-text; } } + @else { + text-shadow: lighten($color, 15) $text-shadow-settings; + &, &:visited { + color: $fb-dark-text; } } +} + +@mixin fancy-button-text-colors($color, $hover: $color, $active: $color, $fb-allow-disabled: $fb-allow-disabled) { + &, &:visited { + color: $color; } + &:hover, &:focus { + color: $hover; } + &:active { + color: $active; } + @if $fb-allow-disabled { + &.disabled, &[disabled] { + color: $color; } } +} + +// Layout the button's box +@mixin fancy-button-structure($font-size: $fb-font-size, $radius: $fb-radius, $border-width: $fb-border-width, $line-height: $fb-line-height) { + @extend .fancy-button-reset-base-class; + @include fancy-button-size($font-size, $radius, $border-width, $line-height); +} + +@mixin fancy-button-size($font-size: $fb-font-size, $radius: $fb-radius, $border-width: $fb-border-width, $line-height: $fb-line-height) { + // better padding for smaller buttons + $v-padding: 0.3em; + $h-padding: 1em; + @if $radius > 0 { + @include border-radius($radius); } + font-size: $font-size; + line-height: $line-height; + @include fancy-button-padding($v-padding, $h-padding, $border-width); +} + +@mixin fancy-button-padding($v-padding, $h-padding, $border-width: $fb-border-width) { + padding: $v-padding $h-padding; + border-width: $border-width; +} + +// Reset the button's important properties to make sure they behave correctly +@mixin fb-reset($font-weight: $fb-font-weight) { + font-family: "Lucida Grande", Lucida, Arial, sans-serif; + background: #{$fb-image-path} repeat-x bottom left; + margin: 0; + width: auto; + overflow: visible; + display: inline-block; + cursor: pointer; + text-decoration: none; + border-style: solid; + font-weight: $font-weight; + &::-moz-focus-inner { + border: none; + padding: 0; } + &:focus { + outline: none; } +} + +@mixin disable-fancy-button($color: $fb-color, $opacity: 0.7) { + @include fb-color($color, "default"); + @include opacity($opacity); + @include box-shadow(none); + cursor: default !important; +} + +.fancy-button-reset-base-class { + @include fb-reset; +} diff --git a/app/assets/stylesheets/vendor/fancy-buttons/_fancy-gradient.scss b/app/assets/stylesheets/vendor/fancy-buttons/_fancy-gradient.scss new file mode 100644 index 0000000..da0baa9 --- /dev/null +++ b/app/assets/stylesheets/vendor/fancy-buttons/_fancy-gradient.scss @@ -0,0 +1,28 @@ +@mixin fancy-gradient($color1, $color2) { + $top_shine: lighten($color1, 18); + $bottom_glow: lighten($color2, 10); + $top_middle: $color1; + $middle: lighten($color2, 3); + $bottom_middle: $color2; + @include background-image(linear-gradient($top_shine, $top_middle 10%, $middle 50%, $bottom_middle 50%, $bottom_glow)); } + +@mixin fancy-gradient-active($color1, $color2) { + $top: lighten($color2, 6); + $bottom: lighten($color2, 14); + $top_middle: lighten($color2, 8); + $middle: lighten($color2, 4); + $bottom_middle: lighten($color2, 1); + @include background-image(linear-gradient($top, $top_middle 30%, $middle 50%, $bottom_middle 50%, $bottom)); } + +@mixin fancy-matte-gradient($color1, $color2) { + @include background-image(linear-gradient($color1, $color2)); } + +@mixin fancy-matte-gradient-active($color1, $color2) { + $top: lighten($color2, 5); + $bottom: lighten($color2, 15); + $middle: lighten($color2, 8); + @include background-image(linear-gradient($top, $middle 40%, $middle 85%, $bottom)); } + +/* incase an inverted custom gradient isn't specified */ +@mixin custom-fancy-gradient-active($color1, $color2) { + @include custom-fancy-gradient($color1, $color2); } diff --git a/app/assets/stylesheets/vendor/survival-kit/_blog.scss b/app/assets/stylesheets/vendor/survival-kit/_blog.scss new file mode 100644 index 0000000..5bec255 --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_blog.scss @@ -0,0 +1,99 @@ +// Survival ✚ Kit + +// News Item +// ---------------------------------------- +//
    +//
    14 de Julio 2010
    +//

    Lorem My Ipsum

    +// Blog Thumb +//

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nec ipsum magna. Duis porttitor, felis quis eleifend vehicula, mauris mi varius nibh, sit amet iaculis magna magna vitae justo...

    +//

    Nota Completa

    +//
    +@mixin news-item($date:#383838, $header:#1491EE) { + @include clearfix; + .date { + color:$date; + font:{ + size:11px; + weight:normal; + }; + } + img { + float:left; + padding: 4px 14px 50px 0; + } + p { + color:$link-color; + } + h3 { + margin-bottom:8px; + a{ + color:$header; + font-size:15px; + font-weight:bold; + text-decoration:none; + } + } +} + + +// Pagination Styling +// ---------------------------------------- +// + +// Notes for Later +//$active-state: (border (1px solid red), height 300px, ..[infinite]); +// Would Output: +// border: 1px solid red; heigh: 300px; + +// Normal, hover, active, disabled +//$pagination-font-weights: normal bold normal; +//$pagination-font-colors: #7F7F7F yellow #FFFFFF #4C7DB5; +//$pagination-borders:none (1px solid #4C7DB5) (none) (1px solid #D0D0D0); +//$pagination-backgrounds: #F5F5F5 #FFFFFF none none; +// color, background, border, weight +//@include pagination(#7F7F7F yellow #FFFFFF #4C7DB5, #F5F5F5 #FFFFFF none none, none (1px solid #4C7DB5) (none) (1px solid #D0D0D0), ); + +@mixin pagination() { + text-align:center; + * { + @include border-radius(4px); + } + .current { + font-weight:bold; + color:#0090BC; + font-size:14px; + padding: 3px 8px; + margin-right:2px; + } + .disabled { + color:#518CBC; + border:1px solid #518CBC; + } + a { + padding: 3px 8px; + @include gradient(#80DFFF, #3BBBE7); + @include box-shadow(0 2px 0px #EBEBEB); + text-decoration:none; + color: #FFF; + font-weight: bold; + border:1px solid #4DC6EF; + &:active { + @include box-shadow(0px 1px #96C5FA, inset 0px 1px #5D96CC); + background:#F6FAFC !important; + } + &:hover { + background:#D0F0FC; + border:1px solid #4DC6EF; + color:$link-hover-color; + @include box-shadow(none); + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/survival-kit/_effects.scss b/app/assets/stylesheets/vendor/survival-kit/_effects.scss new file mode 100644 index 0000000..488a83a --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_effects.scss @@ -0,0 +1,97 @@ +// Survival ✚ Kit + +// Add a Bendy shadow to a squar element. +// @author Chris Eppstein +@mixin bendy-shadow($width, $angle: 5deg, $color: rgba(#333, 0.5)) { + @include box-shadow(0 10px 5px -5px $color); + position: relative; + z-index: 1; + &:before, &:after { + @include box-shadow(0 10px 10px 1px $color); + bottom: 2px; + content: ""; + height: 10px; + position: absolute; + width: $width / 2; + z-index: -1; + } + &:before { + @include rotate(-$angle); + left: 10px; + } + &:after { + @include rotate($angle); + right: 10px; + } +} + +// Sexy button ! +@mixin shiny-button($light-color: #92CE2F, $dark-mix-color: #32D17C, $mix-percent: 40%) { + // Params + $bg-light: $light-color; + $bg-dark: darken(mix($dark-mix-color, $bg-light, $mix-percent), 13%); + + $border-inset-color: $bg-light; + $border-inside-light: lighten($border-inset-color, 13%); + $border-inside-dark: $border-inset-color; + + $border-outside: darken($bg-dark, 9%); + $box-shadow: rgba(35, 35, 35, 0.2); + $text-shadow: darken($bg-dark, 7%); + + @extend .bradius-inner; + @include box-shadow(inset 1px 1px 0px $border-inside-light, inset -1px -1px 0px $border-inside-dark); + @include gradient($bg-light, $bg-dark); + @include text-shadow(2px 2px 1px $text-shadow); + border:1px solid $border-outside; + color:#FFF !important; + + font-size:size(13px); + font-weight: bold; + padding: 9px 60px; + text-decoration: none; + text-decoration: none !important; + &:hover { + @include box-shadow(inset 0px 0px 1px $border-inside-light, 0px 2px 1px $box-shadow); + @include gradient(lighten($bg-light, 6%), lighten($bg-dark, 6%)); + } + &:active { + @include box-shadow(inset 0px 2px 3px $bg-dark); + background: mix($bg-light, $bg-dark, 50%); + } + &.small { + font-size: 12px; + padding: 7px 22px; + } +} + +// +// @TODO: Add docs to shiny-button-colors! +// +@mixin shiny-button-colors($light-color: #92CE2F, $dark-mix-color: #32D17C, $mix-percent: 40%) { + // Params + $bg-light: $light-color; + $bg-dark: darken(mix($dark-mix-color, $bg-light, $mix-percent), 13%); + + $border-inset-color: $bg-light; + $border-inside-light: lighten($border-inset-color, 13%); + $border-inside-dark: $border-inset-color; + + $border-outside: darken($bg-dark, 9%); + $box-shadow: rgba(35, 35, 35, 0.2); + $text-shadow: darken($bg-dark, 7%); + + + @include box-shadow(inset 1px 1px 0px $border-inside-light, inset -1px -1px 0px $border-inside-dark); + @include gradient($bg-light, $bg-dark); + @include text-shadow(2px 2px 1px $text-shadow); + border:1px solid $border-outside; + &:hover { + @include box-shadow(inset 0px 0px 1px $border-inside-light, 0px 2px 1px $box-shadow); + @include gradient(lighten($bg-light, 6%), lighten($bg-dark, 6%)); + } + &:active { + @include box-shadow(inset 0px 2px 3px $bg-dark); + background: mix($bg-light, $bg-dark, 50%); + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/survival-kit/_forms.scss b/app/assets/stylesheets/vendor/survival-kit/_forms.scss new file mode 100644 index 0000000..7e82b87 --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_forms.scss @@ -0,0 +1,313 @@ +// Survival ✚ Kit + +// A simple search box, generic. +// If $width contains a second argument, it won't output the width to the parent element, allowing you to use box-size. +// +// +@mixin search-box-simple { + // Preferences + $width: 210px; + $height: 27px; + $font-size: 12px; + + background: #FFF; + overflow: hidden; + height: $height; + width: $width; + + // Style + border:1px solid #4BC5ED; + margin-top: -3px; + @extend .bradius-inner; + @include gradient(#FFF, #F5F5F5); + + &.active { + @include box-shadow(0px 1px 2px transparentize(#000, 0.8)); + background:#FFF; + } + + // Calculations + $button-width: 27px; + $input-width: $width - $button-width - 2px; + + input, button { + background:transparent; + border: 0; + font-size: $font-size; + outline: none; + } + .text { + @include size($input-width, $height, 7px 10px); + color: #777; + float: left; + line-height: $height - (7px * 2); + } + button, .search { + cursor: pointer; + display: block; + float:right; + height: $height; + padding:0; + width: $button-width; + } + .search { + background:transparent image-url('redesign/vendor/survival-kit/search-13x16.png') center center no-repeat; + } +} + +// Search Box Simple dimention override +@mixin search-box-simple-size($width, $height, $button-width: 27px) { + $input-width: $width - $button-width - 2px; + height: $height; + width: $width; + .text { + @include size($input-width, $height, 7px 10px); + line-height: $height - (7px * 2); + } + button, .search { + height: $height; + width: $button-width; + } +} + + +// Inputs. +$input-shadow : inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.2) !default; +$input-hover-color : #7DBEF1 !default; +$input-hover-shadow : 0 0 6px #7DBEF1 !default; + +// +// Adds the Input state effects +// +@mixin input-effects() { + @if $input-shadow { @include box-shadow($input-shadow); } + border:1px solid #CCCCCC; + outline: 0; + &:focus { + @if $input-hover-shadow { + @include box-shadow($input-hover-shadow); + } + border:1px solid $input-hover-color; + } +} + +// +// Forms Styles (Survival Kit) +// This styles are meant to be used with Simple_Forms (Rails) +// Usage: +// simple-forms(default, option-1 option2) +// Options: +// block-hints : display the hints right after the inut field. +@mixin simple-forms($selector : "simple_form", $opts:false) { + // Setup + $size-modifier : 0px; + $input-width : 300px; + $input-font-size : $base-font-size + $size-modifier; + $vertical-spacing : 7px; + $horizontal-spacing : 10px; + $label-width :148px; + + // 7px = base padding at 0 size modifier. + $vertical-field-padding : floor((7px + $size-modifier) + ($size-modifier / 4.4) * 2); + $horizontal-field-padding: 6px; + + // Colors. + $hint-color : #6E6E6E; + + @if $selector == auto or $selector == default { + $selector: 'simple_form'; + } + + .hidden { display: none; } + // Force $opts into a list goddamnit. + $opts: join($opts, herp derp); + + .#{$selector} { + @include debug; + .hint { + @include debug(green); + display:inline-block; + padding:$vertical-field-padding 0 $vertical-field-padding ($label-width + $horizontal-spacing); + } + + // Fix a issue with the spacing. + input.date { + label { + width: 145px !important; + } + } + + + label { + @include debug(green); + vertical-align:middle; + width:$label-width; // double line labels. + display:inline-block; // works with already inline displayed items. + margin:0 $horizontal-spacing 0 0; + padding: $vertical-field-padding 0; + line-height:$input-font-size + ($input-font-size * 0.26); + text-align: right; + abbr { + @include debug(yellow); + @if index($opts, no-stars) { + display:none; + } @else { + color:#E62500; + float: right; + margin-left: $horizontal-spacing; + } + } + &.boolean, &.collection_radio_buttons { padding:$horizontal-spacing/2; width: auto;} + } + + .ext-sfr { + @include debug(yellow); + display: inline-block; + vertical-align: middle; + width: $label-width; + } + + .input { + @include debug(blue); + padding:$vertical-spacing 0; + .hint { + @extend .ext-sfr; + color: #8A8A8A; + display: block; + font-size: size(11px); + padding: 2px 0 0 ($label-width + $horizontal-spacing); + width: $input-width + ($horizontal-field-padding * 2) + $horizontal-spacing; + } + &.boolean { + padding: 2px 0 0 ($label-width + $horizontal-spacing); + } + } + + select { + border:1px solid #CCCCCC; + outline:none; + // floor(Font Size * Line Height) + (Vertical Input Padding * 2) + 1px) + $calc: floor(($input-font-size * $base-line-height ) + ($vertical-field-padding * 2)) + (1px); + height: $calc + 1px; + padding:(6px + $size-modifier) * $base-line-height ; + &:focus { + border:1px solid $input-hover-color; + } + } + + // Needs to be nested so it doesn't collide with date selects. + .select select, .country select { + width:$input-width + ($horizontal-field-padding * 2); + } + textarea, input[type=text], input[type=password], input[type=email] { + font-size:$input-font-size; + padding: $vertical-field-padding $horizontal-field-padding; + vertical-align:top; + width:$input-width; + // Input Effects + @include input-effects; + } + + textarea { + height:80px; + max-width:$input-width; + } + + input { + &.check_boxes, &.radio, &.boolean { + vertical-align:middle; + } + } + + .submit, .padded { + padding-left: $label-width + $horizontal-spacing; + } + + .form-actions { + background: #F7F7F7; + border-top: 1px solid #DDD; + padding: 17px 0px 18px $label-width + $horizontal-spacing; + } + + // Simple Form Button for the forms. + .button { + @extend .sk-button; + } + + + // Errors @todo: this should be in its own section. + span.error, .error { + @extend .ext-sfr; + color: #D65C5C; + font-size: 12px; + margin-left: 10px; + } + + #error_explanation { + @include box-shadow(#D4D4D4 0 0 10px); + background: #FFEBD6; + border: 1px solid #FFB36C; + color:#895334; + margin:$vertical-spacing * 4 0; + padding: 10px 14px; + h2 { + @include header-size(18px); + color:#AE4910; + margin-top:0; + } + } + + + // Colors for form validity + input:valid, textarea:valid {} + + input:invalid, textarea:invalid { + $error-color: #FF6161; + box-shadow:$input-shadow, inset -7px 0px 0px lighten($error-color, 15%) !important; + &:focus { + @if $input-hover-shadow { + @include box-shadow($input-hover-shadow, inset -7px 0px 0px $error-color !important ); + } + } + } + } + + // Rails 3 wraps errors in Divs + .field_with_errors { + display:inline; + } + + // Make this compatible when you have no javascript loaded! + @if not index($opts, no-browser-support) { + .ie7 { + select { margin-top:15px;} + } + } +} + +// Allows you to have different widths for different layouts. +@mixin simple-form-width($width:false, $label-width:false) { + $horizontal-field-padding : 6px; + $horizontal-spacing : 3px; + $input-width : $width; + + @if $width { + textarea, input[type=text], input[type=password] { + max-width: $width; + width:$width; + } + + .select select, .country select { + width:$input-width + ($horizontal-field-padding * 2); + } + } + + @if $label-width { + .input .hint { + width:$label-width; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/survival-kit/_headers.scss b/app/assets/stylesheets/vendor/survival-kit/_headers.scss new file mode 100644 index 0000000..8b99808 --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_headers.scss @@ -0,0 +1,36 @@ +// Survival ✚ Kit + +// Sets the font size specified in pixels using percents so that the base +// font size changes and 1em has the correct value. When nesting font size +// declarations, within the DOM tree, the base_font_size must be the parent's +// effective font-size in pixels. +// Usage Examples: +// .big +// +font-size(16px) +// .bigger +// +font-size(18px) +// .big .bigger +// +font-size(18px, 16px) +// +// For more information see the table found at http://developer.yahoo.com/yui/3/cssfonts/#fontsize +// From: compass-html5-boilerplate gem. + +@function size($size, $base-font-size: $base-font-size) { + @return ceil(percentage($size / $base-font-size)); +} + + +// Calculate margin and line height according to the given size. +@mixin header-size($size) { + font-size: size($size); +} + +// Calculate the Header based on the H1 Max size. +@mixin htags-sizes($max) { + $per: $max * 0.10; + @for $i from 1 through 6 { + h#{$i} { + @include header-size($max - ($per * $i) ); + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/survival-kit/_images.scss b/app/assets/stylesheets/vendor/survival-kit/_images.scss new file mode 100644 index 0000000..36e67cd --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_images.scss @@ -0,0 +1,121 @@ +// Survival ✚ Kit + +@import "compass/typography/text/replacement"; + +// Replace an A tag with an a background-image. +// @var $image string path to the image +// @var $inline boolean embed via data. +@mixin image-link($image, $inline:false) { + @include image-background($image, $inline); + cursor:pointer; + @extend .ext-hide-text; +} + +// Replace an A tag with an a background-image sprite. You need to provide it with +// the dimentions of the image and the x-pos/y-pos +// +// @var $image string path to the image, inherit is useful. +// @var $height width +// @var $height pixels +// @var $x-pos pixels +// @var $y-pos pixels +// @var $inline boolean embed via data. +@mixin image-sprite-link($image, $width, $height, $x-pos, $y-pos, $inline: false) { + @include sk-background(transparent, $image, $x-pos, $y-pos, no-repeat, $inline); + width:$width; + height:$height; + @extend .ext-hide-text; +} + +// Replace a Header>a tag with a background image. Made specifically for logos. +// @var $image string path to the image +// @var $inline boolean embed via data. +@mixin logo($image, $inline:false) { + @include no-mp; + width: image-width($image); + height: image-height($image); + a { + @include image-link($image, $inline); + &:hover { opacity: 0.7;} + } +} + +// Area for a header link, meant to be used when it inherits a background image. +// This should be invoked on the H1-6 Tag and not in the link, the needed structure is:2 +//

    +@mixin logo-area($width, $height, $debugging: false) { + @include no-mp; + width:$width; + height:$height; + a { + @include link-area($width, $height, $debugging); + } +} + +// An area which should be clickable. It's meant to be a low level mixin, you should +// use the alternatuves. +// - debugging enables a background color to know the position. +@mixin link-area($width, $height, $debugging: false) { + width:$width; + height:$height; + @if $debugging { + @include debug($debugging); + } + @extend .ext-hide-text; +} + +// Mixin for quickly replacing images for any given element. +// @var $image string path to the image +// @var $inline boolean embed via data. +@mixin image-replace($image, $inline:false) { + @include image-background($image, $inline); + @extend .ext-hide-text; +} +@mixin image-replace-url($image, $width, $height) { + background:transparent url($image) left top no-repeat; + @include link-area($width, $height); +} + +// Just adds the image as a background and sets the width/height accordingly. +// @var $image string path to the image +// @var $inline boolean embed via data. +@mixin image-background($image, $inline:false) { + @include sk-background(transparent, $image, no-repeat, top, left, $inline); + width: image-width($image); + height: image-height($image); +} + +// Add a background by passing the exact same parameters as a normal one. With +// one more parameter $inline. Which will use inline-image and add backward +// compatibility to IE7 via *background. +// +// @var $color +// @var $image string can be a path to an image or inherit (will insert tags separately) +// @var $horizontal +// @var $vertical +// @var $repeat +// @var $inline +@mixin sk-background($color, $image, $horizontal, $vertical, $repeat, $inline: false) { + @if $image == inherit { + background-color: $color; + background-repeat: $repeat; + background-position: $horizontal $vertical; + } @else { + @if $inline == true { + background : $color inline-image($image) $horizontal $vertical $repeat; + *background : $color image-url($image) $horizontal $vertical $repeat; + } @else { + background: $color image-url($image) $horizontal $vertical $repeat; + } + } +} + + +// Common styles needed by our Image Mixins. +// Depends on Compass. +.ext-hide-text { + @include hide-text; + display:block; + direction: ltr; + outline:none; +} diff --git a/app/assets/stylesheets/vendor/survival-kit/_lists.scss b/app/assets/stylesheets/vendor/survival-kit/_lists.scss new file mode 100644 index 0000000..ea9670e --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_lists.scss @@ -0,0 +1,37 @@ +// Survival ✚ Kit + +// Add docs to float-list! +@mixin float-list($side:left) { + @include no-mp; + list-style-type: none; + li { float:$side; } +} + +$tc-begin-color : #000 !default; +$tc-end-color : lighten(#646464, 30) !default; +$tc-base-font-size : 11px !default; +$tc-max-font-size : 20px !default; +$tc-how-many : 10 !default; +@mixin tag-cloud($tc-begin-color, $tc-end-color, $tc-base-font-size, $tc-max-font-size, $tc-how-many) { + $font-calculations : $tc-base-font-size; + + li { + display:inline; + background:none; + padding:0 2px; + } + + a { + // Stops words from breaking. + display:inline-block; + } + + @for $i from 1 through $tc-how-many { + // The last item gets the max-font size. + $font-calculations: round($font-calculations + (($tc-max-font-size - $tc-base-font-size) / $tc-how-many)); + a.tag-#{$i} { + font-size:$font-calculations; + color: mix($tc-end-color, $tc-begin-color, ( $i * (100 / $tc-how-many) )); + } + } +} diff --git a/app/assets/stylesheets/vendor/survival-kit/_loader.scss b/app/assets/stylesheets/vendor/survival-kit/_loader.scss new file mode 100644 index 0000000..c09a018 --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_loader.scss @@ -0,0 +1,11 @@ +// Survival ✚ Kit + +// Load all the Libraries. +@import "blog"; +@import "forms"; +@import "images"; +@import "lists"; +@import "navigation"; +@import "tools"; +@import "headers"; +@import "effects"; diff --git a/app/assets/stylesheets/vendor/survival-kit/_navigation.scss b/app/assets/stylesheets/vendor/survival-kit/_navigation.scss new file mode 100644 index 0000000..5e6f13d --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_navigation.scss @@ -0,0 +1,230 @@ +// Survival ✚ Kit + +// Horizontal Navigation Low-level Method. +// +// It's meant to be called from other predifined mixins to avoid calling so many variables per call. +// Used from ul/ol +@mixin horizontal-navigation( + $height, + $color, + $hover-color, + $active-color, + $text-shadow, + $bg, + $bg-hover, + $bg-active, + $box-shadow, + $box-shadow-hover, + $box-shadow-active, + $border-left, + $border-right, + $padding, + $margin, + $border-radius, + $font-weight, + $font-size, + $tab-space // Sets a tabbing space. + ) { + // $bg none or transparent will remove the background. + @if $tab-space == none { $tab-space:0;} + @if $bg == none { $bg:transparent;} + @if $bg-active == auto { $bg-active:$bg-hover; } + @if $active-color == auto { $active-color:$hover-color; } + + // Border Calculation + // ---------------------------------------- + // Check if borders are set to anything but none / auto. + @if $border-left != none and $border-right != none and $border-left != auto and $border-right != auto { + // Borders where explicitly set. + @include _sk-nav-borders($border-left, $border-right); + } @else if $border-left == auto and $border-right == auto and $bg != transparent{ + // Borders calculated magically. + @include _sk-nav-borders(lighten($bg, 10%), darken($bg, 10%)); + } + + height:$height; // instead of clearfix, to keep shadows alive. + margin: 0; + list-style:none; + + // Links and input + li, a { + display:block; + float:left; // this can make it inline or block level. + line-height:$height; + } + + + a { + @if $font-weight != none { + font-weight: $font-weight; + } + @if $padding != none { + padding:$padding; + } + @if $margin != none { + margin:$margin; + } + @if $font-size != none { + font-size:$font-size; + } + + text-decoration:none; + color:$color; + + @if $bg != transparent { + background:$bg; + } + + @if $box-shadow != none { + @include box-shadow($box-shadow); + } + + @if $text-shadow != none { + @include text-shadow($text-shadow, 1px, 1px, 1px); + } + + @if $border-radius != none { + @include border-radius($border-radius); + } + + // Feature for tabs. + @if $tab-space != 0 { + margin-top: -($tab-space); + } + + // States + // ---------------------------------------- + + &:hover{ + @include _sk-nav-effects($hover-color, $bg-hover, $text-shadow, $box-shadow-hover, $bg-hover); + text-decoration:none; + } + + &:visited { + color:$active-color; + } + &.active { + @include _sk-nav-effects($active-color, $bg-active, $text-shadow, $box-shadow-active, $bg-hover); + // Add tab space. + @if $tab-space != 0 { + height:$height + $tab-space; + } + } + } +} + +// Mixin used to generate Background effects by the horizontal-navigation mixin. +@mixin _sk-nav-effects($color, $bg, $text-shadow, $box-shadow, $bg-hover) { + @if $color != auto { + color:$color; + } + @if $bg != transparent { + @if $bg-hover == auto { + background:darken($bg,3%); + } @else { + background:$bg; + } + @if $box-shadow != none{ + @include box-shadow($box-shadow); + } + } + // Remove the text shadow of hover. + @if $text-shadow != none { + @include text-shadow(none); + } +} + +// Low level mixin. +// Invoked by other mixins. +// +// @var $left the left border. +// @var $right the right border +@mixin _sk-nav-borders($left, $right) { + li:first-child, li.first { + border-left:1px solid $right; + }// li:first-child + li:last-child, li.last { + border-right:1px solid $left; + } + a { + border:{ + left: 1px solid $left; + right: 1px solid $right; + }; + &.active, &.active:hover { + border:{ + left:1px solid transparent; + right:1px solid transparent; + }; + } + &:hover { + border:{ + left:1px solid transparent; + right:1px solid transparent; + }; + } + } +} + +// +// @TODO: Add docs to tabs! +// +@mixin navigation-classes($opts: tabs) { + $opts: join($opts, force list); + .nav { + list-style: none; + margin-bottom: $base-line-height; + margin-left: 0; + } + + // Make links block level + .nav > li > a { + display: block; + } + .nav > li > a:hover { + background-color: #EEEEEE; + text-decoration: none; + } + + // Common styles + .nav-tabs { + @extend .nav; + @include pie-clearfix(); + } + .nav-tabs > li, .nav-pills > li { + float: left; + } + .nav-tabs > li > a { + line-height: 14px; + margin-right: 2px; + padding-left: 12px; + padding-right: 12px; // keeps the overall height an even number + } + + .nav-tabs { + border-bottom: 1px solid #ddd; + } + + .nav-tabs > li { + margin-bottom: -1px; + } + + .nav-tabs > li > a { + @include border-radius(4px 4px 0 0); + border: 1px solid transparent; + padding-bottom: 9px; + padding-top: 9px; + &:hover { + border-color: #EEEEEE #EEEEEE #ddd; + } + } + .nav-tabs > .active { + a, a:hover { + background-color: #FFF; + border: 1px solid #ddd; + border-bottom-color: transparent; + color: gray; + cursor: default; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/survival-kit/_secure.scss b/app/assets/stylesheets/vendor/survival-kit/_secure.scss new file mode 100644 index 0000000..f08dd11 --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_secure.scss @@ -0,0 +1,3 @@ +/*! + This is a compiled file. +*/ \ No newline at end of file diff --git a/app/assets/stylesheets/vendor/survival-kit/_tools.scss b/app/assets/stylesheets/vendor/survival-kit/_tools.scss new file mode 100644 index 0000000..e753dfe --- /dev/null +++ b/app/assets/stylesheets/vendor/survival-kit/_tools.scss @@ -0,0 +1,267 @@ +// Survival ✚ Kit +$container-width : 1000px !default; +$compatibility-mode : true, ie ie7 ie8 ie9 ff2 chrome9 !default; + +// Function to know if we're in compatibility mode, if $version is set it'll return if there's a match for that browser. +@function in-compatibility-mode($version: false) { + @if $compatibility-mode { + @if $version { + @return index(nth($compatibility-mode, 2), $version); + } @else { + @return nth($compatibility-mode, 1); + } + } @else { + @return false; + } +} + +// Shortcut to remove margin an padding. +// Used on several @mixins. +@mixin no-mp($extend:false) { + @if $extend { + @extend .no-mp; + } @else { + margin:0; + padding:0; + } +} +// Sometimes it's better to extend a class. +.no-mp { + margin:0; + padding:0; +} + +// Center an element. +@mixin center-container($container-width, $vertical-margin:0, $padding:0) { + margin:$vertical-margin auto; + @if $padding == 0 { + width:$container-width; + } @else { + @include size($container-width, auto, $padding); + } +} + +// Inline Block CrossBrowser. +// Disregards FF2 and IE6 +@mixin inline-block { + display: inline-block; + @if in-compatibility-mode(ie7) { + zoom: 1; + *display:inline; + } +} + +// Shortcut to set absolute positioning. +@mixin pos($pos, $debug: false) { + @if length($pos) == 1 { + $pos: $pos 0 0 0; + } + @if length($pos) == 2 { + $pos: nth($pos,1) nth($pos,2) 0 0; + } + @if length($pos) == 3 { + $pos: nth($pos,1) nth($pos,2) nth($pos, 3) 0; + } + position:absolute; + @if "#{nth($pos, 1)}" != "0" { top: nth($pos, 1); } + @if "#{nth($pos, 2)}" != "0" { right: nth($pos, 2); } + @if "#{nth($pos, 3)}" != "0" { bottom: nth($pos, 3); } + @if "#{nth($pos, 4)}" != "0" { left: nth($pos, 4); } + @if $debug { @include debug($debug); } +} + +// Center a absolute element horizontally; optional offset. +@mixin pos-x-center($width, $offset:0) { + @include pos(0 50% 0 50%); + margin-left:$offset - ( $width / 2 ); + width:$width; +} + +// Center a absolute element vertically; optional offset. +@mixin pos-y-center($height, $offset:0) { + @include pos(50% 0 50% 0); + height:$height; + margin-top:$offset - ( $height / 2 ); +} + +// Set a debug variable. +@mixin debug($color:red) { + @if $debug != false { + @if $color == true { $color:red; } + background: rgba($color, 0.2); // incompatible with IE. + } +} + +// Class available to center container to 1000px +.w, .pagewidth { + @include center-container($container-width); +} + +// Crossbrowser linear gradient. +// Compatible Browsers: FF3.6+ Saf4+ Chrome IE6-IE9 +// @author SitePoint +@mixin background-gradient($from, $to, $start: top, $end: bottom, $fallback:$from, $ie:false) { + @include gradient($from, $to, $start, $end, $fallback); +} +@mixin gradient($from, $to, $start: top, $end: bottom, $fallback:$from){ + background-color: $fallback; + + @if $end == bottom and $start == top { + @if $start == 0 { + background-image: -webkit-gradient(linear, left $start, left bottom, from($from), to($to)); + } @else { + background-image: -webkit-gradient(linear, $start, left bottom, from($from), to($to)); + } + background-image: -webkit-linear-gradient($start, $from, $to); + background-image: -moz-linear-gradient($start, $from, $to); + background-image: -ms-linear-gradient($start, $from, $to); + background-image: -o-linear-gradient($start, $from, $to); + @if in-compatibility-mode() { + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#{ie-hex-str($from)}', EndColorStr='#{ie-hex-str($to)}'); + } + } @else if $end == bottom { + background-image: -webkit-gradient(linear, left $start, 0 $end, from($from), to($to)); + background-image: -webkit-linear-gradient(top, $from $start+px, $to); + background-image: -moz-linear-gradient(top, $from $start+px, $to); + background-image: -ms-linear-gradient(top, $from $start+px, $to); + background-image: -o-linear-gradient(top, $from $start+px, $to); + + } @else { + background-image: -webkit-gradient(linear, left $start, 0 $end, from($from), to($to)); + background-image: -webkit-linear-gradient(top, $from $start+px, $to $end+px); + background-image: -moz-linear-gradient(top, $from $start+px, $to $end+px); + background-image: -ms-linear-gradient(top, $from $start+px, $to $end+px); + background-image: -o-linear-gradient(top, $from $start+px, $to $end+px); + // No IE support for positioned gradients + } +} + +// Mixin that allows you to set the size of the box to a fixed width/height +// taking into consideration the padding and borders for you. +// +// Examples: +// @include size(100px, 100px, 10px, 5px solid red); +// Will render a 100x100. +// +// $width: Pixel value for width +// $height: Pixel value for height +// $padding: Padding accepts: 1px or 1px 2px or 1px 2px 3px 4px +// $border: Border, accepts 1px solid #000 or 1px or 1px 2px or 1px 2px 3px 4px +// When passing a border declaration (1px solid #000) it'll add the CSS for you. +@mixin size($width, $height:auto, $padding: none, $border:none) { + // Prepare the borders, accept the following: + // 1px solid #000 or 1px or 1px 2px or 1px 2px 3px 4px + @if true { + @if $border == none { + $border:0; + } + + $border-len: length($border); + // Standardize padding to a list with 4 items. + @if $border-len == 3 { + border:$border; + $bw: nth($border, 1); + $border: $bw $bw $bw $bw; + } @else if $border-len == 1 { + $border: $border $border $border $border; + } @else if $border-len == 2 { + $border: join($border, $border); + } + } + + // Prepare padding, accept the following: + // 1px or 1px 2px or 1px 2px 3px 4px + @if true { + @if $padding == none { + $padding:0; + } @else { + padding:$padding; + } + + // Standardize padding to a list with 4 items. + $padding-len: length($padding); + @if $padding-len == 1 { + $padding: $padding $padding $padding $padding; + } @else if $padding-len == 2 { + $padding: join($padding, $padding); + } + } + + @if $width != auto { + width: $width - (nth($padding, 2) + nth($padding, 4)) - (nth($border, 2) + nth($border, 4)); + } + @if $height != auto { + height: $height - (nth($padding, 1) + nth($padding, 3)) - (nth($border, 1) + nth($border, 3)); + } +} + +// Float an element with a given width and a direction. Third parameter allows easy debugging. +// Yes, we override Compass :( +// @TODO: Make it use box-size and allow padding. +@mixin float($side, $size:auto, $debug-color:false) { + @if $size != auto { + @if length($size) == 1 { + width:$size; + } @else { + height:nth($size, 2); + width:nth($size, 1); + } + + } + @if in-compatibility-mode(ie6) { + display:inline; + } + float: $side; + @if $debug-color and $debug { + @include debug($debug-color); + } +} + +@mixin transition($property: all, $time: 400ms, $easing: ease-out){ + transition: $property $time $easing; + -moz-transition: $property $time $easing; + -ms-transition: $property $time $easing; + -o-transition: $property $time $easing; + -webkit-transition: $property $time $easing; +} + +// Calculate the Golden Ratio of a given value. +// ---------------------------------------- +@function golden-ratio($size, $type) { + $big : round($size / 1.61803); + $small : $size - $big; + @return if($type == large, $big, $small); +} + + +// +// @TODO: Add docs to link-colors! +// +@mixin link-colors($normal, $hover: false, $active: false, $visited: false, $focus: false) { + @if $normal == default { + $hover: $link-hover-color; + $normal: $link-color; + $visited: $link-visited-color; + } + color: $normal; + @if $visited { + &:visited { + color: $visited; } } + @if $focus { + &:focus { + color: $focus; } } + @if $hover { + &:hover { + color: $hover; } } + @if $active { + &:active { + color: $active; } } +} + +// Substract the Body to the Container width to get the sidebar. +@function sidebar($body-width, $container-width-over: false) { + @if $container-width-over == false { + $container-width-over : $container-width; + } + @return $container-width - $body-width; +} \ No newline at end of file diff --git a/app/controllers/access_authorizations_controller.rb b/app/controllers/access_authorizations_controller.rb new file mode 100644 index 0000000..54365e7 --- /dev/null +++ b/app/controllers/access_authorizations_controller.rb @@ -0,0 +1,68 @@ +class AccessAuthorizationsController < ApplicationController + load_and_authorize_resource :callthrough + load_and_authorize_resource :access_authorization, :through => [:callthrough] + + before_filter :set_parent_and_path_methods + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @access_authorization = @parent.access_authorizations.build + @access_authorization.name = generate_a_new_name(@parent, @access_authorization) + @access_authorization.phone_numbers.build + @access_authorization.login = random_pin + random_pin + @access_authorization.pin = random_pin + end + + def create + @access_authorization = @parent.access_authorizations.build(params[:access_authorization]) + if @access_authorization.save + redirect_to @show_path_method.(@parent, @access_authorization), :notice => t('access_authorizations.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @access_authorization.update_attributes(params[:access_authorization]) + redirect_to @show_path_method.(@parent, @access_authorization), :notice => t('access_authorizations.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @access_authorization.destroy + redirect_to @index_path_method.(@parent), :notice => t('access_authorizations.controller.successfuly_destroyed') + end + + private + + def set_parent_and_path_methods + @parent = @callthrough + @show_path_method = method( :"#{@parent.class.name.underscore}_access_authorization_path" ) + @index_path_method = method( :"#{@parent.class.name.underscore}_access_authorizations_path" ) + @new_path_method = method( :"new_#{@parent.class.name.underscore}_access_authorization_path" ) + @edit_path_method = method( :"edit_#{@parent.class.name.underscore}_access_authorization_path" ) + end + + def spread_breadcrumbs + if @callthrough + add_breadcrumb t("#{@parent.class.name.underscore.pluralize}.index.page_title"), tenant_callthroughs_path(@callthrough.tenant) + add_breadcrumb @callthrough, tenant_callthrough_path(@callthrough.tenant, @callthrough) + add_breadcrumb t("access_authorizations.index.page_title"), callthrough_access_authorizations_path(@callthrough) + if @access_authorization && !@access_authorization.new_record? + add_breadcrumb @access_authorization, callthrough_access_authorization_path(@callthrough, @access_authorization) + end + end + end + +end diff --git a/app/controllers/acd_agents_controller.rb b/app/controllers/acd_agents_controller.rb new file mode 100644 index 0000000..1d119b3 --- /dev/null +++ b/app/controllers/acd_agents_controller.rb @@ -0,0 +1,73 @@ +class AcdAgentsController < ApplicationController + load_and_authorize_resource :automatic_call_distributor + load_and_authorize_resource :acd_agent, :through => [:automatic_call_distributor] + + before_filter :spread_breadcrumbs + + def index + if params[:active] + if params[:active].downcase == 'true' + @acd_agents = @acd_agents.where(:active => true) + elsif params[:active].downcase == 'false' + @acd_agents = @acd_agents.where(:active => false) + end + end + end + + def show + @acd_agent = AcdAgent.find(params[:id]) + end + + def new + @acd_agent = @automatic_call_distributor.acd_agents.build + i = @automatic_call_distributor.acd_agents.count + loop do + i += 1 + break unless @automatic_call_distributor.acd_agents.where(:name => "#{t('acd_agents.name')} #{i}").count > 0 + end + @acd_agent.name = "#{t('acd_agents.name')} #{i}" + @acd_agent.status = 'active' + @acd_agent.calls_answered = 0 + end + + def create + @acd_agent = @automatic_call_distributor.acd_agents.build(params[:acd_agent]) + if @acd_agent.save + redirect_to automatic_call_distributor_acd_agent_path(@automatic_call_distributor, @acd_agent), :notice => t('acd_agents.controller.successfuly_created') + else + render :new + end + end + + def edit + @acd_agent = AcdAgent.find(params[:id]) + end + + def update + @acd_agent = AcdAgent.find(params[:id]) + if @acd_agent.update_attributes(params[:acd_agent]) + redirect_to automatic_call_distributor_acd_agent_path(@automatic_call_distributor, @acd_agent), :notice => t('acd_agents.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @acd_agent = AcdAgent.find(params[:id]) + @acd_agent.destroy + redirect_to automatic_call_distributor_acd_agents_path(@automatic_call_distributor), :notice => t('acd_agents.controller.successfuly_destroyed') + end + + def spread_breadcrumbs + if @automatic_call_distributor.automatic_call_distributorable.class == User + add_breadcrumb t("#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore.pluralize}.index.page_title"), method( :"tenant_#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore.pluralize}_path" ).(@automatic_call_distributor.tenant) + add_breadcrumb @automatic_call_distributor.automatic_call_distributorable, method( :"tenant_#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore}_path" ).(@automatic_call_distributor.tenant, @automatic_call_distributor.automatic_call_distributorable) + end + add_breadcrumb t("automatic_call_distributors.index.page_title"), method( :"#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore}_automatic_call_distributors_path" ).(@automatic_call_distributor.automatic_call_distributorable) + add_breadcrumb @automatic_call_distributor, method( :"#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore}_automatic_call_distributor_path" ).(@automatic_call_distributor.automatic_call_distributorable, @automatic_call_distributor) + add_breadcrumb t("acd_agents.index.page_title"), automatic_call_distributor_acd_agents_path(@automatic_call_distributor) + if @acd_agent && !@acd_agent.new_record? + add_breadcrumb @acd_agent, automatic_call_distributor_acd_agent_path(@automatic_call_distributor, @acd_agent) + end + end +end diff --git a/app/controllers/acd_callers_controller.rb b/app/controllers/acd_callers_controller.rb new file mode 100644 index 0000000..ab58064 --- /dev/null +++ b/app/controllers/acd_callers_controller.rb @@ -0,0 +1,41 @@ +class AcdCallersController < ApplicationController + def index + @acd_callers = AcdCaller.all + end + + def show + @acd_caller = AcdCaller.find(params[:id]) + end + + def new + @acd_caller = AcdCaller.new + end + + def create + @acd_caller = AcdCaller.new(params[:acd_caller]) + if @acd_caller.save + redirect_to @acd_caller, :notice => t('acd_callers.controller.successfuly_created') + else + render :new + end + end + + def edit + @acd_caller = AcdCaller.find(params[:id]) + end + + def update + @acd_caller = AcdCaller.find(params[:id]) + if @acd_caller.update_attributes(params[:acd_caller]) + redirect_to @acd_caller, :notice => t('acd_callers.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @acd_caller = AcdCaller.find(params[:id]) + @acd_caller.destroy + redirect_to acd_callers_url, :notice => t('acd_callers.controller.successfuly_destroyed') + end +end diff --git a/app/controllers/addresses_controller.rb b/app/controllers/addresses_controller.rb new file mode 100644 index 0000000..a70b1f4 --- /dev/null +++ b/app/controllers/addresses_controller.rb @@ -0,0 +1,41 @@ +class AddressesController < ApplicationController + def index + @addresses = Address.all + end + + def show + @address = Address.find(params[:id]) + end + + def new + @address = Address.new + end + + def create + @address = Address.new(params[:address]) + if @address.save + redirect_to @address, :notice => t('addresses.controller.successfuly_created') + else + render :new + end + end + + def edit + @address = Address.find(params[:id]) + end + + def update + @address = Address.find(params[:id]) + if @address.update_attributes(params[:address]) + redirect_to @address, :notice => t('addresses.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @address = Address.find(params[:id]) + @address.destroy + redirect_to addresses_url, :notice => t('addresses.controller.successfuly_destroyed') + end +end diff --git a/app/controllers/api/rows_controller.rb b/app/controllers/api/rows_controller.rb new file mode 100644 index 0000000..6e815eb --- /dev/null +++ b/app/controllers/api/rows_controller.rb @@ -0,0 +1,91 @@ +class Api::RowsController < ApplicationController + before_filter :check_remote_ip_address_whitelist + + def index + @rows = Api::Row.all + + respond_to do |format| + format.xml { render xml: @rows } + end + end + + def show + if params[:user_name] + @row = Api::Row.find_by_user_name(params[:user_name]) + else + @row = Api::Row.find(params[:id]) + end + + respond_to do |format| + format.xml { render xml: @row } + end + end + + def new + @row = Api::Row.new + + respond_to do |format| + format.xml { render xml: @row } + end + end + + def edit + if params[:user_name] + @row = Api::Row.find_by_user_name(params[:user_name]) + else + @row = Api::Row.find(params[:id]) + end + end + + def create + @row = Api::Row.new(params[:row]) + + respond_to do |format| + if @row.save + @row.create_a_new_gemeinschaft_user + + format.xml { render xml: @row, status: :created, location: @row } + else + format.xml { render xml: @row.errors, status: :unprocessable_entity } + end + end + end + + def update + if params[:user_name] + @row = Api::Row.find_by_user_name(params[:user_name]) + else + @row = Api::Row.find(params[:id]) + end + + respond_to do |format| + if @row.update_attributes(params[:row]) + @row.update_user_data + format.xml { head :no_content } + else + format.xml { render xml: @row.errors, status: :unprocessable_entity } + end + end + end + + def destroy + if params[:user_name] + @row = Api::Row.find_by_user_name(params[:user_name]) + else + @row = Api::Row.find(params[:id]) + end + @row.destroy + + respond_to do |format| + format.xml { head :no_content } + end + end + + private + + def check_remote_ip_address_whitelist + if !(REMOTE_IP_ADDRESS_WHITELIST.empty? or REMOTE_IP_ADDRESS_WHITELIST.include?(ENV['REMOTE_ADDR'])) + redirect_to root_url + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..c675f5c --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,161 @@ +class ApplicationController < ActionController::Base + + protect_from_forgery + + before_filter :set_locale + + before_filter :go_to_setup_if_new_installation + before_filter :home_breadcrumb + + helper_method :current_user + + helper_method :guess_local_ip_address + helper_method :guess_local_host + + helper_method :'have_https?' + + helper_method :random_pin + + + #TODO Add check_authorization. See + # https://github.com/ryanb/cancan + # https://github.com/ryanb/cancan/wiki/Ensure-Authorization + # and Gemeinschaft 4 + + # Generate a new name for an Object + # + def generate_a_new_name(parent, child = nil) + if child + i = parent.send(child.class.name.underscore.pluralize).count + loop do + i += 1 + if I18n.t("#{child.class.name.underscore.pluralize}.new_name_scaffold").include?('translation missing') + @guess_a_new_name = I18n.t(child.class.name.underscore.pluralize + '.name') + " #{i}" + else + @guess_a_new_name = I18n.t("#{child.class.name.underscore.pluralize}.new_name_scaffold", :counter => i.to_s) + end + break unless parent.send(child.class.name.underscore.pluralize).where(:name => "#{@guess_a_new_name}").count > 0 + end + else + i = parent.class.count + loop do + i += 1 + if I18n.t("#{parent.class.name.underscore.pluralize}.new_name_scaffold").include?('translation missing') + @guess_a_new_name = I18n.t(parent.class.name.underscore.pluralize + '.name') + " #{i}" + else + @guess_a_new_name = I18n.t("#{parent.class.name.underscore.pluralize}.new_name_scaffold", :counter => i.to_s) + end + break unless parent.class.where(:name => "#{@guess_a_new_name}").count > 0 + end + end + return @guess_a_new_name + end + + # Generate a new random PIN + # + def random_pin + if MINIMUM_PIN_LENGTH > 0 + (1..MINIMUM_PIN_LENGTH).map{|i| (0 .. 9).to_a.sample}.join + end + end + + # return the IP address (preferred) or hostname at which the + # current request arrived + def server_host + return ( + request.env['SERVER_ADDR'] || + request.env['SERVER_NAME'] || + request.env['HTTP_HOST'] + ) + end + + def have_https? + return Connectivity::port_open?( server_host(), 443 ) + end + + + def guess_local_ip_address + ret = nil + begin + ipsocket_addr_info = UDPSocket.open {|s| s.connect("255.255.255.254", 1); s.addr(false) } + ret = ipsocket_addr_info.last if ipsocket_addr_info + rescue + end + return ret + end + + def guess_local_host + ret = guess_local_ip_address() + if ! ret + begin + if request + ret = request.env['SERVER_NAME'] + end + rescue + end + end + if ret && [ + '', + 'localhost', + '127.0.0.1', + '0.0.0.0', + ].include?(ret) + ret = nil + end + return ret + end + + rescue_from CanCan::AccessDenied do |exception| + if @current_user + redirect_to root_url, :alert => 'Access denied! Please ask your admin to grant you the necessary rights.' + else + if Tenant.count == 0 && User.count == 0 + # This is a brand new system. We need to run a setup first. + redirect_to wizards_new_initial_setup_path + else + # You need to login first. + redirect_to log_in_path, :alert => 'Access denied! You need to login first.' + end + end + end + + private + + def current_user + begin + @current_user ||= User.find(session[:user_id]) if session[:user_id] + rescue ActiveRecord::RecordNotFound + session[:user_id] = nil + end + @current_user + end + + def go_to_setup_if_new_installation + if Rails.env != 'test' + if GemeinschaftSetup.all.count == 0 + redirect_to new_gemeinschaft_setup_path + end + end + end + + def home_breadcrumb + if current_user + if current_user && Tenant.find(current_user.current_tenant_id) + add_breadcrumb( current_user.current_tenant, tenant_path(current_user.current_tenant) ) + else + add_breadcrumb I18n.t('pages.controller.index.name'), :root_path + end + end + end + + def set_locale + if current_user && Language.find(current_user.language_id) + I18n.locale = current_user.language.code.downcase + else + logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}" + I18n.locale = request.compatible_language_from(Language.all.map{|x| x.code}) + end + logger.debug "* Locale set to '#{I18n.locale}'" + end + +end diff --git a/app/controllers/automatic_call_distributors_controller.rb b/app/controllers/automatic_call_distributors_controller.rb new file mode 100644 index 0000000..cc0c7e6 --- /dev/null +++ b/app/controllers/automatic_call_distributors_controller.rb @@ -0,0 +1,100 @@ +class AutomaticCallDistributorsController < ApplicationController + DEFAULT_STRATEGY = 'round_robin' + DEFAULT_MAX_CALLERS = 50 + DEFAULT_AGENT_TIMEOUT = 20 + DEFAULT_RETRY_TIMEOUT = 10 + DEFAULT_JOIN = 'agents_active' + DEFAULT_LEAVE = 'no_agents_active' + + load_resource :user + load_resource :tenant + load_and_authorize_resource :automatic_call_distributor, :through => [:user, :tenant ] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + @automatic_call_distributors = AutomaticCallDistributor.all + end + + def show + @automatic_call_distributor = AutomaticCallDistributor.find(params[:id]) + end + + def new + i = @parent.automatic_call_distributors.count + loop do + i += 1 + break unless @parent.automatic_call_distributors.where(:name => "#{t('automatic_call_distributors.name')} #{i}").count > 0 + end + @strategies = AutomaticCallDistributor::STRATEGIES.collect {|r| [ t("automatic_call_distributors.strategies.#{r.to_s}"), r.to_s ] } + @join_on = AutomaticCallDistributor::JOIN_ON.collect {|r| [ t("automatic_call_distributors.join_on.#{r.to_s}"), r.to_s ] } + @leave_on = AutomaticCallDistributor::LEAVE_ON.collect {|r| [ t("automatic_call_distributors.leave_on.#{r.to_s}"), r.to_s ] } + @automatic_call_distributor = @parent.automatic_call_distributors.build( + :name => "#{t('automatic_call_distributors.name')} #{i}", + :strategy => DEFAULT_STRATEGY, + :max_callers => DEFAULT_MAX_CALLERS, + :retry_timeout => DEFAULT_RETRY_TIMEOUT, + :agent_timeout => DEFAULT_AGENT_TIMEOUT, + :join => DEFAULT_JOIN, + :leave => DEFAULT_LEAVE, + ) + + end + + def create + @automatic_call_distributor = @parent.automatic_call_distributors.build(params[:automatic_call_distributor]) + if @automatic_call_distributor.save + m = method( :"#{@parent.class.name.underscore}_automatic_call_distributor_path" ) + redirect_to m.( @parent, @automatic_call_distributor ), :notice => t('automatic_call_distributors.controller.successfuly_created', :resource => @parent) + else + render :new + end + end + + def edit + @strategies = AutomaticCallDistributor::STRATEGIES.collect {|r| [ t("automatic_call_distributors.strategies.#{r.to_s}"), r.to_s ] } + @join_on = AutomaticCallDistributor::JOIN_ON.collect {|r| [ t("automatic_call_distributors.join_on.#{r.to_s}"), r.to_s ] } + @leave_on = AutomaticCallDistributor::LEAVE_ON.collect {|r| [ t("automatic_call_distributors.leave_on.#{r.to_s}"), r.to_s ] } + @automatic_call_distributor = AutomaticCallDistributor.find(params[:id]) + end + + def update + if @automatic_call_distributor.update_attributes(params[:automatic_call_distributor]) + m = method( :"#{@parent.class.name.underscore}_automatic_call_distributor_path" ) + redirect_to m.( @parent, @automatic_call_distributor ), :notice => t('automatic_call_distributors.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @automatic_call_distributor = AutomaticCallDistributor.find(params[:id]) + @automatic_call_distributor.destroy + m = method( :"#{@parent.class.name.underscore}_automatic_call_distributors_url" ) + redirect_to m.( @parent ), :notice => t('automatic_call_distributors.controller.successfuly_destroyed') + end + + private + def set_and_authorize_parent + @parent = @user || @tenant + authorize! :read, @parent + end + + def spread_breadcrumbs + if @user + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t("automatic_call_distributors.index.page_title"), user_automatic_call_distributors_path(@user) + if @automatic_call_distributor && !@automatic_call_distributor.new_record? + add_breadcrumb @automatic_call_distributor, user_automatic_call_distributor_path(@user, @automatic_call_distributor) + end + end + if @tenant + add_breadcrumb t("automatic_call_distributors.index.page_title"), tenant_automatic_call_distributors_path(@tenant) + if @automatic_call_distributor && !@automatic_call_distributor.new_record? + add_breadcrumb @automatic_call_distributor, tenant_automatic_call_distributor_path(@tenant, @automatic_call_distributor) + end + end + end +end diff --git a/app/controllers/call_forwards_controller.rb b/app/controllers/call_forwards_controller.rb new file mode 100644 index 0000000..5321b35 --- /dev/null +++ b/app/controllers/call_forwards_controller.rb @@ -0,0 +1,127 @@ +class CallForwardsController < ApplicationController + load_and_authorize_resource :phone_number + load_and_authorize_resource :call_forward, :through => [:phone_number] + + before_filter :spread_breadcrumbs + + class CallForwardingDestination + attr_accessor :id, :label + + def to_s + return label + end + end + + + def index + end + + def show + end + + def new + @call_forward = @phone_number.call_forwards.build + @call_forward.depth = DEFAULT_CALL_FORWARD_DEPTH + @call_forward.active = true + @call_forwarding_destinations = call_forwarding_destination_types() + @call_forward.destination = CALLFORWARD_DESTINATION_DEFAULT.to_s if defined?(CALLFORWARD_DESTINATION_DEFAULT) + + @available_call_forward_cases = [] + CallForwardCase.all.each do |available_call_forward_case| + if GuiFunction.display?("call_forward_case_#{available_call_forward_case.value}_field_in_call_forward_form", @current_user) + @available_call_forward_cases << available_call_forward_case + end + end + + if @phone_number.call_forwards.where( + :call_forward_case_id => CallForwardCase.find_by_value('noanswer').id, + :active => true + ).count == 0 + @call_forward.call_forward_case_id = CallForwardCase.find_by_value('noanswer').id + @call_forward.timeout = 45 + end + end + + def create + @call_forward = @phone_number.call_forwards.build( params[:call_forward] ) + + if @call_forward.save + redirect_to phone_number_call_forward_path( @phone_number, @call_forward ), :notice => t('call_forwards.controller.successfuly_created') + else + @available_call_forward_cases = CallForwardCase.all + render :new + end + end + + def edit + @available_call_forward_cases = CallForwardCase.all + @call_forwarding_destinations = call_forwarding_destination_types() + end + + def update + @available_call_forward_cases = CallForwardCase.all + if @call_forward.update_attributes(params[:call_forward]) + redirect_to phone_number_call_forward_path( @phone_number, @call_forward ), :notice => t('call_forwards.controller.successfuly_updated') + else + @call_forwarding_destinations = call_forwarding_destination_types() + render :edit + end + end + + def destroy + @call_forward.destroy + redirect_to phone_number_call_forwards_path( @phone_number ), :notice => t('call_forwards.controller.successfuly_destroyed') + end + + private + def spread_breadcrumbs + if @phone_number && @phone_number.phone_numberable_type == 'SipAccount' + @sip_account = @phone_number.phone_numberable + if @sip_account.sip_accountable_type == 'User' + @user = @phone_number.phone_numberable.sip_accountable + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_users_path(@user.current_tenant, @user) + add_breadcrumb t("sip_accounts.index.page_title"), user_sip_accounts_path(@user) + add_breadcrumb @sip_account, user_sip_account_path(@user, @sip_account) + end + if @sip_account.sip_accountable_type == 'Tenant' + @tenant = @sip_account.sip_accountable + add_breadcrumb t("sip_accounts.index.page_title"), tenant_sip_accounts_path(@tenant) + add_breadcrumb @sip_account, tenant_sip_account_path(@tenant, @sip_account) + end + add_breadcrumb t("phone_numbers.index.page_title"), sip_account_phone_numbers_path(@sip_account) + add_breadcrumb @phone_number, sip_account_phone_number_path(@sip_account, @phone_number) + add_breadcrumb t("call_forwards.index.page_title"), phone_number_call_forwards_path(@phone_number) + if @call_forward && !@call_forward.new_record? + add_breadcrumb @call_forward, phone_number_call_forward_path(@phone_number, @call_forward) + end + end + end + + def call_forwarding_destination_types + + phone_number_destination = CallForwardingDestination.new() + phone_number_destination.id = ':PhoneNumber' + phone_number_destination.label = 'Phone Number' + voice_mail_destination = CallForwardingDestination.new() + voice_mail_destination.id = ':Voicemail' + voice_mail_destination.label = 'Voice Mail' + + call_forwarding_destinations = [ + phone_number_destination, + voice_mail_destination, + ] + + if GuiFunction.display?('huntgroup_in_destination_field_in_call_forward_form', @current_user) + HuntGroup.all.each do |hunt_group| + hunt_group_destination = CallForwardingDestination.new() + hunt_group_destination.id = "#{hunt_group.id}:HuntGroup" + hunt_group_destination.label = "HuntGroup: #{hunt_group.to_s}" + call_forwarding_destinations.push(hunt_group_destination) + end + end + + return call_forwarding_destinations + end + +end diff --git a/app/controllers/call_histories_controller.rb b/app/controllers/call_histories_controller.rb new file mode 100644 index 0000000..f956f88 --- /dev/null +++ b/app/controllers/call_histories_controller.rb @@ -0,0 +1,100 @@ +class CallHistoriesController < ApplicationController + + load_resource :sip_account + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + before_filter { |controller| + if ! params[:type].blank? then + @type = params[:type].to_s + end + + if ! params[:page].blank? then + @pagination_page_number = params[:page].to_i + end + } + + def index + hunt_group_member_ids = PhoneNumber.where(:phone_numberable_type => 'HuntGroupMember', :number => @sip_account.phone_numbers.map {|a| a.number}).map {|a| a.phone_numberable_id} + hunt_group_ids = HuntGroupMember.where(:id => hunt_group_member_ids, :active => true).map {|a| a.hunt_group_id} + calls = CallHistory.where('(call_historyable_type = "SipAccount" AND call_historyable_id = ?) OR (call_historyable_type = "HuntGroup" AND call_historyable_id IN (?))', @sip_account.id, hunt_group_ids).order('start_stamp DESC') + + @call_histories = calls.paginate( + :page => @pagination_page_number, + :per_page => DEFAULT_PAGINATION_ENTRIES_PER_PAGE + ) + + @calls_count = calls.count + @calls_received_count = calls.where(:entry_type => 'received').count + @calls_dialed_count = calls.where(:entry_type => 'dialed').count + @calls_missed_count = calls.where(:entry_type => 'missed').count + @calls_forwarded_count = calls.where(:entry_type => 'forwarded').count + + if ! @type.blank? + @call_histories = @call_histories.where(:entry_type => @type) + end + end + + + def destroy + @call_history = CallHistory.where(:id => params[:id]).first + if can?(:destroy, @call_history) + @call_history.destroy + m = method( :"#{@parent.class.name.underscore}_call_histories_url" ) + redirect_to m.(), :notice => t('call_histories.controller.successfuly_destroyed') + end + end + + def destroy_multiple + if ! params[:selected_ids].blank? then + result = @sip_account.call_histories.where(:id => params[:selected_ids]).destroy_all(); + end + + m = method( :"#{@parent.class.name.underscore}_call_histories_url" ) + if result + redirect_to m.(), :notice => t('call_histories.controller.successfuly_destroyed') + else + redirect_to m.() + end + end + + def call + @call_history = CallHistory.where(:id => params[:id]).first + if can?(:call, @call_history) && @sip_account.registration + phone_number = @call_history.display_number + if ! phone_number.blank? + @sip_account.call(phone_number) + end + end + redirect_to(:back) + end + + private + def set_and_authorize_parent + @parent = @sip_account + + authorize! :read, @parent + + @show_path_method = method( :"#{@parent.class.name.underscore}_call_history_path" ) + @index_path_method = method( :"#{@parent.class.name.underscore}_call_histories_path" ) + @new_path_method = method( :"new_#{@parent.class.name.underscore}_call_history_path" ) + @edit_path_method = method( :"edit_#{@parent.class.name.underscore}_call_history_path" ) + end + + def spread_breadcrumbs + if @parent.class == SipAccount + if @sip_account.sip_accountable.class == User + add_breadcrumb t("#{@sip_account.sip_accountable.class.name.underscore.pluralize}.index.page_title"), method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore.pluralize}_path" ).(@sip_account.tenant) + add_breadcrumb @sip_account.sip_accountable, method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore}_path" ).(@sip_account.tenant, @sip_account.sip_accountable) + end + add_breadcrumb t("sip_accounts.index.page_title"), method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_accounts_path" ).(@sip_account.sip_accountable) + add_breadcrumb @sip_account, method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_account_path" ).(@sip_account.sip_accountable, @sip_account) + add_breadcrumb t("call_histories.index.page_title"), sip_account_call_histories_path(@sip_account) + if @call_history && !@call_history.new_record? + add_breadcrumb @call_history, sip_account_call_history_path(@sip_account, @call_history) + end + end + end + +end diff --git a/app/controllers/calls_controller.rb b/app/controllers/calls_controller.rb new file mode 100644 index 0000000..d5f3b42 --- /dev/null +++ b/app/controllers/calls_controller.rb @@ -0,0 +1,6 @@ +class CallsController < ApplicationController + + def index + @calls = Call.all + end +end diff --git a/app/controllers/callthroughs_controller.rb b/app/controllers/callthroughs_controller.rb new file mode 100644 index 0000000..f489622 --- /dev/null +++ b/app/controllers/callthroughs_controller.rb @@ -0,0 +1,75 @@ +class CallthroughsController < ApplicationController + load_and_authorize_resource :tenant + load_and_authorize_resource :callthrough, :through => [:tenant] + + before_filter :set_parent_and_path_methods + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @callthrough = @tenant.callthroughs.build + @callthrough.name = generate_a_new_name(@tenant, @callthrough) + @callthrough.phone_numbers.build + @callthrough.access_authorizations.build(:name => "#{t('access_authorizations.name')} #{@callthrough.access_authorizations.count + 1}", :pin => random_pin).phone_numbers.build + @callthrough.whitelists.build.phone_numbers.build + end + + def create + @callthrough = @tenant.callthroughs.build(params[:callthrough]) + if @callthrough.save + redirect_to tenant_callthrough_path(@tenant, @callthrough), :notice => t('callthroughs.controller.successfuly_created') + else + @callthrough.phone_numbers.build if @callthrough.phone_numbers.size == 0 + render :new + end + end + + def edit + @callthrough.phone_numbers.build + @callthrough.access_authorizations.build.phone_numbers.build + if @callthrough.whitelisted_phone_numbers.count == 0 + if @callthrough.whitelists.count == 0 + @callthrough.whitelists.build.phone_numbers.build + else + @callthrough.whitelists.first.phone_numbers.build + end + end + end + + def update + if @callthrough.update_attributes(params[:callthrough]) + redirect_to tenant_callthrough_path(@tenant, @callthrough), :notice => t('callthroughs.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @callthrough.destroy + redirect_to tenant_callthroughs_path(@tenant), :notice => t('callthroughs.controller.successfuly_destroyed') + end + + private + + def set_parent_and_path_methods + @parent = @tenant + @show_path_method = method( :"#{@parent.class.name.underscore}_callthrough_path" ) + @index_path_method = method( :"#{@parent.class.name.underscore}_callthroughs_path" ) + @new_path_method = method( :"new_#{@parent.class.name.underscore}_callthrough_path" ) + @edit_path_method = method( :"edit_#{@parent.class.name.underscore}_callthrough_path" ) + end + + def spread_breadcrumbs + if @parent && @parent.class == Tenant + add_breadcrumb t("callthroughs.name").pluralize, tenant_callthroughs_path(@parent) + if @callthrough && !@callthrough.new_record? + add_breadcrumb @callthrough, tenant_callthrough_path(@parent, @callthrough) + end + end + end +end diff --git a/app/controllers/conference_invitees_controller.rb b/app/controllers/conference_invitees_controller.rb new file mode 100644 index 0000000..ce55b5a --- /dev/null +++ b/app/controllers/conference_invitees_controller.rb @@ -0,0 +1,93 @@ +class ConferenceInviteesController < ApplicationController + load_and_authorize_resource :conference + load_and_authorize_resource :conference_invitee, :through => [:conference] + + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @conference_invitee = @conference.conference_invitees.build + @conference_invitee.speaker = true + @conference_invitee.moderator = false + @phone_number = @conference_invitee.build_phone_number + end + + def create + @conference_invitee = @conference.conference_invitees.build(params[:conference_invitee]) + + # Try to find this phone_number in phone_books the current_user can read. + # Save the found entry as phone_book_entry. + # + @conference_invitee.phone_number.parse_and_split_number! + phone_numbers = PhoneNumber.where(:number => @conference_invitee.phone_number.number). + where(:phone_numberable_type => 'PhoneBookEntry') + phone_numbers.each do |phone_number| + phone_book = phone_number.phone_numberable.phone_book + if can?(:read, phone_book) + @conference_invitee.phone_book_entry = phone_number.phone_numberable + break + end + end + + if @conference_invitee.save + # m = method( :"#{@parent_in_route.class.name.underscore}_path" ) + # redirect_to m.( @parent_in_route ), :notice => t('conference_invitees.controller.successfuly_created', :resource => @conference_invitees) + m = method( :"#{@conference_invitee.conference.conferenceable_type.underscore}_conference_path") + redirect_to m.( @conference_invitee.conference.conferenceable, @conference_invitee.conference), :notice => t('conference_invitees.controller.successfuly_created', :resource => @conference_invitees) + else + render :new + end + end + + def edit + authorize! :edit, @parent_in_route + end + + def update + if @conference_invitee.update_attributes(params[:conference_invitee]) + redirect_to @conference_invitee, :notice => t('conference_invitees.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @conference_invitee.destroy + redirect_to conference_invitees_url, :notice => t('conference_invitees.controller.successfuly_destroyed') + end + + private + + def spread_breadcrumbs + if @conference + @parent = @conference.conferenceable + if @parent && @parent.class == User + @user = @parent + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_users_path(@user.current_tenant, @user) + add_breadcrumb t("conferences.index.page_title"), user_conferences_path(@user) + if @conference && !@conference.new_record? + add_breadcrumb @conference, user_conference_path(@user, @conference) + end + end + if @parent && @parent.class == Tenant + @tenant = @parent + add_breadcrumb t("conferences.index.page_title"), tenant_conferences_path(@tenant) + if @conference && !@conference.new_record? + add_breadcrumb @conference, tenant_conference_path(@tenant, @conference) + end + end + + add_breadcrumb t("conference_invitees.index.page_title"), conference_conference_invitees_path(@conference) + if @conference_invitee && !@conference_invitee.new_record? + add_breadcrumb @conference_invitee, conference_conference_invitee_path(@conference, @conference_invitee) + end + end + end + +end diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb new file mode 100644 index 0000000..302a23b --- /dev/null +++ b/app/controllers/conferences_controller.rb @@ -0,0 +1,82 @@ +class ConferencesController < ApplicationController + load_resource :user + load_resource :tenant + load_and_authorize_resource :conference, :through => [:user, :tenant] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + @phone_numbers = @conference.phone_numbers + end + + def new + @conference = @parent.conferences.build + @conference.name = generate_a_new_name(@parent, @conference) + @conference.start = nil + @conference.end = nil + @conference.open_for_anybody = true + @conference.max_members = DEFAULT_MAX_CONFERENCE_MEMBERS + @conference.pin = random_pin + + @conference.open_for_anybody = true + @conference.announce_new_member_by_name = true + @conference.announce_left_member_by_name = true + end + + def create + @conference = @parent.conferences.build(params[:conference]) + if @conference.save + m = method( :"#{@parent.class.name.underscore}_conference_path" ) + redirect_to m.( @parent, @conference ), :notice => t('conferences.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @conference.update_attributes(params[:conference]) + m = method( :"#{@parent.class.name.underscore}_conference_path" ) + redirect_to m.( @parent, @conference ), :notice => t('conferences.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @conference.destroy + m = method( :"#{@parent.class.name.underscore}_conferences_url" ) + redirect_to m.( @parent ), :notice => t('conferences.controller.successfuly_destroyed') + end + + private + + def set_and_authorize_parent + @parent = @tenant || @user + authorize! :read, @parent + end + + def spread_breadcrumbs + if @parent && @parent.class == User + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t("conferences.index.page_title"), user_conferences_path(@user) + if @conference && !@conference.new_record? + add_breadcrumb @conference, user_conference_path(@user, @conference) + end + end + if @parent && @parent.class == Tenant + add_breadcrumb t("conferences.index.page_title"), tenant_conferences_path(@tenant) + if @conference && !@conference.new_record? + add_breadcrumb @conference, tenant_conference_path(@tenant, @conference) + end + end + end + +end diff --git a/app/controllers/config_polycom_controller.rb b/app/controllers/config_polycom_controller.rb new file mode 100644 index 0000000..9d44e51 --- /dev/null +++ b/app/controllers/config_polycom_controller.rb @@ -0,0 +1,330 @@ +class ConfigPolycomController < ApplicationController + MAX_SIP_ACCOUNTS_COUNT = 11 + MAX_SOFTKEYS_COUNT = 12 + MAX_DIRECTORY_ENTRIES = 20 + SIP_DEFAULT_PORT = 5060 + + skip_authorization_check + + before_filter { |controller| + if ! params[:mac_address].blank? then + @mac_address = params[:mac_address].upcase.gsub(/[^0-9A-F]/,'') + @phone = Phone.where({ :mac_address => @mac_address }).first + elsif ! params[:phone].blank? then + @phone = Phone.where({ :id => params[:phone].to_i }).first + end + + if ! @phone + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return false + end + + if ! params[:sip_account].blank? + @sip_account = @phone.sip_accounts.where({ :id => params[:sip_account].to_i }).first + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return false + end + end + + if ! params[:type].blank? + @type = params[:type].to_s.strip.downcase + end + } + + def config_files + if params[:mac_address].blank? then + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render + } + } + end + + def settings + if ! request.env['HTTP_USER_AGENT'].index('polycom') + Rails.logger.info "---> User-Agent indicates not a Polycom phone (#{request.env['HTTP_USER_AGENT'].inspect})" + else + Rails.logger.info "---> Phone #{@mac_address.inspect}, IP address #{request.remote_ip.inspect}" + @phone.update_attributes({ :ip_address => request.remote_ip }) + end + + xml_applications_url = "#{request.protocol}#{request.host_with_port}/config_polycom/#{@phone.id}/0" + + @settings = { + 'device.sntp.serverName' => 'pool.ntp.org', + 'device.sntp.gmtOffset' => 3600, + 'up.welcomeSoundOnWarmBootEnabled' => 0, + 'up.welcomeSoundEnabled' => 0, + 'bg.hiRes.color.selection' => '2,1', + 'msg.mwi.1.callBackMode' => 'contact', + 'msg.mwi.1.callBack' => 'f-vmcheck', + 'feature.enhancedFeatureKeys.enabled' => 1, + 'softkey.feature.basicCallManagement.redundant' => 0, + 'softkey.feature.buddies' => 0, + 'softkey.feature.callers' => 0, + 'softkey.feature.directories' => 0, + 'softkey.feature.endcall' => 1, + 'softkey.feature.forward' => 0, + 'softkey.feature.mystatus' => 0, + 'softkey.feature.newcall' => 0, + 'call.directedCallPickupMethod' => 'legacy', + 'call.directedCallPickupString' => 'f-ia-', + 'call.advancedMissedCalls.enabled' => 0, + 'lineKey.reassignment.enabled' => 1, + 'lineKey.1.category' => 'Line', + 'lineKey.1.index' => 1, + } + + for key_index in 2..42 + @settings["lineKey.#{key_index}.category"] = 'Unassigned' + end + + for ring_class in 1..17 + @settings["se.rt.custom#{ring_class}.name"] = "Ringer#{ring_class-1}" + @settings["se.rt.custom#{ring_class}.ringer"] = "ringer#{ring_class}" + end + @settings["se.rt.custom1.type"] = 'visual' + + for ring_class in 1..17 + @settings["voIpProt.SIP.alertInfo.#{ring_class}.class"] = "custom#{ring_class}" + @settings["voIpProt.SIP.alertInfo.#{ring_class}.value"] = "Ringer#{ring_class-1}" + end + + softkey_index = 1 + blf_index = 0 + + @phone.sip_accounts.each do |sip_account| + sip_account_index = 0 + if (sip_account.sip_accountable_type == @phone.phoneable_type) and (sip_account.sip_accountable_id == @phone.phoneable_id) + sip_account_index += 1 + if sip_account_index == 1 + xml_applications_url = "#{request.protocol}#{request.host_with_port}/config_polycom/#{@phone.id}/#{sip_account.id}" + @settings['voIpProt.SIP.outboundProxy.address'] = sip_account.host + @settings['voIpProt.SIP.outboundProxy.port'] = SIP_DEFAULT_PORT + end + + @settings["reg.#{sip_account_index}.address"] = "#{sip_account.auth_name}@#{sip_account.host}" + @settings["reg.#{sip_account_index}.auth.password"] = sip_account.password + @settings["reg.#{sip_account_index}.auth.userId"] = sip_account.auth_name + @settings["reg.#{sip_account_index}.displayName"] = 'Call' + @settings["reg.#{sip_account_index}.label"] = sip_account.caller_name + @settings["voIpProt.server.#{sip_account_index}.address"] = sip_account.host + @settings["voIpProt.server.#{sip_account_index}.port"] = SIP_DEFAULT_PORT + @settings["call.missedCallTracking.#{sip_account_index}.enabled"] = 0 + + sip_account.softkeys.order(:position).each do |softkey| + softkey_index += 1 + if softkey.softkey_function + softkey_function = softkey.softkey_function.name + end + case softkey_function + when 'blf' + blf_index += 1 + @settings["lineKey.#{softkey_index}.category"] = 'BLF' + @settings["attendant.resourceList.#{blf_index}.address"] = "#{softkey.number}@#{sip_account.host}" + @settings["attendant.resourceList.#{blf_index}.label"] = softkey.label + end + end + end + end + + @settings['mb.idleDisplay.home'] = "#{xml_applications_url}/idle_screen.xml" + @settings['mb.idleDisplay.refresh'] = 60 + + @settings['efk.efklist.1.mname'] = "directory" + @settings['efk.efklist.1.status'] = 1 + @settings['efk.efklist.1.action.string'] = "#{xml_applications_url}/phone_book.xml" + @settings['efk.efklist.2.mname'] = "callhistory" + @settings['efk.efklist.2.status'] = 1 + @settings['efk.efklist.2.action.string'] = "#{xml_applications_url}/call_history.xml" + @settings['efk.efklist.3.mname'] = "applications" + @settings['efk.efklist.3.status'] = 1 + @settings['efk.efklist.3.action.string'] = "#{xml_applications_url}/applications.xml" + + @settings['softkey.1.action'] = "#{xml_applications_url}/phone_book.xml" + @settings['softkey.1.enable'] = 1 + @settings['softkey.1.insert'] = 1 + @settings['softkey.1.label'] = 'Directory' + @settings['softkey.1.precede'] = 1 + @settings['softkey.1.use.active'] = 1 + @settings['softkey.1.use.alerting'] = 0 + @settings['softkey.1.use.dialtone'] = 1 + @settings['softkey.1.use.hold'] = 1 + @settings['softkey.1.use.idle'] = 1 + @settings['softkey.1.use.proceeding'] = 0 + @settings['softkey.1.use.setup'] = 0 + @settings['softkey.2.action'] = "#{xml_applications_url}/call_history.xml" + @settings['softkey.2.enable'] = 1 + @settings['softkey.2.insert'] = 2 + @settings['softkey.2.label'] = 'Call History' + @settings['softkey.2.precede'] = 1 + @settings['softkey.2.use.active'] = 1 + @settings['softkey.2.use.alerting'] = 0 + @settings['softkey.2.use.dialtone'] = 1 + @settings['softkey.2.use.hold'] = 1 + @settings['softkey.2.use.idle'] = 1 + @settings['softkey.2.use.proceeding'] = 0 + @settings['softkey.2.use.setup'] = 0 + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render + } + } + end + + + def settings_directory + respond_to { |format| + format.any { + self.formats = [ :xml ] + render + } + } + end + + + def call_history + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ['dialed', 'missed', 'received'].include? @type + @phone_xml_object = { + :name => "call_history", + :title => @type.titleize, + :entries => [] + } + + calls = @sip_account.call_histories.where(:entry_type => @type).order('start_stamp DESC').limit(MAX_DIRECTORY_ENTRIES) + + calls.each do |call| + display_name = call.display_name + phone_number = call.display_number + phone_book_entry = call.phone_book_entry_by_number(phone_number) + if display_name.blank? + display_name = phone_book_entry.to_s + end + + @phone_xml_object[:entries].push({ + :selected => false, + :number => phone_number, + :date => call.start_stamp.strftime('%d.%m %H:%M'), + :text => display_name, + :url => "tel:#{phone_number}", + }) + end + else + base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}" + @phone_xml_object = { + :name => 'call_history_menu', + :title => 'Call History Lists', + :entries => [ + {:text => 'Missed Calls', :url => "#{base_url}?&type=missed", :selected => false}, + {:text => 'Received Calls', :url => "#{base_url}?&type=received", :selected => false}, + {:text => 'Placed Calls', :url => "#{base_url}?&type=dialed", :selected => false}, + ] + } + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}", :content_type => Mime::HTML + } + } + + end + + + def phone_book + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + @phone_xml_object = { + :name => 'phone_book', + :title => "Phone Book".strip, + :entries => [], + :softkeys => [], + } + + phone_books = Array.new() + phone_books = phone_books + @sip_account.sip_accountable.try(:phone_books).all + if @sip_account.sip_accountable.class == User + phone_books = phone_books + @sip_account.sip_accountable.try(:current_tenant).try(:phone_books).all + end + + phone_book_ids = Array.new() + phone_books.each do |phone_book| + phone_book_ids << phone_book.id + end + + PhoneBookEntry.where(:phone_book_id => phone_book_ids).order(:last_name).order(:first_name).limit(MAX_DIRECTORY_ENTRIES).each do |phone_book_entry| + phone_numbers_count = 0 + phone_book_entry.phone_numbers.each do |phone_number| + phone_numbers_count += 1 + if phone_numbers_count > 1 + entry_name = '' + else + entry_name = phone_book_entry.to_s + end + + @phone_xml_object[:entries] << { :text => entry_name, :type => phone_number.name, :number => phone_number.number, :url => "tel:#{phone_number.number}" } + end + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}", :content_type => Mime::HTML + } + } + + end + + + def idle_screen + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "idle_screen", :content_type => Mime::HTML + } + } + end +end diff --git a/app/controllers/config_siemens_controller.rb b/app/controllers/config_siemens_controller.rb new file mode 100644 index 0000000..f398b1a --- /dev/null +++ b/app/controllers/config_siemens_controller.rb @@ -0,0 +1,1239 @@ +require 'nokogiri' +#doc.search('Message/ItemList').each do |a| puts a.children end +class ConfigSiemensController < ApplicationController +#TODO Authentication + # No access for admins though as this contains personal data. + + # We can't use load_and_authorize_resource() here because + # ConfigSiemensController isn't a resource. + # We can try client certificates + MAX_DIRECTORY_ENTRIES = 20 + + skip_authorization_check + + before_filter { |controller| + if ! params[:phone].blank? + @phone = Phone.where({ :id => params[:phone].to_i }).first + end + + if ! params[:sip_account].blank? + @sip_account = SipAccount.where({ :id => params[:sip_account].to_i }).first + end + + if ! @sip_account && @phone + @sip_account = @phone.sip_accounts.where(:sip_accountable_id => @phone.phoneable_id, :sip_accountable_type => @phone.phoneable_type).first + end + } + + + def index + os40_keys=6 + os60_keys=8 + os80_keys=9 + doc = Nokogiri::XML(request.body.read) + #logger.debug("#{params[:WorkpointMessage].to_xml}") + #logger.debug("#{params[:WorkpointMessage][:Message][:ItemList].to_xml}") + @phone_items=Hash.new + contact_reason = params[:WorkpointMessage][:Message][:ReasonForContact] + fragment = params[:WorkpointMessage][:Message][:fragment] + + reply_status = doc.search('Message/ReasonForContact').first[:status] + reply_action = doc.search('Message/ReasonForContact').first[:action] + + doc.search('Message/ItemList/Item').each do |post_item| + @phone_items[post_item[:name]]=post_item.children.to_s + end + if @phone_items['mac-addr'] + mac_address = @phone_items['mac-addr'] + end + phone_type = @phone_items['device-type'] + if phone_type == "OpenStage 40" + max_keys = (os40_keys) * 2 + elsif phone_type == "OpenStage 60" + max_keys = (os60_keys) * 2 + elsif phone_type == "OpenStage 80" + max_keys = (os80_keys) * 2 + else + max_keys = 0 + end + + blf_keys_max = max_keys / 2 + shift_key_position = blf_keys_max + + #logger.debug(request.body.read) + if mac_address + @phone = Phone.find_by_mac_address(mac_address.gsub(':','').upcase) + + if ! @phone && PROVISIONING_AUTO_ADD_PHONE + tenant = Tenant.where(:id => PROVISIONING_AUTO_TENANT_ID).first + if ! tenant + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + @phone = tenant.phones.build + @phone.mac_address = mac_address + @phone.hot_deskable = true + @phone.phone_model = PhoneModel.where('name LIKE ?', "#{phone_type}").first + if ! @phone.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ! PROVISIONING_AUTO_ADD_SIP_ACCOUNT + return + end + + caller_name_index = 0 + sip_account_last = tenant.sip_accounts.where('caller_name LIKE ?', "#{PROVISIONING_AUTO_SIP_ACCOUNT_CALLER_PREFIX}%").sort { |item1, item2| + item1.caller_name.gsub(/[^0-9]/, '').to_i <=> item2.caller_name.gsub(/[^0-9]/, '').to_i + }.last + + if sip_account_last + caller_name_index = sip_account_last.caller_name.gsub(/[^0-9]/, '').to_i + end + caller_name_index = caller_name_index + 1 + + @sip_account = tenant.sip_accounts.build + @sip_account.caller_name = "#{PROVISIONING_AUTO_SIP_ACCOUNT_CALLER_PREFIX}#{caller_name_index}" + @sip_account.call_waiting = CALL_WAITING + @sip_account.clir = DEFAULT_CLIR_SETTING + @sip_account.clip = DEFAULT_CLIP_SETTING + @sip_account.voicemail_pin = random_pin + @sip_account.callforward_rules_act_per_sip_account = CALLFORWARD_RULES_ACT_PER_SIP_ACCOUNT_DEFAULT + @sip_account.hotdeskable = false + loop do + @sip_account.auth_name = SecureRandom.hex(DEFAULT_LENGTH_SIP_AUTH_NAME) + break unless SipAccount.exists?(:auth_name => @sip_account.auth_name) + end + @sip_account.password = SecureRandom.hex(DEFAULT_LENGTH_SIP_PASSWORD) + + if ! @sip_account.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + phone_sip_account = PhoneSipAccount.new() + phone_sip_account.phone_id = @phone.id + phone_sip_account.sip_account_id = @sip_account.id + + if ! phone_sip_account.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + end + end + + + country = 'US' + language = 'en' + if ! @phone.nil? + @phone.update_attributes(:ip_address => request.remote_ip) + @sip_account = @phone.sip_accounts.where(:sip_accountable_type => @phone.phoneable_type, + :sip_accountable_id => @phone.phoneable_id).first + + if @phone.phoneable + if @phone.phoneable_type == 'Tenant' + tenant = @phone.phoneable + language = tenant.language.code + elsif @phone.phoneable_type == 'User' + language = @phone.phoneable.language.code + tenant = @phone.phoneable.current_tenant + end + end + + if tenant && tenant.country + country_map = { + '61' => 'AU', # Australia + '43' => 'AT', # Austria + '86' => 'CN', # China + '45' => 'DK', # Denmark + '33' => 'FA', # France + '49' => 'DE', # Germany + '44' => 'GB', # Great Britain + '91' => 'IN', # India + '39' => 'IT', # Italy + '81' => 'JP', # Japan + '52' => 'MX', # Mexico + '31' => 'NL', # Netherlands + '47' => 'NO', # Norway + '64' => 'NZ', # New Zealand + '34' => 'ES', # Spain + '46' => 'SE', # Sweden + '41' => 'CH', # Switzerland + } + if country_map.include?(tenant.country.country_code) + country = country_map[tenant.country.country_code] + end + end + end + + if ! @phone.nil? && ! @sip_account.blank? + #logger.debug(@phone_items) + @my_nonce = params[:WorkpointMessage][:Message][:nonce] + @new_settings = Array.new + + @new_settings << ['dhcp', nil, 'true'] + @new_settings << ['hostname', nil, mac_address.gsub(':', '') ] + #@new_settings << ['e164-hostname', nil, 'false'] + @new_settings << ['mobility-enabled', nil, 'false'] + @new_settings << ['mobility-password-on-logoff', nil, 'false'] + @new_settings << ['e164', nil, @sip_account.auth_name] + @new_settings << ['sip-user-id', nil, @sip_account.auth_name] + #Not supported + #@new_settings << ['reg-id', nil, @sip_account.auth_name] + #@new_settings << ['reg-number', nil, @sip_account.auth_name] + #@new_settings << ['fully-qualified-phone-no', nil, @sip_account.auth_name] + @new_settings << ['sip-pwd', nil, @sip_account.password] + @new_settings << ['sip-name', nil, @sip_account.caller_name] + @new_settings << ['register-by-name', nil, 'false'] + @new_settings << ['display-id-unicode', nil, @sip_account.caller_name] + @new_settings << ['use-display-id', nil, 'true'] + @new_settings << ['reg-addr', nil, @sip_account.sip_domain.host] + @new_settings << ['reg-port', nil, '5060'] + @new_settings << ['registrar-addr', nil, @sip_account.sip_domain.host] + @new_settings << ['registrar-port', nil, '5060'] + #@new_settings << ['outbound-proxy', nil, @sip_account.sip_domain.host] + @new_settings << ['outbound-proxy-user', nil, @sip_account.auth_name] + #@new_settings << ['sgnl-gateway-addr', nil, @sip_account.sip_domain.host] + #@new_settings << ['sgnl-gateway-port', nil, '5060' ] + @new_settings << ['sgnl-gateway-addr-user', nil, @sip_account.sip_domain.host] + @new_settings << ['sgnl-gateway-port-user', nil, '5060'] + @new_settings << ['sgnl-route', nil, '0' ] + @new_settings << ['mwi-e164', nil, '' ] + @new_settings << ['rtp-base-port', nil, '5004'] + @new_settings << ['default-domain', nil, '' ] + #@new_settings << ['sip-transport', nil, '0' ] + @new_settings << ['sip-transport-user', nil, '0' ] + @new_settings << ['server-type', nil, '0' ] + @new_settings << ['session-timer', nil, 'false' ] + @new_settings << ['session-duration', nil, '3600' ] + @new_settings << ['reg-ttl', nil, '3600' ] + @new_settings << ['realm', nil, @sip_account.sip_domain.realm] + @new_settings << ['emergency-e164', nil, '0110' ] + @new_settings << ['voice-mail-e164', nil, 'f-vmcheck'] + @new_settings << ['auto-answer', nil, 'false'] + @new_settings << ['beep-on-auto-answer', nil, 'true'] + @new_settings << ['auto-reconnect', nil, 'false' ] + @new_settings << ['beep-on-auto-reconnect', nil, 'true'] + #@new_settings << ['permit-decline-call', nil, 'true'] + @new_settings << ['transfer-on-ring', nil, 'false' ] + @new_settings << ['join-allowed-in-conference', nil, 'true'] + @new_settings << ['pickup-group-uri', nil, "f-ig-#{@sip_account.id}"] + @new_settings << ['pickup-group-uri', nil, '' ] + @new_settings << ['hot-line-warm-line-digits', nil, '' ] + @new_settings << ['initial-digit-timer', nil, '30' ] + @new_settings << ['conference-factory-uri', nil, ''] + @new_settings << ['callback-busy-allow', nil, 'false'] + @new_settings << ['callback-busy-code', nil, '' ] + @new_settings << ['callback-ring-allow', nil, 'false'] + @new_settings << ['callback-ring-code', nil, ''] + @new_settings << ['callback-cancel-code', nil, ''] + @new_settings << ['park-server', nil, ''] + #OPTIMIZE Callwaiting + @new_settings << ['call-waiting-enabled', nil, 'true'] + @new_settings << ['qos-layer2', nil, 'true'] + @new_settings << ['l2qos-voice', nil, '5' ] + @new_settings << ['l2qos-signalling', nil, '3' ] + @new_settings << ['l2qos-default', nil, '0'] + @new_settings << ['qos-layer3', nil, 'true'] + @new_settings << ['l3qos-voice', nil, '46'] + @new_settings << ['l3qos-signalling', nil, '26'] + @new_settings << ['vlan-method', nil, '1'] + #OPTIMIZE Timezone settings + @new_settings << ['time', nil, Time.new.localtime.to_i] + @new_settings << ['sntp-addr', nil, 'NULL'] + @new_settings << ['sntp-tz-offset', nil, (Time.new.utc_offset/60).to_i] + @new_settings << ['daylight-save', nil, 'true'] + @new_settings << ['daylight-save-minutes', nil, '0'] + @new_settings << ['auto-daylight-save', nil, 'true'] + @new_settings << ['daylight-save-zone-id', nil, '9'] + #OPTIMIZE Use SNMP? + @new_settings << ['snmp-trap-addr', nil, 'NULL'] + @new_settings << ['snmp-trap-port', nil, '162'] + @new_settings << ['snmp-trap-pwd', nil, 'snmp' ] + @new_settings << ['snmp-traps-active', nil, 'false' ] + @new_settings << ['diagnostic-trap-addr', nil, 'NULL'] + @new_settings << ['diagnostic-trap-port', nil, '162'] + @new_settings << ['diagnostic-trap-pwd', nil, 'snmp' ] + @new_settings << ['diagnostic-traps-active', nil, 'false' ] + @new_settings << ['diagnostic-snmp-active', nil, 'false'] + @new_settings << ['qdc-collection-unit-addr', nil, 'NULL'] + @new_settings << ['qdc-collection-unit-port', nil, '12010'] + + @new_settings << ['qdc-trap-pwd', nil, 'QOSDC'] + @new_settings << ['qdc-snmp-active', nil, 'false'] + @new_settings << ['qdc-qcu-active', nil, 'false'] + @new_settings << ['snmp-queries-allowed', nil, 'false'] + #@new_settings << ['snmp-pwd', nil, ''] + @new_settings << ['disable-microphone', nil, 'false'] + @new_settings << ['loudspeech-enabled', nil, 'true'] + @new_settings << ['audio-silence-suppression', nil, 'false'] + + @new_settings << ['port1', nil, '0' ] # 0=Automatic (speed) + @new_settings << ['port2', nil, '0' ] + @new_settings << ['port2-mode', nil, '1' ] + @new_settings << ['port2-auto-mdix-enabled', nil, 'true' ] + @new_settings << ['originating-line-preference', nil, '0'] + @new_settings << ['terminating-line-preference', nil, '0'] + @new_settings << ['line-key-operating-mode', nil, '0'] + @new_settings << ['line-rollover-type', nil, '2'] + @new_settings << ['line-rollover-volume', nil, '5' ]# 1-5 + @new_settings << ['line-registration-leds', nil, 'true'] + @new_settings << ['keyset-use-focus', nil, 'true' ] + @new_settings << ['keyset-remote-forward-ind', nil, 'true'] + @new_settings << ['keyset-reservation-timer', nil, '60' ] # 0-300 + @new_settings << ['dial-plan-enabled', nil, 'false' ] + @new_settings << ['Canonical-dialing-international-prefix', nil, ''] + @new_settings << ['Canonical-dialing-local-country-code', nil, ''] + @new_settings << ['Canonical-dialing-national-prefix', nil, ''] + @new_settings << ['Canonical-dialing-local-area-code', nil, ''] + @new_settings << ['Canonical-dialing-local-node', nil, ''] + @new_settings << ['Canonical-dialing-external-access', nil, '0'] + @new_settings << ['Canonical-dialing-operator-code', nil, ''] + @new_settings << ['Canonical-dialing-emergency-number', nil, ''] + @new_settings << ['Canonical-dialing-dial-needs-access-code', nil, '0'] + @new_settings << ['Canonical-dialing-dial-needs-intGWcode', nil, '1'] + @new_settings << ['Canonical-dialing-min-local-number-length', nil, '10'] + @new_settings << ['Canonical-dialing-extension-initial-digits', nil, ''] + @new_settings << ['Canonical-dialing-dial-internal-form', nil, '0' ] + @new_settings << ['Canonical-dialing-dial-external-form', nil, '0' ] + #@new_settings << ['Canonical-lookup-local-code', nil, '' ] + #@new_settings << ['Canonical-lookup-international-code', nil, ''] + @new_settings << ['hot-keypad-dialing', nil, 'false'] + @new_settings << ['ldap-transport', nil, '0'] + @new_settings << ['ldap-server-address', nil, 'NULL' ] + @new_settings << ['ldap-server-port', nil, '389' ] + @new_settings << ['ldap-authentication', nil, '1'] + @new_settings << ['ldap-user', nil, 'NULL' ] + @new_settings << ['ldap-pwd', nil, 'NULL' ] + @new_settings << ['ldap-max-responses', nil, '25'] + @new_settings << ['backup-addr', nil, 'NULL'] + @new_settings << ['backup-registration', nil, 'false'] + @new_settings << ['qdc-qcu-active', nil, 'false' ] + @new_settings << ['min-admin-passw-length', nil, '6' ] + #@new_settings << ['default-locked-config-menus', nil, 'true' ] + @new_settings << ['locked-config-menus', nil, 'true' ] + #@new_settings << ['default-locked-local-function-menus', nil, 'true' ] + @new_settings << ['locked-local-function-menus', nil, 'true' ] + #@new_settings << ['dls-mode-secure', nil, '0' ] + @new_settings << ['dls-chunk-size', nil, '9492'] + #@new_settings << ['default-passw-policy', nil, 'false'] + @new_settings << ['deflect-destination', nil, ''] + @new_settings << ['display-skin', nil, (@phone.phoneable_type == 'User' ? '0' : '1')] + @new_settings << ['enable-bluetooth-interface', nil, 'true'] + @new_settings << ['usb-access-enabled', nil, 'false' ] + @new_settings << ['usb-backup-enabled', nil, 'false' ] + @new_settings << ['line-button-mode', nil, '0' ] + @new_settings << ['lock-forwarding', nil, '' ] + @new_settings << ['loudspeaker-function-mode', nil, '0' ] + @new_settings << ['max-pin-retries', nil, '10' ] + @new_settings << ['screensaver-enabled', nil, 'false' ] + @new_settings << ['inactivity-timeout', nil, '60' ] + @new_settings << ['not-used-timeout', nil, '5' ] + @new_settings << ['passw-char-set', nil, '0' ] + @new_settings << ['refuse-call', nil, 'true' ] + @new_settings << ['restart-password', nil, '124816'] + #OPTIMIZE clock format + @new_settings << ['time-format', nil, '0' ]# 1=12 h + @new_settings << ['uaCSTA-enabled', nil, 'false' ] + @new_settings << ['enable-test-interface', nil, 'false'] + @new_settings << ['enable-WBM', nil, 'true'] + #@new_settings << ['pixelsaver-timeout', nil, '2' ]# 2 hours? + #@new_settings << ['voice-message-dial-tone', nil, '' ] + #@new_settings << ['call-pickup-allowed', nil, 'true' ] + @new_settings << ['group-pickup-tone-allowed', nil, 'true'] + @new_settings << ['group-pickup-as-ringer', nil, 'false'] + @new_settings << ['group-pickup-alert-type', nil, '0' ] + #@new_settings << ['default-profile', nil, '' ] + @new_settings << ['count-medium-priority', nil, '5'] + @new_settings << ['timer-medium-priority', nil, '60'] # 1 - 999 + @new_settings << ['timer-high-priority', nil, '5'] # 0 - 999 + @new_settings << ['dss-sip-detect-timer', nil, '10'] + @new_settings << ['dss-sip-deflect', nil, 'false' ] + @new_settings << ['dss-sip-refuse', nil, 'false' ] + @new_settings << ['feature-availability', nil, 'false'] + @new_settings << ['feature-availability', nil, 'true' ] + @new_settings << ['feature-availability', nil, 'true' ] + @new_settings << ['local-control-feature-availability', nil, 'false' ] + @new_settings << ['trace-level', nil, '0' ] # Off + #@new_settings << ['default-locked-function-keys', nil, 'true' ]# "unknown item" + @new_settings << ['blf-code', nil, 'f-ia-'] # pickup prefix for softkey function 59 (BLF) + @new_settings << ['stimulus-feature-code', nil, true] + @new_settings << ['stimulus-led-control-uri', nil, true] + + @new_settings << ['min-user-passw-length', nil, '6' ]# 6 - 24 + #OPTIMIZE language + @new_settings << ['country-iso', nil, country ] + @new_settings << ['language-iso', nil, language] + @new_settings << ['date-format', nil, '0' ] # DD.MM.YYYY + #OPTIMIZE ringtones + @new_settings << ['ringer-melody', nil, '1'] + + @new_settings << ['ringer-melody', nil, '2'] + @new_settings << ['ringer-tone-sequence', nil, '2'] + + 1.upto(8) do |index| + @new_settings << ['alert', index, "Ringer#{index}^#{index}^2^60"] + end + @new_settings << ['alert', 9, "Ringer9^1^1^60"] + @new_settings << ['alert', 10, "Ringer10^1^3^60"] + @new_settings << ['alert', 11, "Ringer0^0^2^60"] + + #Applications + @new_settings << ['XML-app-name', 1, 'call_history'] + @new_settings << ['XML-app-control-key', 1, '3'] + @new_settings << ['XML-app-action', 1, 'update'] + @new_settings << ['XML-app-display-name', 1, 'Call History'] + @new_settings << ['XML-app-program-name', 1, "config_siemens/#{@phone.id}/call_history.xml"] + @new_settings << ['XML-app-special-instance', 1, '3'] + @new_settings << ['XML-app-server-addr', 1, request.host] + @new_settings << ['XML-app-server-port', 1, '80'] + @new_settings << ['XML-app-transport', 1, '0'] + @new_settings << ['XML-app-proxy-enabled', 1, 'false'] + @new_settings << ['XML-app-remote-debug', 1, 'false'] + @new_settings << ['XML-app-debug-prog-name', 1, ''] + @new_settings << ['XML-app-num-tabs', 1, '3'] + @new_settings << ['XML-app-restart', 1, 'true'] + @new_settings << ['XML-app-auto-start', 1, 'true'] + @new_settings << ['XML-app-tab1-display-name', 1, 'Missed'] + @new_settings << ['XML-app-tab1-name', 1, 'call_history'] + @new_settings << ['XML-app-tab2-display-name', 1, 'Received'] + @new_settings << ['XML-app-tab2-name', 1, 'call_history_received'] + @new_settings << ['XML-app-tab3-display-name', 1, 'Dialed'] + @new_settings << ['XML-app-tab3-name', 1, 'call_history_dialed'] + + @new_settings << ['XML-app-name', 2, 'menu'] + @new_settings << ['XML-app-control-key', 2, '6'] + @new_settings << ['XML-app-action', 2, 'update'] + @new_settings << ['XML-app-display-name', 2, 'Menu'] + @new_settings << ['XML-app-program-name', 2, "config_siemens/#{@phone.id}/menu.xml"] + @new_settings << ['XML-app-special-instance', 2, '0'] + @new_settings << ['XML-app-server-addr', 2, request.host] + @new_settings << ['XML-app-server-port', 2, '80'] + @new_settings << ['XML-app-transport', 2, '0'] + @new_settings << ['XML-app-proxy-enabled', 2, 'false'] + @new_settings << ['XML-app-remote-debug', 2, 'false'] + @new_settings << ['XML-app-debug-prog-name', 2, ''] + @new_settings << ['XML-app-num-tabs', 2, '3'] + @new_settings << ['XML-app-restart', 2, 'true'] + @new_settings << ['XML-app-tab1-display-name', 2, "Gemeinschaft #{GEMEINSCHAFT_VERSION}"] + @new_settings << ['XML-app-tab1-name', 2, 'menu'] + @new_settings << ['XML-app-tab2-display-name', 2, 'Status'] + @new_settings << ['XML-app-tab2-name', 2, 'menu_status'] + @new_settings << ['XML-app-tab3-display-name', 2, 'Help'] + @new_settings << ['XML-app-tab3-name', 2, 'menu_help'] + + + @new_settings << ['clear-calllog', nil, 'true'] + @new_settings << ['server-based-features', nil, 'true'] + + + if ! @sip_account.call_forwards.blank? + call_forwarding_object = @sip_account.call_forwards.where(:call_forward_case_id => CallForwardCase.where(:value => 'always').first).first + if call_forwarding_object + @new_settings << ['key-functionality', 4002, '1'] + @new_settings << ['function-key-def', 4002, '63'] + @new_settings << ['stimulus-led-control-uri', 4002, "f-cftg-#{call_forwarding_object.id}" ] + @new_settings << ['send-url-address', 4002, request.host] + @new_settings << ['send-url-protocol', 4002, 3] # 0=https, 3=http + @new_settings << ['send-url-port', 4002, '80'] + @new_settings << ['send-url-path', 4002, "/config_siemens/#{@phone.id}/#{@sip_account.id}/call_forwarding.xml"] + @new_settings << ['send-url-query', 4002, "id=#{call_forwarding_object.id}&function=toggle"] + @new_settings << ['send-url-method', 4002, '0'] # 0=get, 1=post + else + @new_settings << ['key-functionality', 4002, '0'] + end + else + @new_settings << ['key-functionality', 4002, '0'] + end + + @new_settings << ['function-key-def', 4003, '10'] # Hold + + @new_settings << ['feature-availability', 2, 'false' ] # call forwarding + @new_settings << ['feature-availability', 11, 'false' ] # DND + @new_settings << ['feature-availability', 30, 'false'] # DSS + @new_settings << ['feature-availability', 31, 'false'] # feature toggle + @new_settings << ['feature-availability', 33, 'true'] # line overview + @new_settings << ['feature-availability', 33, 'false'] # phone lock + + + @soft_keys = Array.new + # Fill softkeys with keys dependent on limit of phone + @sip_account.softkeys.order(:position).each do |sk| + @soft_keys << sk + end + # Delete unset softkeys + # OPTIMIZE 40 should be enough for 2 modules, but for some reason array is empty o early + max_keys = max_keys + 50 + while @soft_keys.length <= max_keys + @soft_keys << Softkey.new + end + + @key_pos=1 + + #@soft_keys.each do |sk| + + while @key_pos < shift_key_position + + (1..shift_key_position-1).each do |idx| + first_level_keys(idx) + end + end + if @key_pos == shift_key_position + @new_settings << ['function-key-def', shift_key_position, '18'] + @new_settings << ['key-label', shift_key_position, 'Shift'] + @new_settings << ['key-label-unicode', shift_key_position, 'Shift'] + @key_pos = @key_pos+1 + end + + (1001..1000+shift_key_position-1).each do |idx| + second_level_keys(idx) + end + # First key-module first level + (301..311).each do |idx| + first_level_keys(idx) + end + # First key-module shift level + (1301..1311).each do |idx| + second_level_keys(idx) + end + # Second key-module first level + (401..411).each do |idx| + first_level_keys(idx) + end + # Second key-module shift level + (1401..1411).each do |idx| + second_level_keys(idx) + end + [312, 412].each do |idx| + @new_settings << ['function-key-def', idx, '18'] + @new_settings << ['key-label', idx, 'Shift'] + @new_settings << ['key-label-unicode', idx, 'Shift'] + end + #end + logger.debug(@new_settings) + end + + if (@phone.nil? || @sip_account.blank?) && fragment != "final" + @new_settings = Array.new + @my_nonce = params[:WorkpointMessage][:Message][:nonce] + @new_settings << ['e164', nil, 'NULL'] + @new_settings << ['sip-user-id', nil, ""] + @new_settings << ['sip-pwd', nil, ""] + @new_settings << ['sip-name', nil, ""] + @new_settings << ['display-id-unicode', nil, ""] + @new_settings << ['reg-addr', nil, ""] + @new_settings << ['registrar-addr', nil, "NULL"] + @new_settings << ['outbound-proxy-user', nil, ""] + @new_settings << ['sgnl-gateway-addr-user', nil, ""] + @new_settings << ['realm', nil, ""] + @new_settings << ['pickup-group-uri', nil, ""] + logger.debug(@new_settings) + + respond_to { |format| + format.xml { render :action => "write" } + } + elsif contact_reason == 'local-changes' + respond_to { |format| + format.xml { render :action => "clean-up" } + } + elsif (reply_status == 'accepted' && contact_reason == 'reply-to' && reply_action == 'ReadAllItems') + respond_to { |format| + format.xml { render :action => "write" } + } + + elsif ["reply-to"].include? contact_reason + respond_to { |format| + format.xml { render :action => "clean-up" } + } + + else + respond_to { |format| + format.xml { render :action => "index" } + } + end + + end + + def first_level_keys(key_idx) + sk = @soft_keys.shift + if sk.softkey_function + softkey_function = sk.softkey_function.name + end + case softkey_function + when 'blf' + @new_settings << ['function-key-def', key_idx, '59'] + @new_settings << ['stimulus-led-control-uri', key_idx, sk.number ] + @new_settings << ['stimulus-DTMF-sequence', key_idx, sk.number ] + @new_settings << ['blf-popup', key_idx, 'true'] + when 'log_out' + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, 'f-lo' ] + @new_settings << ['stimulus-led-control-uri', key_idx, '' ] + @new_settings << ['stimulus-DTMF-sequence', key_idx, '' ] + when 'log_in' + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, "f-li-#{sk.number}" ] + @new_settings << ['stimulus-led-control-uri', key_idx, '' ] + @new_settings << ['stimulus-DTMF-sequence', key_idx, '' ] + when 'hold' + @new_settings << ['function-key-def', key_idx, '10'] + @new_settings << ['select-dial', key_idx, '' ] + @new_settings << ['stimulus-led-control-uri', key_idx, '' ] + @new_settings << ['stimulus-DTMF-sequence', key_idx, '' ] + when 'dtmf' + @new_settings << ['function-key-def', key_idx, '54'] + @new_settings << ['stimulus-DTMF-sequence', key_idx, sk.number ] + @new_settings << ['stimulus-led-control-uri', key_idx, '' ] + @new_settings << ['stimulus-DTMF-sequence', key_idx, '' ] + when 'call_forwarding' + @new_settings << ['function-key-def', key_idx, '63'] + @new_settings << ['stimulus-led-control-uri', key_idx, "f-cftg-#{sk.call_forward_id}" ] + @new_settings << ['send-url-address', key_idx, request.host] + @new_settings << ['send-url-protocol', key_idx, 3] + @new_settings << ['send-url-port', key_idx, '80'] + @new_settings << ['send-url-path', key_idx, "/config_siemens/#{@phone.id}/#{@sip_account.id}/call_forwarding.xml"] + @new_settings << ['send-url-query', key_idx, "id=#{sk.call_forward_id}&function=toggle"] + @new_settings << ['send-url-method', key_idx, '0'] + @new_settings << ['blf-popup', key_idx, 'false'] + when 'call_forwarding_always' + phone_number = PhoneNumber.where(:number => sk.number, :phone_numberable_type => 'SipAccount').first + if phone_number + account_param = (phone_number.phone_numberable_id != @sip_account.id ? "&account=#{phone_number.phone_numberable_id}" : '') + else + phone_number = @sip_account.phone_numbers.first + account_param = '' + end + + if phone_number + @new_settings << ['function-key-def', key_idx, '63'] + @new_settings << ['stimulus-led-control-uri', key_idx, "f-cfutg-#{phone_number.id}" ] + @new_settings << ['send-url-address', key_idx, request.host] + @new_settings << ['send-url-protocol', key_idx, 3] # 0=https, 3=http + @new_settings << ['send-url-port', key_idx, '80'] + @new_settings << ['send-url-path', key_idx, "/config_siemens/#{@phone.id}/#{@sip_account.id}/call_forwarding.xml"] + @new_settings << ['send-url-query', key_idx, "type=always&function=toggle#{account_param}"] + @new_settings << ['send-url-method', key_idx, '0'] # 0=get, 1=post + # @new_settings << ['send-url-user-id', key_idx, 'user'] + # @new_settings << ['send-url-passwd', key_idx, 'secret'] + @new_settings << ['blf-popup', key_idx, 'false'] + end + when 'call_forwarding_assistant' + phone_number = PhoneNumber.where(:number => sk.number, :phone_numberable_type => 'SipAccount').first + if phone_number + account_param = (phone_number.phone_numberable_id != @sip_account.id ? "&account=#{phone_number.phone_numberable_id}" : '') + else + phone_number = @sip_account.phone_numbers.first + account_param = '' + end + + if phone_number + @new_settings << ['function-key-def', key_idx, '63'] + @new_settings << ['stimulus-led-control-uri', key_idx, "f-cfatg-#{phone_number.id}" ] + @new_settings << ['send-url-address', key_idx, request.host] + @new_settings << ['send-url-protocol', key_idx, 3] # 0=https, 3=http + @new_settings << ['send-url-port', key_idx, '80'] + @new_settings << ['send-url-path', key_idx, "/config_siemens/#{@phone.id}/#{@sip_account.id}/call_forwarding.xml"] + @new_settings << ['send-url-query', key_idx, "type=assistant&function=toggle#{account_param}"] + @new_settings << ['send-url-method', key_idx, '0'] # 0=get, 1=post + @new_settings << ['blf-popup', key_idx, 'false'] + end + when 'hunt_group_membership' + phone_number = PhoneNumber.where(:number => sk.number, :phone_numberable_type => 'HuntGroup').first + if phone_number + hunt_group = HuntGroup.where(:id => phone_number.phone_numberable_id).first + end + + sip_account_phone_numbers = Array.new() + @sip_account.phone_numbers.each do |pn| + sip_account_phone_numbers.push(pn.number) + end + + hunt_group_member_numbers = PhoneNumber.where(:number => sip_account_phone_numbers, :phone_numberable_type => 'HuntGroupMember') + + hunt_group_member = nil + if hunt_group and hunt_group_member_numbers + hunt_group_member_numbers.each do |hunt_group_member_number| + hunt_group_member = hunt_group.hunt_group_members.where(:id => hunt_group_member_number.phone_numberable_id).first + if hunt_group_member + break + end + end + end + + if hunt_group_member + @new_settings << ['function-key-def', key_idx, '63'] + @new_settings << ['stimulus-led-control-uri', key_idx, "f-hgmtg-#{hunt_group_member.id}" ] + @new_settings << ['send-url-address', key_idx, request.host] + @new_settings << ['send-url-protocol', key_idx, 3] # 0=https, 3=http + @new_settings << ['send-url-port', key_idx, '80'] + @new_settings << ['send-url-path', key_idx, "/config_siemens/#{@phone.id}/#{@sip_account.id}/hunt_group.xml"] + @new_settings << ['send-url-query', key_idx, "group=#{hunt_group.id}&account=#{hunt_group_member.id}&function=toggle"] + @new_settings << ['send-url-method', key_idx, '0'] # 0=get, 1=post + @new_settings << ['blf-popup', key_idx, 'false'] + end + when nil + @new_settings << ['function-key-def', key_idx, '0'] + @new_settings << ['stimulus-led-control-uri', key_idx, '' ] + @new_settings << ['stimulus-DTMF-sequence', key_idx, '' ] + else + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, sk.number ] + @new_settings << ['stimulus-led-control-uri', key_idx, '' ] + @new_settings << ['stimulus-DTMF-sequence', key_idx, '' ] + end + @new_settings << ['key-label', key_idx, sk.label ] + @new_settings << ['key-label-unicode', key_idx, sk.label ] + @key_pos = @key_pos+1 + end + + def second_level_keys(key_idx) + sk = @soft_keys.shift + softkey_function = nil + if sk.softkey_function + softkey_function = sk.softkey_function.name + end + case softkey_function + when 'log_out' + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, 'f-lo' ] + when 'log_in' + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, "f-li-#{sk.number}" ] + when 'dtmf' + @new_settings << ['function-key-def', key_idx, '54'] + @new_settings << ['stimulus-DTMF-sequence', key_idx, sk.number ] + when nil + @new_settings << ['function-key-def', key_idx, '0'] + else + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, sk.number ] + end + @new_settings << ['key-label', key_idx, sk.label ] + @new_settings << ['key-label-unicode', key_idx, sk.label ] + @key_pos = @key_pos+1 + end + + def call_history + if ! params[:number].blank? + number = params[:number] + end + + if ! params[:function].blank? + function = params[:function].to_s.downcase + end + + if ! params[:sip_account].blank? + @sip_account = SipAccount.where({ :id => params[:sip_account].to_i }).first + end + + if ! @sip_account and ! params[:phonenumber].blank? + @sip_account = SipAccount.where(:auth_name => params[:phonenumber]).first + end + + if ! params[:type].blank? + @type = params[:type] + elsif ! params[:tab].blank? + @type = params[:tab].rpartition("_")[2] + end + + if ! ['dialed', 'missed', 'received'].include? @type + @type = 'missed' + end + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}" + + @phone_xml_object = { + :name => "menu_list", + :columns => 1, + :url => base_url, + :make_call => (function.to_s == 'dial' ? number.to_s : nil), + :hidden => {:sip_account => @sip_account.id, :type => @type}, + :commands => [{ + :type => 'SELECT', + :label => 'Dial', + :display_on => 'LISTITEM', + :key => 'function', + :value => 'dial', + }], + :entries => [], + } + + if function.to_s == 'clear_notification' + @sip_account.call_histories.update_all({:read_flag => true}) + end + + last_missed_call = @sip_account.call_histories.where(:entry_type => 'missed').order('start_stamp DESC').first + if last_missed_call and !last_missed_call.read_flag + @phone_xml_object[:led] = true + else + @phone_xml_object[:led] = false + end + + calls = @sip_account.call_histories.where(:entry_type => @type).order('start_stamp DESC').limit(MAX_DIRECTORY_ENTRIES) + + if @type == 'missed' && @phone_xml_object[:led] == true + @phone_xml_object[:commands].push({ + :type => 'SELECT', + :label => 'Clear Notification', + :key => 'function', + :value => 'clear_notification', + }) + end + + auto_reload_time = 60 + + SIEMENS_HISTORY_RELOAD_TIMES.each_pair do |time_range, reload_value| + if time_range === Time.now.localtime.hour + auto_reload_time = reload_value + end + end + + @phone_xml_object[:commands].push({ + :type => 'UPDATE', + :auto => auto_reload_time, + :label => 'Update', + }) + + calls.each do |call| + display_name = call.display_name + phone_number = call.display_number + phone_book_entry = call.phone_book_entry_by_number(phone_number) + if display_name.blank? + display_name = phone_book_entry.to_s + end + + @phone_xml_object[:entries].push({ + :selected => false, + :key => 'number', + :value => phone_number, + :text => "#{call_date_compact(call.start_stamp)} #{display_name} #{phone_number}", + }) + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + end + + def call_forwarding + if ! params[:type].blank? + @type = params[:type] + end + + if ! params[:function].blank? + @function = params[:function] + end + + if ! params[:id].blank? + @call_forwarding_id = params[:id].to_i + end + + if ! params[:sip_account].blank? + @sip_account = SipAccount.where({ :id => params[:sip_account].to_i }).first + end + + if ! params[:account].blank? + @sip_account = SipAccount.where({ :id => params[:account].to_i }).first + end + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if @function == 'toggle' + if @call_forwarding_id + call_forwarding = @sip_account.call_forwards.where(:id => @call_forwarding_id).first + + if !call_forwarding and @sip_account.softkeys.where(:call_forward_id => @call_forwarding_id).count > 0 + call_forwarding = CallForward.where(:id => @call_forwarding_id).first + end + + if call_forwarding + call_forwarding.toggle + end + elsif @type + call_forwarding = @sip_account.call_forwarding_toggle(@type) + end + if !call_forwarding + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if !call_forwarding.errors.blank? + error_messages = Array.new() + call_forwarding.errors.messages.each_pair do |key, message| + error_messages.push(message.join(';')) + end + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + elsif call_forwarding.active + render( + :status => 200, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + else + render( + :status => 200, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + end + return + end + + base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}" + + @phone_xml_object = { + :name => "number_list", + :columns => 1, + :url => base_url, + :hidden => {:sip_account => @sip_account.id, :type => @type}, + :entries => [] + } + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render + } + } + end + + + def hunt_group + if ! params[:goto].blank? + redirect_to params[:goto] + return; + end + + if ! params[:function].blank? + @function = params[:function] + end + + if ! params[:group].blank? + @hunt_group = HuntGroup.where({ :id => params[:group].to_i }).first + end + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ! params[:account].blank? + hunt_group_member = @hunt_group.hunt_group_members.where({ :id => params[:account].to_i }).first + end + + base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}" + + if @function == 'toggle' + if ! hunt_group_member + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if hunt_group_member.can_switch_status_itself == true + if hunt_group_member.active + hunt_group_member.active = false + else + hunt_group_member.active = true + end + + if ! hunt_group_member.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + else + render( + :status => 200, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + end + return + end + elsif @function == 'members' + commands = [{ + :type => 'UPDATE', + :auto => 20, + :label => 'Update', + },{ + :type => 'BACK', + :label => 'Back', + :display_on => 'OPTIONS', + },{ + :type => 'EXIT', + :label => 'Exit', + :display_on => 'OPTIONS', + },{ + :type => 'SELECT', + :label => 'Show', + :display_on => 'LISTITEM', + }] + + @phone_xml_object = { + :name => "menu_list", + :columns => 1, + :url => base_url, + :hidden => {:function => @function, :group => @hunt_group.id}, + :entries => [], + :commands => commands, + } + + @hunt_group.hunt_group_members.where(:active => true).each do |member| + @phone_xml_object[:entries].push({ + :selected => false, + :value => member.id, + :text => member.name, + }) + end + else + hunt_groups = Array.new() + phone_numbers = Array.new() + @sip_account.phone_numbers.each do |phone_number| + phone_numbers.push(phone_number.number) + assistant_call_forwardings = phone_number.call_forwards.where(:call_forward_case_id => CallForwardCase.where(:value => 'assistant').first.id) + assistant_call_forwardings.each do |assistant_call_forwarding| + if assistant_call_forwarding.call_forwardable_type == 'HuntGroup' && assistant_call_forwarding.call_forwardable_id.to_i > 0 + hunt_groups.push(assistant_call_forwarding.call_forwardable_id.to_i) + end + end + end + + hunt_group_members = Array.new() + if phone_numbers.length > 0 + hunt_group_members = PhoneNumber.where(:phone_numberable_type => 'HuntGroupMember', :number => phone_numbers) + end + + hunt_group_members.each do |hunt_group| + hunt_groups.push(hunt_group.phone_numberable.hunt_group_id) + end + + hunt_groups = HuntGroup.where(:id => hunt_groups) + + @phone_xml_object = { + :name => "menu_list", + :columns => 1, + :url => base_url, + :hidden => {:function => 'members'}, + :entries => [], + :commands => [{ + :type => 'EXIT', + :label => 'Exit', + :display_on => 'OPTIONS', + },{ + :type => 'BACK', + :label => 'Back', + :display_on => 'OPTIONS', + },{ + :type => 'SELECT', + :label => 'Select', + :display_on => 'LISTITEM', + }], + } + + hunt_groups.each do |hunt_group| + @phone_xml_object[:entries].push({ + :selected => false, + :key => 'group', + :value => hunt_group.id, + :text => hunt_group.name, + }) + end + + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + end + + + def menu + if ! @phone or ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + type = 'menu' + if ! params[:type].blank? + type = params[:type] + elsif ! params[:tab].blank? + tab = params[:tab].rpartition("_") + if tab[1] != '' + type = tab[2] + end + end + + if ! params[:item].blank? + item = params[:item] + end + + menu_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}" + base_url = "#{request.protocol}#{request.host_with_port}/config_siemens/#{@phone.id}" + + @phone_xml_object = { + :name => "menu_list", + :columns => 1, + :url => menu_url, + :hidden => {:type => type}, + :entries => [] + } + + case type + when 'menu' + items = [ + { + :value => 'phone_directory', + :text => "Directory", + :url => "#{menu_url}?type=#{type}", + },{ + :value => 'call_history', + :text => "Call History", + :url => "#{menu_url}?type=call_history", + }, + ] + when 'status' + items = [ + { + :value => 'hunt_group', + :text => "Hunt Group", + :url => "#{base_url}/hunt_group.xml" + }, + ] + + commands = [ + { + :type => 'UPDATE', + :auto => 10, + :label => 'Update', + } + ] + when 'help' + items = [ + { + :key => 'item', + :value => 'help', + :text => "Help", + :url => "#{menu_url}?type=#{type}", + }, + ] + when 'call_history' + items = [ + { + :value => 'missed', + :text => "Missed", + :url => "#{base_url}/call_history.xml?type=missed", + },{ + :value => 'dialed', + :text => "Dialed", + :url => "#{base_url}/call_history.xml?type=dialed", + },{ + :value => 'received', + :text => "Received", + :url => "#{base_url}/call_history.xml?type=received", + }, + ] + end + + if item + items.each do |entry| + if entry[:value] == item + redirect_to entry[:url] + return; + end + end + end + + @phone_xml_object[:entries] = items + @phone_xml_object[:commands] = commands + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + end + + def call_date_compact(date) + if date.strftime('%Y%m%d') == DateTime::now.strftime('%Y%m%d') + return date.strftime('%H:%M') + end + return date.strftime('%d.%m %H:%M') + end +end diff --git a/app/controllers/config_siemens_sort_controller.rb b/app/controllers/config_siemens_sort_controller.rb new file mode 100644 index 0000000..c0739e5 --- /dev/null +++ b/app/controllers/config_siemens_sort_controller.rb @@ -0,0 +1,371 @@ +require 'nokogiri' +#doc.search('Message/ItemList').each do |a| puts a.children end +class ConfigSiemensController < ApplicationController +#TODO Authentication + # No access for admins though as this contains personal data. + + # We can't use load_and_authorize_resource() here because + # ConfigSiemensController isn't a resource. + # We can try client certificates + + skip_authorization_check + + + def index + os40_keys=7 + os60_keys=8 + os80_keys=9 + doc = Nokogiri::XML(request.body.read) + #logger.debug("#{params[:WorkpointMessage].to_xml}") + #logger.debug("#{params[:WorkpointMessage][:Message][:ItemList].to_xml}") + @phone_items=Hash.new + contact_reason = params[:WorkpointMessage][:Message][:ReasonForContact] + reply_status = doc.search('Message/ReasonForContact').first[:status] + reply_action = doc.search('Message/ReasonForContact').first[:action] + + doc.search('Message/ItemList/Item').each do |post_item| + @phone_items[post_item[:name]]=post_item.children.to_s + end + + mac_address = @phone_items['mac-addr'] + phone_type = @phone_items['device-type'] + if phone_type == "OpenStage 40" + max_keys = (os40_keys) * 2 + elsif phone_type == "OpenStage 60" + max_keys = (os60_keys) * 2 + elsif phone_type == "OpenStage 80" + max_keys = (os80_keys) * 2 + else + max_keys = 0 + end + + blf_keys_max = max_keys / 2 + shift_key_position = blf_keys_max - 1 + + #logger.debug(request.body.read) + @phone = Phone.find_by_mac_address(mac_address.gsub(':','').upcase) + if ! @phone.nil? + @phone.update_attributes(:ip_address => request.remote_ip) + sip_account = SipAccount.where(:sip_accountable_type == @phone.phoneable_type, + :sip_accountable_id == @phone.phoneable_id).first + end + + if ! @phone.nil? && ! sip_account.nil? + #logger.debug(@phone_items) + @my_nonce = params[:WorkpointMessage][:Message][:nonce] + @new_settings = Array.new + + @new_settings << ['dhcp', nil, 'true'] + @new_settings << ['hostname', nil, mac_address.gsub(':', '') ] + @new_settings << ['e164-hostname', nil, 'false'] + @new_settings << ['mobility-enabled', nil, 'false'] + @new_settings << ['mobility-password-on-logoff', nil, 'false'] + @new_settings << ['e164', nil, sip_account.try(:phone_numbers).first.number] + @new_settings << ['sip-user-id', nil, sip_account.auth_name] + @new_settings << ['reg-id', nil, sip_account.auth_name] + @new_settings << ['reg-number', nil, sip_account.auth_name] + @new_settings << ['fully-qualified-phone-no', nil, sip_account.auth_name] + @new_settings << ['sip-pwd', nil, sip_account.password] + @new_settings << ['sip-name', nil, sip_account.caller_name] + @new_settings << ['register-by-name', nil, 'false'] + #OPTIMIZE Display ID ? + @new_settings << ['display-id', nil, sip_account.try(:phone_numbers).first.number] + @new_settings << ['display-id-unicode', nil, sip_account.caller_name] + @new_settings << ['use-display-id', nil, 'true'] + @new_settings << ['reg-addr', nil, sip_account.sip_domain.host] + @new_settings << ['reg-port', nil, '5060'] + @new_settings << ['registrar-addr', nil, sip_account.sip_domain.host] + @new_settings << ['registrar-port', nil, '5060'] + @new_settings << ['outbound-proxy', nil, sip_account.sip_domain.host] + @new_settings << ['outbound-proxy-user', nil, sip_account.sip_domain.host] + @new_settings << ['sgnl-gateway-addr', nil, sip_account.sip_domain.host] + @new_settings << ['sgnl-gateway-addr-user', nil, sip_account.sip_domain.host] + @new_settings << ['sgnl-gateway-port', nil, '5060' ] + @new_settings << ['sgnl-gateway-port-user', nil, '5060'] + @new_settings << ['sgnl-route', nil, '0' ] + @new_settings << ['mwi-e164', nil, '' ] + @new_settings << ['rtp-base-port', nil, '5004'] + @new_settings << ['default-domain', nil, '' ] + @new_settings << ['sip-transport', nil, '0' ] + @new_settings << ['sip-transport-user', nil, '0' ] + @new_settings << ['server-type', nil, '0' ] + @new_settings << ['session-timer', nil, 'true'] + @new_settings << ['session-duration', nil, '3600' ] + @new_settings << ['reg-ttl', nil, '3600' ] + @new_settings << ['realm', nil, sip_account.sip_domain.realm] + @new_settings << ['emergency-e164', nil, '0110' ] + @new_settings << ['voice-mail-e164', nil, 'voicemail'] + @new_settings << ['auto-answer', nil, 'false'] + @new_settings << ['beep-on-auto-answer', nil, 'true'] + @new_settings << ['auto-reconnect', nil, 'false' ] + @new_settings << ['beep-on-auto-reconnect', nil, 'true'] + @new_settings << ['permit-decline-call', nil, 'true'] + @new_settings << ['transfer-on-ring', nil, 'false' ] + @new_settings << ['join-allowed-in-conference', nil, 'true'] + @new_settings << ['pickup-group-uri', nil, '*8*'] + @new_settings << ['pickup-group-uri', nil, '' ] + @new_settings << ['hot-line-warm-line-digits', nil, '' ] + @new_settings << ['initial-digit-timer', nil, '30' ] + @new_settings << ['conference-factory-uri', nil, ''] + @new_settings << ['callback-busy-allow', nil, 'false'] + @new_settings << ['callback-busy-code', nil, '' ] + @new_settings << ['callback-ring-allow', nil, 'false'] + @new_settings << ['callback-ring-code', nil, ''] + @new_settings << ['callback-cancel-code', nil, ''] + @new_settings << ['park-server', nil, ''] + #OPTIMIZE Callwaiting + @new_settings << ['call-waiting-enabled', nil, 'true'] + @new_settings << ['qos-layer2', nil, 'true'] + @new_settings << ['l2qos-voice', nil, '5' ] + @new_settings << ['l2qos-signalling', nil, '3' ] + @new_settings << ['l2qos-default', nil, '0'] + @new_settings << ['qos-layer3', nil, 'true'] + @new_settings << ['l3qos-voice', nil, '46'] + @new_settings << ['l3qos-signalling', nil, '26'] + @new_settings << ['vlan-method', nil, '1'] + #OPTIMIZE Timezone + @new_settings << ['sntp-tz-offset', nil, ''] + @new_settings << ['daylight-save', nil, ''] + @new_settings << ['daylight-save-minutes', nil, ''] + #OPTIMIZE Use SNMP? + @new_settings << ['snmp-trap-addr', nil, ''] + @new_settings << ['snmp-trap-port', nil, ''] + @new_settings << ['snmp-trap-pwd', nil, 'snmp' ] + @new_settings << ['snmp-traps-active', nil, 'false' ] + @new_settings << ['diagnostic-trap-addr', nil, ''] + @new_settings << ['diagnostic-trap-port', nil, ''] + @new_settings << ['diagnostic-trap-pwd', nil, 'snmp' ] + @new_settings << ['diagnostic-traps-active', nil, 'false' ] + @new_settings << ['diagnostic-snmp-active', nil, 'false'] + @new_settings << ['qdc-collection-unit-addr', nil, ''] + @new_settings << ['qdc-collection-unit-port', nil, '12010'] + + @new_settings << ['qdc-trap-pwd', nil, 'QOSDC'] + @new_settings << ['qdc-snmp-active', nil, 'false'] + @new_settings << ['qdc-qcu-active', nil, 'false'] + @new_settings << ['snmp-queries-allowed', nil, 'false'] + @new_settings << ['snmp-pwd', nil, ''] + @new_settings << ['disable-microphone', nil, 'false'] + @new_settings << ['loudspeech-enabled', nil, 'true'] + @new_settings << ['audio-silence-suppression', nil, 'false'] + + @new_settings << ['port1', nil, '0' ] # 0=Automatic (speed) + @new_settings << ['port2', nil, '0' ] + @new_settings << ['port2-mode', nil, '1' ] + @new_settings << ['port2-auto-mdix-enabled', nil, 'true' ] + @new_settings << ['originating-line-preference', nil, '0'] + @new_settings << ['terminating-line-preference', nil, '0'] + @new_settings << ['line-key-operating-mode', nil, '0'] + @new_settings << ['line-rollover-type', nil, '2'] + @new_settings << ['line-rollover-volume', nil, '5' ]# 1-5 + @new_settings << ['line-registration-leds', nil, 'true'] + @new_settings << ['keyset-use-focus', nil, 'true' ] + @new_settings << ['keyset-remote-forward-ind', nil, 'true'] + @new_settings << ['keyset-reservation-timer', nil, '60' ] # 0-300 + @new_settings << ['dial-plan-enabled', nil, '' ] + @new_settings << ['Canonical-dialing-international-prefix', nil, ''] + @new_settings << ['Canonical-dialing-local-country-code', nil, ''] + @new_settings << ['Canonical-dialing-national-prefix', nil, ''] + @new_settings << ['Canonical-dialing-local-area-code', nil, ''] + @new_settings << ['Canonical-dialing-local-node', nil, ''] + @new_settings << ['Canonical-dialing-external-access', nil, '0'] + @new_settings << ['Canonical-dialing-operator-code', nil, ''] + @new_settings << ['Canonical-dialing-emergency-number', nil, ''] + @new_settings << ['Canonical-dialing-dial-needs-access-code', nil, '0'] + @new_settings << ['Canonical-dialing-dial-needs-intGWcode', nil, '0'] + @new_settings << ['Canonical-dialing-min-local-number-length', nil, '10'] + @new_settings << ['Canonical-dialing-extension-initial-digits', nil, ''] + @new_settings << ['Canonical-dialing-dial-internal-form', nil, '0' ] + @new_settings << ['Canonical-dialing-dial-external-form', nil, '0' ] + @new_settings << ['Canonical-lookup-local-code', nil, '' ] + @new_settings << ['Canonical-lookup-international-code', nil, ''] + @new_settings << ['hot-keypad-dialing', nil, ''] + @new_settings << ['ldap-transport', nil, '0'] + @new_settings << ['ldap-server-address', nil, '' ] + @new_settings << ['ldap-server-port', nil, '389' ] + @new_settings << ['ldap-authentication', nil, '1'] + @new_settings << ['ldap-user', nil, '' ] + @new_settings << ['ldap-pwd', nil, '' ] + @new_settings << ['ldap-max-responses', nil, '25'] + @new_settings << ['backup-addr', nil, ''] + @new_settings << ['backup-registration', nil, 'false'] + @new_settings << ['qdc-qcu-active', nil, 'false' ] + @new_settings << ['min-admin-passw-length', nil, '6' ] + @new_settings << ['default-locked-config-menus', nil, 'true' ] + @new_settings << ['locked-config-menus', nil, 'true' ] + @new_settings << ['default-locked-local-function-menus', nil, 'true' ] + @new_settings << ['locked-local-function-menus', nil, 'true' ] + @new_settings << ['dls-mode-secure', nil, '0' ] + @new_settings << ['dls-chunk-size', nil, '9492'] + @new_settings << ['default-passw-policy', nil, 'false'] + @new_settings << ['deflect-destination', nil, ''] + @new_settings << ['display-skin', nil, ''] + @new_settings << ['enable-bluetooth-interface', nil, 'true'] + @new_settings << ['usb-access-enabled', nil, 'false' ] + @new_settings << ['usb-backup-enabled', nil, 'false' ] + @new_settings << ['line-button-mode', nil, '0' ] + @new_settings << ['lock-forwarding', nil, '' ] + @new_settings << ['loudspeaker-function-mode', nil, '0' ] + @new_settings << ['max-pin-retries', nil, '' ] + @new_settings << ['inactivity-timeout', nil, '30' ] + @new_settings << ['not-used-timeout', nil, '2' ] + @new_settings << ['passw-char-set', nil, '0' ] + @new_settings << ['refuse-call', nil, 'true' ] + @new_settings << ['restart-password', nil, ''] + #OPTIMIZE clock format + @new_settings << ['time-format', nil, '0' ]# 1=12 h + @new_settings << ['uaCSTA-enabled', nil, 'false' ] + @new_settings << ['enable-test-interface', nil, 'false'] + @new_settings << ['enable-WBM', nil, 'true'] + @new_settings << ['pixelsaver-timeout', nil, '2' ]# 2 hours? + @new_settings << ['voice-message-dial-tone', nil, '' ] + @new_settings << ['call-pickup-allowed', nil, 'true' ] + @new_settings << ['group-pickup-tone-allowed', nil, 'true'] + @new_settings << ['group-pickup-as-ringer', nil, 'false'] + @new_settings << ['group-pickup-alert-type', nil, '0' ] + @new_settings << ['default-profile', nil, '' ] + @new_settings << ['count-medium-priority', nil, '5'] + @new_settings << ['timer-medium-priority', nil, '60'] # 1 - 999 + @new_settings << ['timer-high-priority', nil, '5'] # 0 - 999 + @new_settings << ['dss-sip-detect-timer', nil, '10'] + @new_settings << ['dss-sip-deflect', nil, 'false' ] + @new_settings << ['dss-sip-refuse', nil, 'false' ] + @new_settings << ['feature-availability', nil, 'false'] + @new_settings << ['feature-availability', nil, 'true' ] + @new_settings << ['feature-availability', nil, 'true' ] + @new_settings << ['local-control-feature-availability', nil, 'false' ] + @new_settings << ['trace-level', nil, '0' ] # Off + @new_settings << ['default-locked-function-keys', nil, 'true' ]# "unknown item" + #OPTIMIZE Put pickup prefix into database/global constant? + @new_settings << ['blf-code', nil, 'f_ia_'] # pickup prefix for softkey function 59 (BLF) + @new_settings << ['stimulus-feature-code', nil, true] + @new_settings << ['stimulus-led-control-uri', nil, true] + + + @new_settings << ['min-user-passw-length', nil, '6' ]# 6 - 24 + #OPTIMIZE language + @new_settings << ['country-iso', nil, 'DE' ] + @new_settings << ['language-iso', nil, 'de'] + @new_settings << ['date-format', nil, '0' ] # DD.MM.YYYY + #OPTIMIZE ringtones + @new_settings << ['ringer-melody', nil, '1'] + + @new_settings << ['ringer-melody', nil, '2'] + @new_settings << ['ringer-tone-sequence', nil, '2'] + + soft_keys = Array.new + # Getting BLF keys only for the first level + blf_keys = sip_account.softkeys.find( + :all, + :conditions => {:function => ['blf', 'conference']}, + :limit => blf_keys_max) + #Getting other keys + non_blf_keys = sip_account.softkeys.find( + :all, + :conditions => {:function => ['speed_dial']}) + + # Fill softkey array with BLF keys up to shift key + blf_keys.each do |k| + soft_keys << k + end + # Fill sofkey with other keys up to end + non_blf_keys.each do |k| + if soft_keys.length < max_keys + soft_keys << k + end + end + # Delete unset softkeys + while soft_keys.length < max_keys + soft_keys << Softkey.new + end + + key_pos=1 + + #soft_keys.each do |sk| + + while key_pos < shift_key_position + + (1..shift_key_position-1).each do |key_idx| + sk = soft_keys.shift + logger.debug(sk.function, key_idx) + if sk.function == "blf" + @new_settings << ['function-key-def', key_idx, '59'] + @new_settings << ['select-dial', key_idx, sk.number ] + elsif sk.function == "log_out" + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, 'f_lo' ] + elsif sk.function == "log_in" + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, "f_li_#{sk.number}" ] + elsif sk.function == "dtmf" + @new_settings << ['function-key-def', key_idx, '54'] + @new_settings << ['stimulus-DTMF-sequence', key_idx, sk.number ] + elsif sk.function.nil? + @new_settings << ['function-key-def', key_idx, '0'] + else + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, sk.number ] + end + @new_settings << ['key-label', key_idx, sk.label ] + @new_settings << ['key-label-unicode', key_idx, sk.label ] + key_pos = key_pos+1 + + end + end + if key_pos == shift_key_position + @new_settings << ['function-key-def', shift_key_position, '18'] + @new_settings << ['key-label', shift_key_position, 'Shift'] + @new_settings << ['key-label-unicode', shift_key_position, 'Shift'] + key_pos = key_pos+1 + end + + (1001..1000+shift_key_position-1).each do |key_idx| + sk = soft_keys.shift + if sk.function == "log_out" + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, 'f_lo' ] + elsif sk.function == "log_in" + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, "f_li_#{sk.number}" ] + elsif sk.function == "dtmf" + @new_settings << ['function-key-def', key_idx, '54'] + @new_settings << ['stimulus-DTMF-sequence', key_idx, sk.number ] + elsif sk.function.nil? + @new_settings << ['function-key-def', key_idx, '0'] + else + @new_settings << ['function-key-def', key_idx, '1'] + @new_settings << ['select-dial', key_idx, sk.number ] + end + @new_settings << ['key-label', key_idx, sk.label ] + @new_settings << ['key-label-unicode', key_idx, sk.label ] + key_pos = key_pos+1 + + end + + #end + logger.debug(@new_settings) + end + + if @phone.nil? || sip_account.nil? + respond_to { |format| + format.xml { render :action => "clean-up" } + } + + elsif (reply_status == 'accepted' && contact_reason == 'reply-to' && reply_action == 'ReadAllItems') + respond_to { |format| + format.xml { render :action => "write" } + } + + elsif ["reply-to"].include? contact_reason + respond_to { |format| + format.xml { render :action => "clean-up" } + } + + else + respond_to { |format| + format.xml { render :action => "index" } + } + end + + end +end diff --git a/app/controllers/config_snom_controller.rb b/app/controllers/config_snom_controller.rb new file mode 100644 index 0000000..40f0c4e --- /dev/null +++ b/app/controllers/config_snom_controller.rb @@ -0,0 +1,1169 @@ +class ConfigSnomController < ApplicationController + MAX_SIP_ACCOUNTS_COUNT = 11 + MAX_SOFTKEYS_COUNT = 12 + (42 * 3) - 1 + MAX_DIRECTORY_ENTRIES = 20 + KEYPAD_TO_CHAR = { + '0' => [' ','-','.',',','0'], + '1' => [' ','-','.',',','1'], + '2' => ['a','b','c','2'], + '3' => ['d','e','f','3'], + '4' => ['g','h','i','4'], + '5' => ['j','k','l','5'], + '6' => ['m','n','o','6'], + '7' => ['p','q','r','s','7'], + '8' => ['t','u','v','8'], + '9' => ['w','x','y','z','9'], + } + + skip_authorization_check + + before_filter { |controller| + @mac_address = params[:mac_address].to_s.upcase.gsub(/[^0-9A-F]/,'') + @provisioning_authenticated = false + + if !params[:provisioning_key].blank? + @phone = Phone.where({ :provisioning_key => params[:provisioning_key] }).first + if @phone + @provisioning_authenticated = true + @mac_address = @phone.mac_address + end + end + + if ! @mac_address.blank? then + if !@phone + @phone = Phone.where({ :mac_address => @mac_address }).first + end + + if ! @phone && PROVISIONING_AUTO_ADD_PHONE + tenant = Tenant.where(:id => PROVISIONING_AUTO_TENANT_ID).first + if ! tenant + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + @phone = tenant.phones.build + @phone.mac_address = @mac_address + @phone.hot_deskable = true + + mac_address_to_model = { + '00041325' => 'Snom 300', + '00041328' => 'Snom 300', + '0004132D' => 'Snom 300', + '0004132F' => 'Snom 300', + '00041334' => 'Snom 300', + '00041350' => 'Snom 300', + '0004133B' => 'Snom 300', + '00041337' => 'Snom 300', + '00041324' => 'Snom 320', + '00041327' => 'Snom 320', + '0004132C' => 'Snom 320', + '00041331' => 'Snom 320', + '00041335' => 'Snom 320', + '00041338' => 'Snom 320', + '00041351' => 'Snom 320', + '00041323' => 'Snom 360', + '00041329' => 'Snom 360', + '0004132B' => 'Snom 360', + '00041339' => 'Snom 360', + '00041390' => 'Snom 360', + '00041326' => 'Snom 370', + '0004132E' => 'Snom 370', + '0004133A' => 'Snom 370', + '00041352' => 'Snom 370', + '00041340' => 'Snom 820', + '00041345' => 'Snom 821', + '00041348' => 'Snom 821', + '00041341' => 'Snom 870', + } + + @phone.phone_model = PhoneModel.where(:name => mac_address_to_model[@mac_address[0, 8]]).first + if ! @phone.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ! PROVISIONING_AUTO_ADD_SIP_ACCOUNT + return + end + + caller_name_index = 0 + sip_account_last = tenant.sip_accounts.where('caller_name LIKE ?', "#{PROVISIONING_AUTO_SIP_ACCOUNT_CALLER_PREFIX}%").sort { |item1, item2| + item1.caller_name.gsub(/[^0-9]/, '').to_i <=> item2.caller_name.gsub(/[^0-9]/, '').to_i + }.last + + if sip_account_last + caller_name_index = sip_account_last.caller_name.gsub(/[^0-9]/, '').to_i + end + caller_name_index = caller_name_index + 1 + + @sip_account = tenant.sip_accounts.build + @sip_account.caller_name = "#{PROVISIONING_AUTO_SIP_ACCOUNT_CALLER_PREFIX}#{caller_name_index}" + @sip_account.call_waiting = CALL_WAITING + @sip_account.clir = DEFAULT_CLIR_SETTING + @sip_account.clip = DEFAULT_CLIP_SETTING + @sip_account.voicemail_pin = random_pin + @sip_account.callforward_rules_act_per_sip_account = CALLFORWARD_RULES_ACT_PER_SIP_ACCOUNT_DEFAULT + @sip_account.hotdeskable = false + loop do + @sip_account.auth_name = SecureRandom.hex(DEFAULT_LENGTH_SIP_AUTH_NAME) + break unless SipAccount.exists?(:auth_name => @sip_account.auth_name) + end + @sip_account.password = SecureRandom.hex(DEFAULT_LENGTH_SIP_PASSWORD) + + if ! @sip_account.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + phone_sip_account = PhoneSipAccount.new() + phone_sip_account.phone_id = @phone.id + phone_sip_account.sip_account_id = @sip_account.id + + if ! phone_sip_account.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + end + elsif ! params[:phone].blank? then + @phone = Phone.where({ :id => params[:phone].to_i }).first + end + + if ! @phone + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + end + + if ! params[:sip_account].blank? + @sip_account = @phone.sip_accounts.where({ :id => params[:sip_account].to_i }).first + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + end + end + + if ! params[:type].blank? + @type = params[:type].to_s.strip.downcase + end + + if ! params[:keys].blank? + @dialpad_keys = params[:keys].to_s.strip + end + } + + + def show + send_sensitve = @provisioning_authenticated || !@phone.provisioning_key_active + @phone_settings = Hash.new() + + if defined?(PROVISIONING_KEY_LENGTH) && PROVISIONING_KEY_LENGTH > 0 + if @phone.provisioning_key.blank? + @phone.update_attributes({ :provisioning_key => SecureRandom.hex(PROVISIONING_KEY_LENGTH), :provisioning_key_active => false }) + elsif @provisioning_authenticated + @phone.update_attributes({ :provisioning_key_active => true }) + end + + if send_sensitve + if defined?(PROVISIONING_PROTOCOL) && PROVISIONING_PROTOCOL + provisioning_protocol = PROVISIONING_PROTOCOL + else + provisioning_protocol = request.protocol + end + @phone_settings[:setting_server] = "#{provisioning_protocol}#{request.host_with_port}/snom-#{@phone.provisioning_key}.xml" + end + end + + if defined?(PROVISIONING_SET_HTTP_USER) && @phone.http_user.blank? + if PROVISIONING_SET_HTTP_USER.class == Fixnum + @phone.update_attributes({ :http_user => SecureRandom.hex(PROVISIONING_SET_HTTP_USER) }) + elsif PROVISIONING_SET_HTTP_USER.class == String + @phone.update_attributes({ :http_user => PROVISIONING_SET_HTTP_USER }) + end + end + + if defined?(PROVISIONING_SET_HTTP_PASSWORD) && @phone.http_password.blank? + if PROVISIONING_SET_HTTP_PASSWORD.class == Fixnum + @phone.update_attributes({ :http_password => SecureRandom.hex(PROVISIONING_SET_HTTP_PASSWORD) }) + elsif PROVISIONING_SET_HTTP_PASSWORD.class == String + @phone.update_attributes({ :http_password => PROVISIONING_SET_HTTP_PASSWORD }) + end + end + + if send_sensitve + @phone_settings[:http_user] = @phone.http_user + @phone_settings[:http_pass] = @phone.http_password + if defined?(PROVISIONING_ADMIN_PASSWORD) + if PROVISIONING_ADMIN_PASSWORD.class == TrueClass + @phone_settings[:admin_mode_password] = @phone.http_password + elsif PROVISIONING_ADMIN_PASSWORD.class == String + @phone_settings[:admin_mode_password] = PROVISIONING_ADMIN_PASSWORD + end + end + end + + if ! request.env['HTTP_USER_AGENT'].index('snom') + Rails.logger.info "---> User-Agent indicates not a Snom phone (#{request.env['HTTP_USER_AGENT'].inspect})" + else + Rails.logger.info "---> Phone #{@mac_address.inspect}, IP address #{request.remote_ip.inspect}" + @phone.update_attributes({ :ip_address => request.remote_ip }) + end + + @softkeys = Array.new() + @sip_accounts = Array.new() + + if send_sensitve + @phone.sip_accounts.each do |sip_account| + if (sip_account.sip_accountable_type == @phone.phoneable_type) and (sip_account.sip_accountable_id == @phone.phoneable_id) + snom_sip_account = { + :id => sip_account.id, + :active => 'on', + :pname => sip_account.auth_name, + :pass => sip_account.password, + :host => sip_account.host, + :outbound => sip_account.host, + :name => sip_account.auth_name, + :realname => 'Call', + :idle_text => sip_account.caller_name, + :mailbox => "" + } + @sip_accounts.push(snom_sip_account) + sip_account_index = @sip_accounts.length + sip_account.softkeys.order(:position).each do |softkey| + if softkey.softkey_function + softkey_function = softkey.softkey_function.name + end + case softkey_function + when 'blf' + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "blf |f-ia-"}) + when 'speed_dial' + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "speed #{softkey.number}"}) + when 'dtmf' + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "dtmf #{softkey.number}"}) + when 'log_out' + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "speed f-lo"}) + when 'log_in' + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "speed f-li-#{softkey.number}"}) + when 'conference' + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "blf |f-ta-"}) + when 'call_forwarding' + @softkeys.push({ + :context => sip_account_index, + :function => softkey.function, + :label => softkey.label, + :softkey => softkey, + :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"), + :subscription => { + :to => "f-cftg-#{softkey.call_forward_id}@#{sip_account.host}", + :for => "#{sip_account.auth_name}@#{sip_account.host}" + }, + :actions => [{ + :type => :url, + :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/call_forwarding.xml?id=#{softkey.call_forward_id}&function=toggle", + :when => 'on press', + }], + }) + when 'call_forwarding_always' + phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'SipAccount').first + if phone_number + account_param = (phone_number.phone_numberable_id != snom_sip_account[:id] ? "&account=#{phone_number.phone_numberable_id}" : '') + else + phone_number = sip_account.phone_numbers.first + account_param = '' + end + + @softkeys.push({ + :context => sip_account_index, + :function => softkey.function, + :label => softkey.label, + :softkey => softkey, + :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"), + :subscription => { + :to => "f-cfutg-#{phone_number.id}@#{sip_account.host}", + :for => "#{sip_account.auth_name}@#{sip_account.host}" + }, + :actions => [{ + :type => :url, + :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/call_forwarding.xml?type=always&function=toggle#{account_param}", + :when => 'on press', + }], + }) + when 'call_forwarding_assistant' + phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'SipAccount').first + if phone_number + account_param = (phone_number.phone_numberable_id != snom_sip_account[:id] ? "&account=#{phone_number.phone_numberable_id}" : '') + else + phone_number = sip_account.phone_numbers.first + account_param = '' + end + + @softkeys.push({ + :context => sip_account_index, + :function => softkey.function, + :label => softkey.label, + :softkey => softkey, + :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"), + :subscription => { + :to => "f-cfatg-#{phone_number.id}@#{sip_account.host}", + :for => "#{sip_account.auth_name}@#{sip_account.host}" + }, + :actions => [{ + :type => :url, + :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/call_forwarding.xml?type=assistant&function=toggle#{account_param}", + :when => 'on press', + }], + }) + when 'hunt_group_membership' + phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'HuntGroup').first + if phone_number + hunt_group = HuntGroup.where(:id => phone_number.phone_numberable_id).first + end + + sip_account_phone_numbers = Array.new() + SipAccount.where(:id => @sip_accounts.first[:id]).first.phone_numbers.each do |phone_number| + sip_account_phone_numbers.push(phone_number.number) + end + + hunt_group_member_numbers = PhoneNumber.where(:number => sip_account_phone_numbers, :phone_numberable_type => 'HuntGroupMember') + + hunt_group_member = nil + if hunt_group and hunt_group_member_numbers + hunt_group_member_numbers.each do |hunt_group_member_number| + hunt_group_member = hunt_group.hunt_group_members.where(:id => hunt_group_member_number.phone_numberable_id).first + if hunt_group_member + break + end + end + end + + if hunt_group_member + @softkeys.push({ + :context => sip_account_index, + :function => softkey.function, + :label => softkey.label, + :softkey => softkey, + :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"), + :subscription => { + :to => "f-hgmtg-#{hunt_group_member.id}@#{sip_account.host}", + :for => "#{sip_account.auth_name}@#{sip_account.host}" + }, + :actions => [{ + :type => :url, + :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/hunt_group.xml?group=#{hunt_group.id}&account=#{hunt_group_member.id}&function=toggle", + :when => 'on press', + }], + }) + else + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => 'none'}) + end + when 'acd_membership' + acd_agent = nil + phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'AutomaticCallDistributor').first + if phone_number + acd = AutomaticCallDistributor.where(:id => phone_number.phone_numberable_id).first + if acd + acd_agent = acd.acd_agents.where(:destination_type => 'SipAccount', :destination_id => sip_account.id).first + end + end + + if acd_agent + @softkeys.push({ + :context => sip_account_index, + :function => softkey.function, + :label => softkey.label, + :softkey => softkey, + :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"), + :subscription => { + :to => "f-acdmtg-#{acd_agent.id}@#{sip_account.host}", + :for => "#{sip_account.auth_name}@#{sip_account.host}" + }, + :actions => [{ + :type => :url, + :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/acd.xml?acd=#{acd.id}&agent=#{acd_agent.id}&function=toggle", + :when => 'on press', + }], + }) + else + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => 'none'}) + end + when 'hold' + @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "keyevent F_R"}) + else + @softkeys.push({:label => softkey.label, :data => 'none'}) + end + end + end + end + end + + languages_map = { + 'ca' => 'Catalan', + 'bs' => 'Bosanski', + 'da' => 'Dansk', + 'de' => 'Deutsch', + 'cs' => 'Cestina', + 'en' => 'English', + 'es' => 'Espanol', + 'fi' => 'Suomi', + 'et' => 'Estonian', + 'fr' => 'Francais', + 'he' => 'Hebrew', + 'hu' => 'Hungarian', + 'it' => 'Italiano', + 'nl' => 'Dutch', + 'no' => 'Norsk', + 'pl' => 'Polski', + 'pt' => 'Portugues', + 'si' => 'Slovenian', + 'sk' => 'Slovencina', + 'ru' => 'Russian', + 'sv' => 'Svenska', + 'tr' => 'Turkce', + } + + tone_schemes_map = { + '1' => 'USA', # United States + '61' => 'AUS', # Australia + '43' => 'AUT', # Austria + '86' => 'CHN', # China + '45' => 'DNK', # Denmark + '33' => 'FRA', # France + '49' => 'GER', # Germany + '44' => 'GBR', # Great Britain + '91' => 'IND', # India + '39' => 'ITA', # Italy + '81' => 'JPN', # Japan + '52' => 'MEX', # Mexico + '31' => 'NLD', # Netherlands + '47' => 'NOR', # Norway + '64' => 'NZL', # New Zealand + '34' => 'ESP', # Spain + '46' => 'SWE', # Sweden + '41' => 'SWI', # Switzerland + } + + if @phone.phoneable + if @phone.phoneable_type == 'Tenant' + tenant = @phone.phoneable + language = tenant.language.code + elsif @phone.phoneable_type == 'User' + tenant = @phone.phoneable.current_tenant + language = @phone.phoneable.language.code + end + end + + if tenant && tenant.country + tone_scheme = tenant.country.country_code + end + + @phone_settings[:tone_scheme] = tone_schemes_map.include?(tone_scheme.to_s) ? tone_schemes_map[tone_scheme.to_s] : 'USA' + @phone_settings[:language] = languages_map.include?(language.to_s) ? languages_map[language.to_s] : 'English' + + xml_applications_url = "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{(@sip_accounts.blank? ? '0' : @sip_accounts.first[:id])}" + @dkeys = { + :menu => 'keyevent F_SETTINGS', + :retrieve => 'speed f-vmcheck', + :conf => 'keyevent F_CONFERENCE', + :redial => "url #{xml_applications_url}/call_history.xml?type=dialed", + :directory => "url #{xml_applications_url}/phone_book.xml", + :idle_ok => "url #{xml_applications_url}/call_history.xml?type=dialed", + :idle_cancel => "keyevent F_CANCEL", + :idle_up => "keyevent F_PREV_ID", + :idle_down => "keyevent F_NEXT_ID", + :idle_left => "url #{xml_applications_url}/call_history.xml?type=received", + :idle_right => "url #{xml_applications_url}/call_history.xml?type=missed", + } + + # Remap conference key to first conference if found + #conference = Conference.where(:conferenceable_type => @phone.phoneable_type, :conferenceable_id => @phone.phoneable_id).first + #if conference and conference.phone_numbers + # @dkeys[:conf] = "speed f_ta_#{conference.phone_numbers.first.number}" + #end + + @sip_accounts.length().upto(MAX_SIP_ACCOUNTS_COUNT) do |index| + snom_sip_account = { + :id => index, + :active => 'off', + :pname => '', + :pass => '', + :host => '', + :outbound => '', + :name => '', + :realname => '', + :idle_text => '', + } + @sip_accounts.push(snom_sip_account) + end + + @softkeys.length().upto(MAX_SOFTKEYS_COUNT) do |index| + @softkeys.push({:label => "", :data => "none"}) + end + + @state_settings_url = "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/state_settings.xml" + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render + } + } + end + + def idle_screen + + snom_360_bg = 'Qk0+BAAAAAAAAD4AAAAoAAAAgAAAAEAAAAABAAEAAAAAAAAEAAATCwAAEwsAAAIAAAACAAAA//// +AAAAAAAAAAAAAAAAAAAAAbbZxzbbAAAAAAAAAAAAAAG222222wAAAAAAAAAAAAAB9ttttt8AAAAA +AAAAAAAAAbbbbbbbAAAAAAAAAAAAAAG222222wAAAAAAAAAAAAABttttttsAAAAAAAAAAAAAAOPx +xx+OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkkkkkkkkkkkkkkAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAACSSSSSSSSSSSSSQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAA' + + @phone_xml_object = { + :image => { + :data => snom_360_bg, + :location_x => 0, + :location_y => 0, + :invert => 0 + }, + :clock => { + :location_x => 128, + :location_y => 0, + }, + :date => { + :location_x => 100, + :location_y => 40, + }, + :line => { + :location_x => 0, + :location_y => 0, + }, + } + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render + } + } + end + + def log_in + + base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}" + exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml" + + log_in_number = params[:log_in].to_s.gsub(/[^0-9]/,'') + pin = params[:pin].to_s.gsub(/[^0-9]/,'') + + if ! params[:user].blank? + user = User.where(:id => params[:user].to_i).first + phone_number = PhoneNumber.where(:number => log_in_number, :phone_numberable_type => 'SipAccount').first + if phone_number + sip_account = phone_number.phone_numberable + end + elsif ! params[:log_in].blank? + phone_number = PhoneNumber.where(:number => log_in_number, :phone_numberable_type => 'SipAccount').first + if phone_number && phone_number.phone_numberable && phone_number.phone_numberable.sip_accountable && phone_number.phone_numberable.sip_accountable_type == 'User' + user = phone_number.phone_numberable.sip_accountable + end + end + + @phone_xml_object = { + :name => "snom_phone_text", + :title => "Error", + :prompt => "Log in", + :text => 'Log in failed!', + :fetch_url => base_url, + :fetch_mil => '2000', + } + + if ! user + @phone_xml_object = { + :name => "snom_phone_input", + :title => "Log In", + :prompt => "Log In", + :url => base_url, + :display_name => "Log In", + :query_string_param => "log_in", + :default_value => log_in_number, + :input_flags => "n", + :softkeys => [ + {:name => "F1", :label => "Exit", :url => exit_url} + ] + } + elsif pin.blank? + @phone_xml_object = { + :name => "snom_phone_input", + :title => "PIN", + :prompt => "PIN", + :url => base_url, + :display_name => "PIN", + :query_string_param => "user=#{user.id}&log_in=#{log_in_number}&pin", + :default_value => "", + :input_flags => "pn", + :softkeys => [ + {:name => "F1", :label => "Exit", :url => exit_url} + ] + } + elsif user.authenticate_by_pin?(pin) + if @phone.user_login(user, sip_account) + @phone_xml_object = { + :name => "snom_phone_text", + :title => "Log in successful", + :prompt => "Log in", + :text => "#{user.to_s} logged in", + :fetch_url => exit_url, + :fetch_mil => '1000', + } + end + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + end + + def log_out + if ! @phone + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml" + + if @phone.user_logout() + @phone_xml_object = { + :name => "snom_phone_text", + :title => "Log out successful", + :prompt => "Log out", + :text => 'Log out successful', + :fetch_url => exit_url, + :fetch_mil => '1000', + } + else + @phone_xml_object = { + :name => "snom_phone_text", + :title => "Error", + :prompt => "Log out", + :text => 'Log out failed!', + :fetch_url => exit_url, + :fetch_mil => '2000', + } + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + end + + def phone_book + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + @phone_xml_object = { + :name => 'snom_phone_directory', + :title => "$(lang:menu100_phone_book) #{@dialpad_keys}".strip, + :entries => [], + :softkeys => [], + } + + phone_books = Array.new() + phone_books = phone_books + @sip_account.sip_accountable.try(:phone_books).all + if @sip_account.sip_accountable.class == User + phone_books = phone_books + @sip_account.sip_accountable.try(:current_tenant).try(:phone_books).all + end + + phone_book_ids = Array.new() + phone_books.each do |phone_book| + phone_book_ids << phone_book.id + end + + PhoneBookEntry.where(:phone_book_id => phone_book_ids).order(:last_name).order(:first_name).limit(MAX_DIRECTORY_ENTRIES).each do |phone_book_entry| + if phone_book_entry.phone_numbers.count > 1 + @phone_xml_object[:entries] << { :text => phone_book_entry.to_s, :number => phone_book_entry.phone_numbers.first } + end + phone_book_entry.phone_numbers.each do |phone_number| + if phone_book_entry.phone_numbers.count > 1 + entry_name = " #{phone_number.name} #{phone_number.number}" + else + entry_name = "#{phone_book_entry.to_s} #{phone_number.number}" + end + + @phone_xml_object[:entries] << { :text => entry_name, :number => phone_number.number } + end + end + + base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}" + phone_book_url = "#{base_url}?type=#{@type.to_s}" + for key_id in (0..9) + @phone_xml_object[:softkeys] << {:name => key_id, :url => "#{phone_book_url}&keys=#{@dialpad_keys.to_s}#{key_id}" } + end + @phone_xml_object[:softkeys] << {:name => '*', :url => "#{phone_book_url}&keys=#{@dialpad_keys.to_s[0..-2]}" } + @phone_xml_object[:softkeys] << {:name => '#', :url => "#{phone_book_url}&keys=" } + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + + end + + def call_history + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ['dialed', 'missed', 'received'].include? @type + @phone_xml_object = { + :name => "snom_phone_directory", + :title => "$(lang:menu100_call_lists) - #{@type.to_s.camelize}", + :entries => [] + } + + if @type == 'missed' + hunt_group_member_ids = PhoneNumber.where(:phone_numberable_type => 'HuntGroupMember', :number => @sip_account.phone_numbers.map {|a| a.number}).map {|a| a.phone_numberable_id} + hunt_group_ids = HuntGroupMember.where(:id => hunt_group_member_ids, :active => true).map {|a| a.hunt_group_id} + calls = CallHistory.where('entry_type = ? AND ((call_historyable_type = "SipAccount" AND call_historyable_id = ?) OR (call_historyable_type = "HuntGroup" AND call_historyable_id IN (?)))', @type, @sip_account.id, hunt_group_ids).order('start_stamp DESC').limit(MAX_DIRECTORY_ENTRIES) + else + calls = @sip_account.call_histories.where(:entry_type => @type).order('start_stamp DESC').limit(MAX_DIRECTORY_ENTRIES) + end + + calls.each do |call| + display_name = call.display_name + phone_number = call.display_number + phone_book_entry = call.phone_book_entry_by_number(phone_number) + if display_name.blank? + display_name = phone_book_entry.to_s + end + + @phone_xml_object[:entries].push({ + :selected => false, + :number => phone_number, + :text => "#{call_date_compact(call.start_stamp)} #{display_name} #{call.display_number}", + }) + end + else + base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}" + @phone_xml_object = { + :name => 'snom_phone_menu', + :title => '$(lang:menu100_call_lists)', + :entries => [ + {:text => '$(lang:list_missed)', :url => "#{base_url}?&type=missed", :selected => false}, + {:text => '$(lang:list_taken)', :url => "#{base_url}?&type=received", :selected => false}, + {:text => '$(lang:list_dialed)', :url => "#{base_url}?&type=dialed", :selected => false}, + ] + } + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + + end + + def state_settings + @base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}" + + @sip_account_ids = Array.new() + @phone.sip_accounts.each do |sip_account| + if (sip_account.sip_accountable_type == @phone.phoneable_type) and (sip_account.sip_accountable_id == @phone.phoneable_id) + @sip_account_ids.push(sip_account.id) + end + end + + if @phone.hot_deskable + @enable_login = true + if @phone.phoneable_type != 'Tenant' + @enable_logout = true + end + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render + } + } + end + + def call_forwarding + if ! params[:type].blank? + @type = params[:type] + end + + if ! params[:function].blank? + @function = params[:function] + end + + if ! params[:id].blank? + @call_forwarding_id = params[:id].to_i + end + + if ! params[:sip_account].blank? + @sip_account = SipAccount.where({ :id => params[:sip_account].to_i }).first + end + + if ! params[:account].blank? + @sip_account = SipAccount.where({ :id => params[:account].to_i }).first + end + + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml" + + if @function == 'toggle' + if @call_forwarding_id + call_forwarding = @sip_account.call_forwards.where(:id => @call_forwarding_id).first + + if !call_forwarding and @sip_account.softkeys.where(:call_forward_id => @call_forwarding_id).count > 0 + call_forwarding = CallForward.where(:id => @call_forwarding_id).first + end + + if call_forwarding + call_forwarding.toggle + end + elsif @type + call_forwarding = @sip_account.call_forwarding_toggle(@type) + end + if !call_forwarding + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if !call_forwarding.errors.blank? + error_messages = Array.new() + call_forwarding.errors.messages.each_pair do |key, message| + error_messages.push(message.join(';')) + end + @phone_xml_object = { + :name => 'snom_phone_text', + :title => t("call_forwards.name"), + :prompt => t("call_forwards.name"), + :text => "ERROR #{error_messages.join(',')} #{call_forwarding.to_s})", + :fetch_url => exit_url, + :fetch_mil => '1000', + } + elsif call_forwarding.active + @phone_xml_object = { + :name => 'snom_phone_text', + :title => t("call_forwards.name"), + :prompt => t("call_forwards.name"), + :text => "ON #{call_forwarding.to_s})", + :fetch_url => exit_url, + :fetch_mil => '1000', + } + else + @phone_xml_object = { + :name => 'snom_phone_text', + :title => t("call_forwards.name"), + :prompt => t("call_forwards.name"), + :text => "OFF #{call_forwarding.to_s}", + :fetch_url => exit_url, + :fetch_mil => '1000', + } + end + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + end + + def hunt_group + if ! params[:function].blank? + @function = params[:function] + end + + if ! params[:sip_account].blank? + @sip_account = SipAccount.where({ :id => params[:sip_account].to_i }).first + end + + if ! params[:group].blank? + @hunt_group = HuntGroup.where({ :id => params[:group].to_i }).first + end + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ! @hunt_group + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ! params[:account].blank? + hunt_group_member = @hunt_group.hunt_group_members.where({ :id => params[:account].to_i }).first + end + + if ! hunt_group_member + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml" + + if @function == 'toggle' + if hunt_group_member.can_switch_status_itself == true + if hunt_group_member.active + hunt_group_member.active = false + else + hunt_group_member.active = true + end + + if ! hunt_group_member.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + end + + if hunt_group_member.active + @phone_xml_object = { + :name => 'snom_phone_text', + :title => 'Hunt Group', + :prompt => 'Hunt Group', + :text => "#{@hunt_group.name} on", + :fetch_url => exit_url, + :fetch_mil => '1000', + } + else + @phone_xml_object = { + :name => 'snom_phone_text', + :title => 'Hunt Group', + :prompt => 'Hunt Group', + :text => "#{@hunt_group.name} off", + :fetch_url => exit_url, + :fetch_mil => '1000', + } + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + end + end + + def acd + if ! params[:function].blank? + @function = params[:function] + end + + if ! params[:sip_account].blank? + @sip_account = SipAccount.where({ :id => params[:sip_account].to_i }).first + end + + if ! params[:acd].blank? + @acd = AutomaticCallDistributor.where({ :id => params[:acd].to_i }).first + end + + if ! @sip_account + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ! @acd + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ! params[:agent].blank? + acd_agent = @acd.acd_agents.where({ :id => params[:agent].to_i }).first + end + + if ! acd_agent + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml" + + if @function == 'toggle' + if acd_agent.status == 'active' + acd_agent.status = 'inactive' + else + acd_agent.status = 'active' + end + + if ! acd_agent.save + render( + :status => 500, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if acd_agent.status == 'active' + @phone_xml_object = { + :name => 'snom_phone_text', + :title => 'ACD', + :prompt => 'ACD', + :text => "#{@acd.name} on", + :fetch_url => exit_url, + :fetch_mil => '1000', + } + else + @phone_xml_object = { + :name => 'snom_phone_text', + :title => 'ACD', + :prompt => 'ACD', + :text => "#{@acd.name} off", + :fetch_url => exit_url, + :fetch_mil => '1000', + } + end + + respond_to { |format| + format.any { + self.formats = [ :xml ] + render :action => "_#{@phone_xml_object[:name]}" + } + } + end + end + + def exit + render( + :status => 200, + :layout => false, + :content_type => 'text/xml', + :text => "", + ) + end + + def call_date_compact(date) + if date.strftime('%Y%m%d') == DateTime::now.strftime('%Y%m%d') + return date.strftime('%H:%M') + end + return date.strftime('%d.%m %H:%M') + end + +end diff --git a/app/controllers/fax_accounts_controller.rb b/app/controllers/fax_accounts_controller.rb new file mode 100644 index 0000000..ce03bc5 --- /dev/null +++ b/app/controllers/fax_accounts_controller.rb @@ -0,0 +1,82 @@ +class FaxAccountsController < ApplicationController + load_resource :user + load_resource :user_group + load_and_authorize_resource :fax_account, :through => [:user, :user_group] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @fax_account = @parent.fax_accounts.build + @fax_account.name = generate_a_new_name(@parent, @fax_account) + @fax_account.days_till_auto_delete = DAYS_TILL_AUTO_DELETE + @fax_account.retries = DEFAULT_NUMBER_OF_RETRIES + @fax_account.station_id = @parent.to_s + @fax_account.phone_numbers.build + if @parent.class == User && !@parent.email.blank? + @fax_account.email = @parent.email + end + end + + def create + @fax_account = @parent.fax_accounts.build(params[:fax_account]) + if @fax_account.save + m = method( :"#{@parent.class.name.underscore}_fax_account_path" ) + redirect_to m.( @parent, @fax_account ), :notice => t('fax_accounts.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @fax_account.update_attributes(params[:fax_account]) + m = method( :"#{@parent.class.name.underscore}_fax_account_path" ) + redirect_to m.( @parent, @fax_account ), :notice => t('fax_accounts.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @fax_account.destroy + m = method( :"#{@parent.class.name.underscore}_fax_accounts_url" ) + redirect_to m.( @parent ), :notice => t('fax_accounts.controller.successfuly_destroyed') + end + + private + def set_and_authorize_parent + @parent = @user || @user_group + authorize! :read, @parent + end + + def spread_breadcrumbs + if @parent && @parent.class == User + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t("fax_accounts.index.page_title"), user_fax_accounts_path(@user) + if @fax_account && !@fax_account.new_record? + add_breadcrumb @fax_account, user_fax_account_path(@user, @fax_account) + end + end + + if @parent && @parent.class == UserGroup + @user_group = @parent + add_breadcrumb t("user_groups.index.page_title"), tenant_user_groups_path(@user_group.tenant) + add_breadcrumb @user_group, tenant_user_group_path(@user_group.tenant, @user_group) + add_breadcrumb t("fax_accounts.index.page_title"), user_group_fax_accounts_path(@user_group) + if @fax_account && !@fax_account.new_record? + add_breadcrumb @fax_account, user_group_fax_account_path(@user_group, @fax_account) + end + end + end + +end diff --git a/app/controllers/fax_documents_controller.rb b/app/controllers/fax_documents_controller.rb new file mode 100644 index 0000000..eff9604 --- /dev/null +++ b/app/controllers/fax_documents_controller.rb @@ -0,0 +1,82 @@ +class FaxDocumentsController < ApplicationController + load_and_authorize_resource :fax_account + load_and_authorize_resource :fax_document, :through => [:fax_account] + + before_filter :spread_breadcrumbs + + def index + @fax_documents = @fax_documents.order(:created_at).reverse_order + end + + def show + respond_to do |format| + @fax_document = FaxDocument.find(params[:id]) + format.html + format.xml { render :xml => @fax_document } + format.pdf { + caller_number = @fax_document.caller_id_number.to_s.gsub(/[^0-9]/, '') + if caller_number.blank? + caller_number = 'anonymous' + end + + if @fax_document.document.path + send_file @fax_document.document.path, :type => "application/pdf", + :filename => "#{@fax_document.created_at.strftime('%Y%m%d-%H%M%S')}-#{caller_number}.pdf" + else + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + end + } + end + end + + def new + @fax_document = @fax_account.fax_documents.build + @phone_number = @fax_document.build_destination_phone_number + end + + def create + @fax_document = @fax_account.fax_documents.build(params[:fax_document]) + @fax_document.retry_counter = @fax_account.retries + if @fax_document.save + @fax_document.queue_for_sending! + redirect_to fax_account_fax_document_path(@fax_document.fax_account, @fax_document), :notice => t('fax_documents.controller.successfuly_created') + else + render :new + end + end + + def destroy + @fax_account = FaxAccount.find(params[:fax_account_id]) + @fax_document = @fax_account.fax_documents.find(params[:id]) + @fax_document.destroy + redirect_to fax_account_fax_documents_url, :notice => t('fax_documents.controller.successfuly_destroyed') + end + + private + def spread_breadcrumbs + breadcrumbs = [] + breadcrumbs = case @fax_account.fax_accountable.class.to_s + when 'User' ; [ + [@fax_account.fax_accountable.to_s, user_path(@fax_account.fax_accountable)], + [t('fax_accounts.name').pluralize, user_fax_accounts_path(@fax_account.fax_accountable)], + [t('fax_documents.name').pluralize, fax_account_fax_documents_path(@fax_account)], + ] + when 'UserGroup' ; [ + [@fax_account.fax_accountable, user_group_path(@fax_account.fax_accountable)], + [t('fax_accounts.name').pluralize, user_group_fax_accounts_path(@fax_account.fax_accountable)], + [t('fax_documents.name').pluralize, fax_account_fax_documents_path(@fax_account)], + ] + end + if !breadcrumbs.blank? + breadcrumbs.each do |breadcrumb| + add_breadcrumb breadcrumb[0], breadcrumb[1] + end + end + end + +end diff --git a/app/controllers/freeswitch_voicemail_msgs_controller.rb b/app/controllers/freeswitch_voicemail_msgs_controller.rb new file mode 100644 index 0000000..085db3d --- /dev/null +++ b/app/controllers/freeswitch_voicemail_msgs_controller.rb @@ -0,0 +1,7 @@ +class FreeswitchVoicemailMsgsController < ApplicationController + load_and_authorize_resource :sip_account + load_and_authorize_resource :freeswitch_voicemail_msg, :through => [:sip_account] + + def index + end +end diff --git a/app/controllers/gemeinschaft_setups_controller.rb b/app/controllers/gemeinschaft_setups_controller.rb new file mode 100644 index 0000000..cafb8a3 --- /dev/null +++ b/app/controllers/gemeinschaft_setups_controller.rb @@ -0,0 +1,61 @@ +class GemeinschaftSetupsController < ApplicationController + load_and_authorize_resource :gemeinschaft_setup + + skip_before_filter :go_to_setup_if_new_installation + # before_filter :redirect_if_not_a_fresh_installation + + def new + @user = @gemeinschaft_setup.build_user( + :user_name => t('gemeinschaft_setups.initial_setup.admin_name'), + :male => true, + :email => 'admin@localhost', + ) + @sip_domain = @gemeinschaft_setup.build_sip_domain( + :host => guess_local_host(), + :realm => guess_local_host(), + ) + @gemeinschaft_setup.country = Country.find_by_name('Germany') + @gemeinschaft_setup.language = Language.find_by_name('Deutsch') + end + + def create + if @gemeinschaft_setup.save + super_tenant = Tenant.create( + :name => SUPER_TENANT_NAME, + :country_id => @gemeinschaft_setup.country.id, + :language_id => @gemeinschaft_setup.language_id, + :description => t('gemeinschaft_setups.initial_setup.super_tenant_description'), + ) + + # Admin + user = @gemeinschaft_setup.user + super_tenant.tenant_memberships.create(:user_id => user.id) + user.update_attributes(:current_tenant_id => super_tenant.id) + + # Create the Super-Tenant's group: + super_tenant_super_admin_group = super_tenant.user_groups.create(:name => t('gemeinschaft_setups.initial_setup.super_admin_group_name')) + super_tenant_super_admin_group.user_group_memberships.create(:user_id => user.id) + + # Auto-Login: + session[:user_id] = user.id + + # Redirect to the user + redirect_to new_tenant_url, :notice => t('gemeinschaft_setups.initial_setup.successful_setup') + else + render :new + end + end + + private + + def redirect_if_not_a_fresh_installation + if GemeinschaftSetup.all.count > 0 + if current_user + redirect_to root_url , :alert => t('gemeinschaft_setups.initial_setup.access_denied_only_available_on_a_new_system') + else + redirect_to log_in_path , :alert => t('gemeinschaft_setups.initial_setup.access_denied_only_available_on_a_new_system') + end + end + end + +end diff --git a/app/controllers/gs_cluster_sync_log_entries_controller.rb b/app/controllers/gs_cluster_sync_log_entries_controller.rb new file mode 100644 index 0000000..3e65037 --- /dev/null +++ b/app/controllers/gs_cluster_sync_log_entries_controller.rb @@ -0,0 +1,25 @@ +class GsClusterSyncLogEntriesController < ApplicationController + + # GET /gs_cluster_sync_log_entries/new.json + def new + @gs_cluster_sync_log_entry = GsClusterSyncLogEntry.new + + respond_to do |format| + format.json { render json: @gs_cluster_sync_log_entry } + end + end + + # POST /gs_cluster_sync_log_entries.json + def create + @gs_cluster_sync_log_entry = GsClusterSyncLogEntry.new(params[:gs_cluster_sync_log_entry]) + + respond_to do |format| + if @gs_cluster_sync_log_entry.save + format.json { render json: @gs_cluster_sync_log_entry, status: :created, location: @gs_cluster_sync_log_entry } + else + format.json { render json: @gs_cluster_sync_log_entry.errors, status: :unprocessable_entity } + end + end + end + +end diff --git a/app/controllers/gs_nodes_controller.rb b/app/controllers/gs_nodes_controller.rb new file mode 100644 index 0000000..3667775 --- /dev/null +++ b/app/controllers/gs_nodes_controller.rb @@ -0,0 +1,170 @@ +class GsNodesController < ApplicationController + + load_and_authorize_resource :gs_node, :only => [:index, :show, :new, :create, :edit, :update, :destroy] + + before_filter :spread_breadcrumbs + + def index + + end + + def show + + end + + def new + @gs_node = GsNode.new + @gs_node.push_updates_to = true + @gs_node.accepts_updates_from = true + @gs_node.element_name = 'gs_cluster_sync_log_entry' + end + + def create + @gs_node = GsNode.new(params[:gs_node]) + if @gs_node.save + redirect_to @gs_node, :notice => t('gs_nodes.controller.successfuly_created') + else + render :new + end + end + + def edit + @gs_node = GsNode.find(params[:id]) + end + + def update + @gs_node = GsNode.find(params[:id]) + if @gs_node.update_attributes(params[:gs_node]) + redirect_to @gs_node, :notice => t('gs_nodes.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @gs_node = GsNode.find(params[:id]) + @gs_node.destroy + redirect_to gs_nodes_url, :notice => t('gs_nodes.controller.successfuly_destroyed') + end + + def sync + if !GsNode.where(:ip_address => request.remote_ip).first + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + return + end + + if ! params[:newer].blank? + @newer_as = Time.at(params[:newer].to_i) + else + @newer_as = Time.at(0) + end + + if ! params[:class].blank? + @request_class = params[:class].to_s + else + @request_class = ''; + end + + @node = GsNode.where(:ip_address => HOMEBASE_IP_ADDRESS).first + + if @request_class.blank? || @request_class == "tenants" + @tenants = Tenant.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "user_groups" + @user_groups = UserGroup.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "users" + @users = User.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "user_group_memberships" + @user_group_memberships = UserGroupMembership.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "sip_accounts" + @sip_accounts = SipAccount.where('updated_at > ?',@newer_as) + end + + if @request_class.blank? || @request_class == "conferences" + @conferences = Conference.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "fax_accounts" + @fax_accounts = FaxAccount.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "phone_books" + @phone_books = PhoneBook.where('updated_at > ?',@newer_as) + end + + if @request_class.blank? || @request_class == "phone_book_entries" + @phone_book_entries = PhoneBookEntry.where('updated_at > ?',@newer_as) + end + + if @request_class.blank? || @request_class == "phone_numbers" + @phone_numbers = PhoneNumber.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "call_forwards" + @call_forwards = CallForward.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "softkeys" + @softkeys = Softkey.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "ringtones" + @ringtones = Ringtone.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "conference_invitees" + @conference_invitees = ConferenceInvitee.where('updated_at > ?', @newer_as) + end + + if @request_class == "fax_documents" + @fax_documents = FaxDocument.where('updated_at > ?', @newer_as) + end + + if @request_class == "call_histories" + @call_histories = CallHistory.where('updated_at > ?', @newer_as) + end + + if @request_class.blank? || @request_class == "deleted_items" + @deleted_items = Array.new + deleted_items_log = GsClusterSyncLogEntry.where('action = "destroy" AND updated_at > ?', @newer_as) + deleted_items_log.each do |deleted_item_log_entry| + content = JSON(deleted_item_log_entry.content) + content['class_name'] = deleted_item_log_entry.class_name + if content['uuid'] + @deleted_items << content + end + end + end + + if params[:image].to_s == 'false' + @image_include = false + else + @image_include = true + end + + end + + private + + def spread_breadcrumbs + if @gs_node + add_breadcrumb t("gs_nodes.index.page_title"), gs_nodes_path + + if @gs_node && !@gs_node.new_record? + add_breadcrumb @gs_node, gs_node_path(@gs_node) + end + end + end +end diff --git a/app/controllers/gui_functions_controller.rb b/app/controllers/gui_functions_controller.rb new file mode 100644 index 0000000..2ab2c5e --- /dev/null +++ b/app/controllers/gui_functions_controller.rb @@ -0,0 +1,73 @@ +class GuiFunctionsController < ApplicationController + before_filter :load_user_groups + before_filter :spread_breadcrumbs + + def index + @gui_functions = GuiFunction.order(:category, :name) + end + + def show + @gui_function = GuiFunction.find(params[:id]) + end + + def new + @gui_function = GuiFunction.new + + @user_groups.each do |user_group| + if @gui_function.user_groups.where(:id => user_group.id).count == 0 + @gui_function.gui_function_memberships.build(:user_group_id => user_group.id, :activated => true) + end + end + end + + def create + @gui_function = GuiFunction.new(params[:gui_function]) + + if @gui_function.save + redirect_to @gui_function, :notice => t('gui_functions.controller.successfuly_created') + else + render :new + end + end + + def edit + @gui_function = GuiFunction.find(params[:id]) + @user_groups.each do |user_group| + if @gui_function.user_groups.where(:id => user_group.id).count == 0 + @gui_function.gui_function_memberships.build(:user_group_id => user_group.id, :activated => true) + end + end + end + + def update + @gui_function = GuiFunction.find(params[:id]) + if @gui_function.update_attributes(params[:gui_function]) + redirect_to @gui_function, :notice => t('gui_functions.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @gui_function = GuiFunction.find(params[:id]) + @gui_function.destroy + redirect_to gui_functions_url, :notice => t('gui_functions.controller.successfuly_destroyed') + end + + private + def load_user_groups + @user_groups = Tenant.find(@current_user.current_tenant).user_groups.order(:position) + end + + def spread_breadcrumbs + if @tenant + add_breadcrumb t("user_groups.index.page_title"), tenant_user_groups_path(@tenant) + if @user_group && !@user_group.new_record? + add_breadcrumb @user_group, tenant_user_group_path(@tenant, @user_group) + end + end + + add_breadcrumb t("gui_functions.index.page_title"), gui_functions_path + end + +end diff --git a/app/controllers/hunt_group_members_controller.rb b/app/controllers/hunt_group_members_controller.rb new file mode 100644 index 0000000..90206ee --- /dev/null +++ b/app/controllers/hunt_group_members_controller.rb @@ -0,0 +1,67 @@ +class HuntGroupMembersController < ApplicationController + load_and_authorize_resource :hunt_group + load_and_authorize_resource :hunt_group_member, :through => [:hunt_group] + + before_filter :spread_breadcrumbs + + def index + if params[:active] + if params[:active].downcase == 'true' + @hunt_group_members = @hunt_group_members.where(:active => true) + elsif params[:active].downcase == 'false' + @hunt_group_members = @hunt_group_members.where(:active => false) + end + end + end + + def show + end + + def new + @hunt_group_member = @hunt_group.hunt_group_members.build + + i = @hunt_group.hunt_group_members.count + loop do + i += 1 + break unless @hunt_group.hunt_group_members.where(:name => "#{t('hunt_group_members.name')} #{i}").count > 0 + end + @hunt_group_member.name = "#{t('hunt_group_members.name')} #{i}" + @hunt_group_member.active = true + @hunt_group_member.can_switch_status_itself = true + end + + def create + @hunt_group_member = @hunt_group.hunt_group_members.build(params[:hunt_group_member]) + if @hunt_group_member.save + redirect_to hunt_group_hunt_group_member_path(@hunt_group, @hunt_group_member), :notice => t('hunt_group_members.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @hunt_group_member.update_attributes(params[:hunt_group_member]) + redirect_to hunt_group_hunt_group_member_path(@hunt_group, @hunt_group_member), :notice => t('hunt_group_members.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @hunt_group_member.destroy + redirect_to hunt_group_hunt_group_members_path(@hunt_group), :notice => t('hunt_group_members.controller.successfuly_destroyed') + end + + def spread_breadcrumbs + add_breadcrumb t("hunt_groups.index.page_title"), tenant_hunt_groups_path(@hunt_group.tenant) + add_breadcrumb @hunt_group, tenant_hunt_group_path(@hunt_group.tenant, @hunt_group) + add_breadcrumb t("hunt_group_members.index.page_title"), hunt_group_hunt_group_members_path(@hunt_group) + if @hunt_group_member && !@hunt_group_member.new_record? + add_breadcrumb @hunt_group_member, hunt_group_hunt_group_member_path(@hunt_group, @hunt_group_member) + end + end + +end diff --git a/app/controllers/hunt_groups_controller.rb b/app/controllers/hunt_groups_controller.rb new file mode 100644 index 0000000..13a556a --- /dev/null +++ b/app/controllers/hunt_groups_controller.rb @@ -0,0 +1,55 @@ +class HuntGroupsController < ApplicationController + load_and_authorize_resource :tenant + load_and_authorize_resource :hunt_group, :through => [:tenant] + + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + i = @tenant.hunt_groups.count + loop do + i += 1 + break unless @tenant.hunt_groups.where(:name => "#{t('hunt_groups.name')} #{i}").count > 0 + end + @hunt_group = @tenant.hunt_groups.build(:name => "#{t('hunt_groups.name')} #{i}") + end + + def create + @hunt_group = @tenant.hunt_groups.build(params[:hunt_group]) + if @hunt_group.save + redirect_to tenant_hunt_group_path(@tenant, @hunt_group), :notice => t('hunt_groups.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + @hunt_group = HuntGroup.find(params[:id]) + if @hunt_group.update_attributes(params[:hunt_group]) + redirect_to tenant_hunt_group_path(@tenant, @hunt_group), :notice => t('hunt_groups.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @hunt_group.destroy + redirect_to tenant_hunt_groups_path(@tenant), :notice => t('hunt_groups.controller.successfuly_destroyed') + end + + private + def spread_breadcrumbs + add_breadcrumb t("hunt_groups.index.page_title"), tenant_hunt_groups_path(@tenant) + if @hunt_group && !@hunt_group.new_record? + add_breadcrumb @hunt_group, tenant_hunt_group_path(@tenant, @hunt_group) + end + end +end diff --git a/app/controllers/manufacturers_controller.rb b/app/controllers/manufacturers_controller.rb new file mode 100644 index 0000000..1bcf9de --- /dev/null +++ b/app/controllers/manufacturers_controller.rb @@ -0,0 +1,49 @@ +class ManufacturersController < ApplicationController + load_and_authorize_resource :manufacturer + + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + end + + def create + @manufacturer = Manufacturer.new(params[:manufacturer]) + if @manufacturer.save + redirect_to @manufacturer, :notice => t('manufacturers.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @manufacturer.update_attributes(params[:manufacturer]) + redirect_to @manufacturer, :notice => t('manufacturers.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @manufacturer.destroy + redirect_to manufacturers_url, :notice => t('manufacturers.controller.successfuly_destroyed') + end + + private + + def spread_breadcrumbs + add_breadcrumb t("manufacturers.index.page_title"), manufacturers_path + if @manufacturer && !@manufacturer.new_record? + add_breadcrumb @manufacturer, manufacturer_path(@manufacturer) + end + end + +end diff --git a/app/controllers/page_controller.rb b/app/controllers/page_controller.rb new file mode 100644 index 0000000..1f37449 --- /dev/null +++ b/app/controllers/page_controller.rb @@ -0,0 +1,24 @@ +class PageController < ApplicationController + # load_and_authorize_resource :class => false + # CanCan doesn't work here really good because Page is not a resource. + + before_filter :if_fresh_system_then_go_to_wizard + skip_before_filter :home_breadcrumb, :only => [:index] + + def index;end + def conference;end + + private + def if_fresh_system_then_go_to_wizard + if Tenant.count == 0 && User.count == 0 + # This is a brand new system. We need to run a setup first. + redirect_to wizards_new_initial_setup_path + else + if current_user.nil? + # You need to login first. + redirect_to log_in_path, :alert => I18n.t('pages.controller.access_denied_login_first') + end + end + end + +end diff --git a/app/controllers/phone_book_entries_controller.rb b/app/controllers/phone_book_entries_controller.rb new file mode 100644 index 0000000..823d50e --- /dev/null +++ b/app/controllers/phone_book_entries_controller.rb @@ -0,0 +1,135 @@ +class PhoneBookEntriesController < ApplicationController + load_and_authorize_resource :phone_book + load_and_authorize_resource :phone_book_entry, :through => :phone_book, :shallow => true + + before_filter :spread_breadcrumbs + + def index + # In case this is a search params[:q] or params[:name] will contain the query. + # + @query = params[:q] + @query ||= params[:name] + @query = @query.strip if @query + + if !@query.blank? + if @query.match(/^\+?\d+$/) != nil + # Find by phone number + phone_book_entries_ids = @phone_book_entries.map{|entry| entry.id} + @found_phone_numbers = PhoneNumber. + where(:phone_numberable_type => 'PhoneBookEntry', :phone_numberable_id => phone_book_entries_ids). + where('number LIKE ?', "#{@query}%") + @search_result = @phone_book_entries.where(:id => @found_phone_numbers.map{|entry| entry.phone_numberable_id}) + elsif @query.match(/^[\"\'](.*)[\"\']$/) != nil + # The User searched for =>'example'<= so he wants an EXACT search for that. + # This is the fasted and most accurate way of searching. + # The order to search is: last_name, first_name and organization. + # It stops searching as soon as it finds results. + # + @query = $1 + @search_result = @phone_book_entries.where(:last_name => @query) + @search_result = @phone_book_entries.where(:first_name => @query) if @search_result.count == 0 + @search_result = @phone_book_entries.where(:organization => @query) if @search_result.count == 0 + + @exact_search = true + else + # Search with SQL LIKE + # + @search_result = @phone_book_entries. + where( '( ( last_name LIKE ? ) OR ( first_name LIKE ? ) OR ( organization LIKE ? ) )', + "#{@query}%", "#{@query}%", "#{@query}%" ) + + @exact_search = false + end + + # Let's have a run with our phonetic search. + # + phonetic_query = PhoneBookEntry.koelner_phonetik(@query) + @phonetic_search_result = @phone_book_entries.where(:last_name_phonetic => phonetic_query) + @phonetic_search_result = @phone_book_entries.where(:first_name_phonetic => phonetic_query) if @phonetic_search_result.count == 0 + @phonetic_search_result = @phone_book_entries.where(:organization_phonetic => phonetic_query) if @phonetic_search_result.count == 0 + + if @phonetic_search_result.count == 0 + # Let's try the search with SQL LIKE. Just in case. + # + @phonetic_search_result = @phone_book_entries.where( 'last_name_phonetic LIKE ?', "#{phonetic_query}%" ) + @phonetic_search_result = @phone_book_entries.where( 'first_name_phonetic LIKE ?', "#{phonetic_query}%" ) if @phonetic_search_result.count == 0 + @phonetic_search_result = @phone_book_entries.where( 'organization_phonetic LIKE ?', "#{phonetic_query}%" ) if @phonetic_search_result.count == 0 + end + + @phonetic_search = true if @phonetic_search_result.count > 0 + + @phone_book_entries = @search_result + + if @phone_book_entries.count == 0 && @exact_search == false && @phonetic_search + @phone_book_entries = @phonetic_search_result + end + end + + # Let's sort the results and do pagination. + # + @phone_book_entries = @phone_book_entries. + order([ :last_name, :first_name, :organization ]). + paginate( + :page => @pagination_page_number, + :per_page => DEFAULT_PAGINATION_ENTRIES_PER_PAGE + ) + end + + def show + end + + def new + @phone_book_entry = @phone_book.phone_book_entries.build + @phone_book_entry.is_male = true + end + + def create + @phone_book_entry = @phone_book.phone_book_entries.build( params[:phone_book_entry] ) + if @phone_book_entry.save + redirect_to phone_book_phone_book_entry_path( @phone_book, @phone_book_entry ), :notice => t('phone_book_entries.controller.successfuly_created', :resource => @phone_book_entry) + else + render :new + end + end + + def edit + end + + def update + if @phone_book_entry.update_attributes(params[:phone_book_entry]) + redirect_to @phone_book_entry, :notice => t('phone_book_entries.controller.successfuly_updated', :resource => @phone_book_entry) + else + render :edit + end + end + + def destroy + @phone_book_entry.destroy + redirect_to phone_book_entries_url, :notice => t('phone_book_entries.controller.successfuly_destroyed') + end + + private + + def spread_breadcrumbs + if @phone_book + if @phone_book.phone_bookable.class == Tenant + add_breadcrumb t("phone_books.index.page_title"), tenant_phone_books_path(@phone_book.phone_bookable) + add_breadcrumb @phone_book, tenant_phone_book_path(@phone_book.phone_bookable, @phone_book) + add_breadcrumb t("phone_book_entries.index.page_title"), phone_book_phone_book_entries_path(@phone_book) + end + + if @phone_book.phone_bookable.class == User + add_breadcrumb t("users.index.page_title"), tenant_users_path(@phone_book.phone_bookable.current_tenant) + add_breadcrumb @phone_book.phone_bookable, tenant_user_path(@phone_book.phone_bookable.current_tenant, @phone_book.phone_bookable) + add_breadcrumb t("phone_books.index.page_title"), user_phone_books_path(@phone_book.phone_bookable) + add_breadcrumb @phone_book, user_phone_book_path(@phone_book.phone_bookable, @phone_book) + add_breadcrumb t("phone_book_entries.index.page_title"), phone_book_phone_book_entries_path(@phone_book) + end + + if @phone_book_entry && !@phone_book_entry.new_record? + add_breadcrumb @phone_book_entry, phone_book_phone_book_entry_path(@phone_book, @phone_book_entry) + end + end + end + +end diff --git a/app/controllers/phone_books_controller.rb b/app/controllers/phone_books_controller.rb new file mode 100644 index 0000000..54e7889 --- /dev/null +++ b/app/controllers/phone_books_controller.rb @@ -0,0 +1,105 @@ +class PhoneBooksController < ApplicationController + load_resource :user + load_resource :user_group + load_resource :tenant + load_and_authorize_resource :phone_book, :through => [:user, :user_group, :tenant] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + @by_name = params[:name] + + @pagination_page_number = params[:page].to_i + @pagination_page_number = 1 if @pagination_page_number < 1 + + if @by_name.blank? + @phone_book_entries = @phone_book. + phone_book_entries. + order([ :last_name, :first_name ]). + paginate( + :page => @pagination_page_number, + :per_page => DEFAULT_PAGINATION_ENTRIES_PER_PAGE + ) + else + # search by name + @by_name = @by_name. + gsub( /[^A-Za-z0-9#]/, '' ). + gsub('*','?'). + gsub('%','_'). + gsub(/^#/,''). + upcase + + @phone_book_entries = @phone_book. + phone_book_entries. + where( '( ( last_name LIKE ? ) OR ( first_name LIKE ? ) )', "#{@by_name}%", "#{@by_name}%" ). + order([ :last_name, :first_name ]). + paginate( + :page => @pagination_page_number, + :per_page => DEFAULT_PAGINATION_ENTRIES_PER_PAGE + ) + end + end + + def new + @phone_book = @parent.phone_books.build + @phone_book.name = generate_a_new_name(@parent, @phone_book) + end + + def create + @phone_book = @parent.phone_books.build( params[:phone_book] ) + if @phone_book.save + m = method( :"#{@parent.class.name.underscore}_phone_book_path" ) + redirect_to m.( @parent, @phone_book ), :notice => t('phone_books.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @phone_book.update_attributes(params[:phone_book]) + m = method( :"#{@parent.class.name.underscore}_phone_book_path" ) + redirect_to m.( @parent, @phone_book ), :notice => t('phone_books.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @phone_book.destroy + m = method( :"#{@parent.class.name.underscore}_phone_books_url" ) + redirect_to m.( @parent ), :notice => t('phone_books.controller.successfuly_destroyed') + end + + private + def set_and_authorize_parent + @parent = @user || @user_group || @tenant + authorize! :read, @parent + end + + def spread_breadcrumbs + if @parent.class == Tenant + add_breadcrumb t("phone_books.index.page_title"), tenant_phone_books_path(@tenant) + if @phone_book && !@phone_book.new_record? + add_breadcrumb @phone_book, tenant_phone_book_path(@tenant, @phone_book) + end + end + + if @parent.class == User + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t("phone_books.index.page_title"), user_phone_books_path(@user) + if @phone_book && !@phone_book.new_record? + add_breadcrumb @phone_book, user_phone_book_path(@user, @phone_book) + end + end + + end + +end diff --git a/app/controllers/phone_models_controller.rb b/app/controllers/phone_models_controller.rb new file mode 100644 index 0000000..59facdf --- /dev/null +++ b/app/controllers/phone_models_controller.rb @@ -0,0 +1,52 @@ +class PhoneModelsController < ApplicationController + load_and_authorize_resource :manufacturer + load_and_authorize_resource :phone_model, :through => [:manufacturer] + + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @phone_model = @manufacturer.phone_models.build + end + + def create + @phone_model = @manufacturer.phone_models.build.new(params[:phone_model]) + if @phone_model.save + redirect_to manufacturer_phone_model_path( @manufacturer, @phone_model ), :notice => t('phone_models.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @phone_model.update_attributes(params[:phone_model]) + redirect_to manufacturer_phone_model_path( @manufacturer, @phone_model ), :notice => t('phone_models.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @phone_model.destroy + redirect_to manufacturer_phone_models_url( @manufacturer ), :notice => t('phone_models.controller.successfuly_destroyed') + end + + private + + def spread_breadcrumbs + add_breadcrumb t("manufacturers.index.page_title"), manufacturers_path + add_breadcrumb @manufacturer, manufacturer_path(@manufacturer) + add_breadcrumb t("phone_models.index.page_title"), manufacturer_phone_models_path(@manufacturer) + if @phone_model && !@phone_model.new_record? + add_breadcrumb @phone_model, manufacturer_phone_model_path(@manufacturer, @phone_model) + end + end +end diff --git a/app/controllers/phone_number_ranges_controller.rb b/app/controllers/phone_number_ranges_controller.rb new file mode 100644 index 0000000..a4e7238 --- /dev/null +++ b/app/controllers/phone_number_ranges_controller.rb @@ -0,0 +1,56 @@ +class PhoneNumberRangesController < ApplicationController + load_and_authorize_resource :tenant + load_and_authorize_resource :phone_number_range, :through => [:tenant] + + before_filter :set_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @phone_number_range = @parent.phone_number_ranges.build + end + + def create + @phone_number_range = @parent.phone_number_ranges.build(params[:phone_number_range]) + if @phone_number_range.save + redirect_to @phone_number_range, :notice => t('phone_number_ranges.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @phone_number_range.update_attributes(params[:phone_number_range]) + redirect_to @phone_number_range, :notice => t('phone_number_ranges.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @phone_number_range.destroy + redirect_to phone_number_ranges_url, :notice => t('phone_number_ranges.controller.successfuly_destroyed') + end + + private + + def set_parent + @parent = @tenant + end + + def spread_breadcrumbs + add_breadcrumb t("phone_number_ranges.index.page_title"), tenant_phone_number_ranges_path(@tenant) + if @phone_number_range && !@phone_number_range.new_record? + add_breadcrumb t("phone_number_ranges.ranges.#{@phone_number_range}.label"), tenant_phone_number_range_path(@tenant, @phone_number_range) + end + end + +end diff --git a/app/controllers/phone_numbers_controller.rb b/app/controllers/phone_numbers_controller.rb new file mode 100644 index 0000000..065934c --- /dev/null +++ b/app/controllers/phone_numbers_controller.rb @@ -0,0 +1,226 @@ +class PhoneNumbersController < ApplicationController + load_resource :phone_book_entry + load_resource :sip_account + load_resource :conference + load_resource :fax_account + load_resource :phone_number_range + load_resource :callthrough + load_resource :whitelist + load_resource :access_authorization + load_resource :hunt_group + load_resource :hunt_group_member + load_resource :automatic_call_distributor + load_and_authorize_resource :phone_number, :through => [:phone_book_entry, :sip_account, :conference, + :fax_account, :phone_number_range, :callthrough, + :whitelist, :access_authorization, :hunt_group, + :hunt_group_member, :automatic_call_distributor] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + @ringtoneable_classes = { + 'SipAccount' => true, + 'HuntGroup' => true, + 'AutomaticCallDistributor' => true, + 'PhoneBookEntry' => true, + } + @forwardable_classes = { + 'SipAccount' => true, + 'HuntGroup' => true, + 'AutomaticCallDistributor' => true, + } + end + + def new + @phone_number = @parent.phone_numbers.build() + end + + def create + @phone_number = @parent.phone_numbers.new( params[:phone_number] ) + if @phone_number.save + m = method( :"#{@parent.class.name.underscore}_phone_number_path" ) + redirect_to m.( @parent, @phone_number ), :notice => t('phone_numbers.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @phone_number.update_attributes(params[:phone_number]) + m = method( :"#{@parent.class.name.underscore}_phone_number_path" ) + redirect_to m.( @parent, @phone_number ), :notice => t('phone_numbers.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @phone_number.destroy + m = method( :"#{@parent.class.name.underscore}_phone_numbers_url" ) + redirect_to m.(), :notice => t('phone_numbers.controller.successfuly_destroyed') + end + + def move_higher + @phone_number.move_higher + redirect_to :back + end + + def move_lower + @phone_number.move_lower + redirect_to :back + end + + private + def set_and_authorize_parent + @parent = @phone_book_entry || @sip_account || @conference || @fax_account || + @phone_number_range || @callthrough || @whitelist || @access_authorization || + @hunt_group || @hunt_group_member || @automatic_call_distributor + + authorize! :read, @parent + + @show_path_method = method( :"#{@parent.class.name.underscore}_phone_number_path" ) + @index_path_method = method( :"#{@parent.class.name.underscore}_phone_numbers_path" ) + @new_path_method = method( :"new_#{@parent.class.name.underscore}_phone_number_path" ) + @edit_path_method = method( :"edit_#{@parent.class.name.underscore}_phone_number_path" ) + end + + def spread_breadcrumbs + if @parent.class == Callthrough + add_breadcrumb t("#{@parent.class.name.underscore.pluralize}.index.page_title"), tenant_callthroughs_path(@parent.tenant) + add_breadcrumb @callthrough, tenant_callthrough_path(@parent.tenant, @callthrough) + add_breadcrumb t("phone_numbers.index.page_title"), callthrough_phone_numbers_path(@parent) + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, callthrough_phone_number_path(@callthrough, @phone_number) + end + end + + if @parent.class == SipAccount + if @sip_account.sip_accountable.class == User + add_breadcrumb t("#{@sip_account.sip_accountable.class.name.underscore.pluralize}.index.page_title"), method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore.pluralize}_path" ).(@sip_account.tenant) + add_breadcrumb @sip_account.sip_accountable, method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore}_path" ).(@sip_account.tenant, @sip_account.sip_accountable) + end + add_breadcrumb t("sip_accounts.index.page_title"), method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_accounts_path" ).(@sip_account.sip_accountable) + add_breadcrumb @sip_account, method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_account_path" ).(@sip_account.sip_accountable, @sip_account) + add_breadcrumb t("phone_numbers.index.page_title"), sip_account_phone_numbers_path(@sip_account) + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, sip_account_phone_number_path(@sip_account, @phone_number) + end + end + + if @parent.class == Conference + @conference = @parent + conference_parent = @conference.conferenceable + if conference_parent && conference_parent.class == User + @user = conference_parent + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t("conferences.index.page_title"), user_conferences_path(@user) + add_breadcrumb @conference, user_conference_path(@user, @conference) + end + if conference_parent && conference_parent.class == Tenant + @tenant = conference_parent + add_breadcrumb t("conferences.index.page_title"), tenant_conferences_path(@tenant) + add_breadcrumb @conference, tenant_conference_path(@tenant, @conference) + end + add_breadcrumb t("phone_numbers.index.page_title"), conference_phone_numbers_path(@conference) + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, conference_phone_number_path(@conference, @phone_number) + end + end + + if @parent.class == HuntGroup + add_breadcrumb t("#{@parent.class.name.underscore.pluralize}.index.page_title"), tenant_hunt_groups_path(@parent.tenant) + add_breadcrumb @hunt_group, tenant_hunt_group_path(@parent.tenant, @hunt_group) + add_breadcrumb t("phone_numbers.index.page_title"), hunt_group_phone_numbers_path(@parent) + end + + if @parent.class == HuntGroupMember + add_breadcrumb t("hunt_groups.index.page_title"), tenant_hunt_groups_path(@parent.hunt_group.tenant) + add_breadcrumb @parent.hunt_group, tenant_hunt_group_path(@parent.hunt_group.tenant, @parent.hunt_group) + add_breadcrumb t("hunt_group_members.index.page_title"), hunt_group_hunt_group_members_path(@parent.hunt_group) + add_breadcrumb @parent, hunt_group_hunt_group_member_path(@parent.hunt_group, @parent) + add_breadcrumb t("phone_numbers.index.page_title"), hunt_group_member_phone_numbers_path(@parent) + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, hunt_group_member_phone_number_path(@parent, @phone_number) + end + end + + if @parent.class == AccessAuthorization + if @parent.access_authorizationable.class == Callthrough + callthrough = @parent.access_authorizationable + tenant = callthrough.tenant + add_breadcrumb t("callthroughs.index.page_title"), tenant_callthroughs_path(tenant) + add_breadcrumb callthrough, tenant_callthrough_path(tenant, callthrough) + add_breadcrumb t("access_authorizations.index.page_title"), callthrough_access_authorizations_path(callthrough) + add_breadcrumb @parent, callthrough_access_authorization_path(callthrough, @parent) + add_breadcrumb t("phone_numbers.index.page_title"), access_authorization_phone_numbers_path(@parent) + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, access_authorization_phone_number_path(@parent, @phone_number) + end + end + end + + if @parent.class == PhoneBookEntry + @phone_book = @parent.phone_book + if @parent.phone_book.phone_bookable.class == Tenant + @tenant = @parent.phone_book.phone_bookable + add_breadcrumb t("phone_books.index.page_title"), tenant_phone_books_path(@tenant) + add_breadcrumb @phone_book, tenant_phone_book_path(@tenant, @phone_book) + add_breadcrumb @phone_book_entry, phone_book_phone_book_entry_path(@phone_book, @phone_book_entry) + + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, phone_book_entry_phone_number_path(@phone_book_entry, @phone_number) + end + end + + if @parent.phone_book.phone_bookable.class == User + @user = @parent.phone_book.phone_bookable + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t("phone_books.index.page_title"), user_phone_books_path(@user) + add_breadcrumb @phone_book, user_phone_book_path(@user, @phone_book) + add_breadcrumb @phone_book_entry, phone_book_phone_book_entry_path(@phone_book, @phone_book_entry) + + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, phone_book_entry_phone_number_path(@phone_book_entry, @phone_number) + end + end + end + + if @parent.class == Whitelist + @tenant = @parent.whitelistable.tenant + @callthrough = @parent.whitelistable + @whitelist = @parent + add_breadcrumb t("callthroughs.name").pluralize, tenant_callthroughs_path(@tenant) + add_breadcrumb @callthrough, tenant_callthrough_path(@tenant, @callthrough) + add_breadcrumb t("whitelists.index.page_title"), callthrough_whitelists_path(@callthrough) + add_breadcrumb @whitelist, callthrough_whitelist_path(@callthrough, @whitelist) + add_breadcrumb t("phone_numbers.index.page_title"), whitelist_phone_numbers_path(@whitelist) + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, whitelist_phone_number_path(@whitelist, @phone_number) + end + end + + if @parent.class == AutomaticCallDistributor + if @automatic_call_distributor.automatic_call_distributorable.class == User + add_breadcrumb t("#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore.pluralize}.index.page_title"), method( :"tenant_#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore.pluralize}_path" ).(@automatic_call_distributor.tenant) + add_breadcrumb @automatic_call_distributor.automatic_call_distributorable, method( :"tenant_#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore}_path" ).(@automatic_call_distributor.tenant, @automatic_call_distributor.automatic_call_distributorable) + end + add_breadcrumb t("automatic_call_distributors.index.page_title"), method( :"#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore}_automatic_call_distributors_path" ).(@automatic_call_distributor.automatic_call_distributorable) + add_breadcrumb @automatic_call_distributor, method( :"#{@automatic_call_distributor.automatic_call_distributorable.class.name.underscore}_automatic_call_distributor_path" ).(@automatic_call_distributor.automatic_call_distributorable, @automatic_call_distributor) + add_breadcrumb t("phone_numbers.index.page_title"), automatic_call_distributor_phone_numbers_path(@automatic_call_distributor) + if @phone_number && !@phone_number.new_record? + add_breadcrumb @phone_number, automatic_call_distributor_phone_number_path(@automatic_call_distributor, @phone_number) + end + end + + end + +end diff --git a/app/controllers/phone_sip_accounts_controller.rb b/app/controllers/phone_sip_accounts_controller.rb new file mode 100644 index 0000000..8558c55 --- /dev/null +++ b/app/controllers/phone_sip_accounts_controller.rb @@ -0,0 +1,60 @@ +class PhoneSipAccountsController < ApplicationController + load_and_authorize_resource :phone + load_and_authorize_resource :phone_sip_account, :through => [:phone] + + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @available_sip_accounts = @phone.phoneable.sip_accounts + + # Ensure a SipAccount is used on a single phone only. + # + @available_sip_accounts = @available_sip_accounts.delete_if { |x| x.phone_sip_account_ids.count > 0 } + + if @available_sip_accounts.count == 0 + redirect_to method( :"new_#{@phone.phoneable.class.name.underscore}_sip_account_path" ).(@phone.phoneable), :alert => t('phone_sip_accounts.controller.no_existing_sip_accounts_warning') + else + @phone_sip_account = @phone.phone_sip_accounts.build(:sip_account_id => @available_sip_accounts.first.try(:id)) + end + end + + def create + @phone_sip_account = @phone.phone_sip_accounts.build(params[:phone_sip_account]) + if @phone_sip_account.save + redirect_to method( :"#{@phone_sip_account.phone.phoneable.class.name.underscore}_phone_path" ).(@phone_sip_account.phone.phoneable, @phone_sip_account.phone), :notice => t('phone_sip_accounts.controller.successfuly_created') + else + render :new + end + end + + def destroy + @phone_sip_account.destroy + redirect_to method( :"#{@phone_sip_account.phone.phoneable.class.name.underscore}_phone_path" ).(@phone_sip_account.phone.phoneable, @phone_sip_account.phone), :notice => t('phone_sip_accounts.controller.successfuly_destroyed') + end + + private + + def spread_breadcrumbs + if @phone.phoneable.class == User + user = @phone.phoneable + add_breadcrumb t('users.index.page_title'), tenant_users_path(user.current_tenant) + add_breadcrumb user, tenant_user_path(user.current_tenant, user) + add_breadcrumb t('phones.index.page_title'), user_phones_path(user) + elsif @phone.phoneable.class == Tenant + tenant = @phone.phoneable + add_breadcrumb t('phones.index.page_title'), tenant_phones_path(tenant) + end + add_breadcrumb @phone, method( :"#{@phone.phoneable.class.name.underscore}_phone_path" ).(@phone.phoneable, @phone) + add_breadcrumb t('phone_sip_accounts.index.page_title'), phone_phone_sip_accounts_path(@phone) + if @phone_sip_account && !@phone_sip_account.new_record? + add_breadcrumb @phone_sip_account, phone_phone_sip_account_path(@phone, @phone_sip_account) + end + end + +end diff --git a/app/controllers/phones_controller.rb b/app/controllers/phones_controller.rb new file mode 100644 index 0000000..d46bf86 --- /dev/null +++ b/app/controllers/phones_controller.rb @@ -0,0 +1,72 @@ +class PhonesController < ApplicationController + load_resource :tenant + load_resource :user + load_and_authorize_resource :phone, :through => [:tenant, :user] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @phone = @phoneable.phones.build() + + # Use the last phone.phone_model as the default. + # + @phone.phone_model_id = Phone.last.try(:phone_model).try(:id) + end + + def create + @phone = @phoneable.phones.build(params[:phone]) + if @phone.save + m = method( :"#{@phoneable.class.name.underscore}_phone_path" ) + redirect_to m.( @phoneable, @phone ), :notice => t('phones.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @phone.update_attributes(params[:phone]) + m = method( :"#{@phoneable.class.name.underscore}_phone_path" ) + redirect_to m.( @phoneable, @phone ), :notice => t('phones.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @phone.destroy + m = method( :"#{@phoneable.class.name.underscore}_phones_url" ) + redirect_to m.( @phoneable ), :notice => t('phones.controller.successfuly_destroyed') + end + + private + def set_and_authorize_parent + @phoneable = (@user || @tenant) + @parent = @phoneable + authorize! :read, @parent + @nesting_prefix = @phoneable ? "#{@phoneable.class.name.underscore}_" : '' + end + + def spread_breadcrumbs + if @user + add_breadcrumb t('users.index.page_title'), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t('phones.index.page_title'), user_phones_path(@user) + elsif @tenant + add_breadcrumb t('phones.index.page_title'), tenant_phones_path(@tenant) + end + if @phone && !@phone.new_record? + add_breadcrumb @phone, method( :"#{@phone.phoneable.class.name.underscore}_phone_path" ).(@phone.phoneable, @phone) + end + end + +end diff --git a/app/controllers/ringtones_controller.rb b/app/controllers/ringtones_controller.rb new file mode 100644 index 0000000..7ffe30e --- /dev/null +++ b/app/controllers/ringtones_controller.rb @@ -0,0 +1,67 @@ +class RingtonesController < ApplicationController + load_resource :phone_number + load_resource :boss_assistant_cooperation + load_and_authorize_resource :ringtone, :through => [:phone_number, :boss_assistant_cooperation] + + before_filter :set_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @ringtone = @parent.ringtones.build + end + + def create + @ringtone = @parent.ringtones.build(params[:ringtone]) + if @ringtone.save + redirect_to phone_number_ringtone_path(@parent, @ringtone), :notice => t('ringtones.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @ringtone.update_attributes(params[:ringtone]) + redirect_to method( :"#{@parent.class.name.underscore}_ringtone_path" ).(@ringtone.ringtoneable, @ringtone), :notice => t('ringtones.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @ringtone.destroy + redirect_to phone_number_ringtones_path(@parent), :notice => t('ringtones.controller.successfuly_destroyed') + end + + private + def set_parent + @parent = @phone_number || @boss_assistant_cooperation + end + + def spread_breadcrumbs + if @parent.class == PhoneNumber && @parent.phone_numberable.class == SipAccount + @sip_account = @parent.phone_numberable + if @sip_account.sip_accountable.class == User + add_breadcrumb t("#{@sip_account.sip_accountable.class.name.underscore.pluralize}.index.page_title"), method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore.pluralize}_path" ).(@sip_account.tenant) + add_breadcrumb @sip_account.sip_accountable, method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore}_path" ).(@sip_account.tenant, @sip_account.sip_accountable) + end + add_breadcrumb t("sip_accounts.index.page_title"), method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_accounts_path" ).(@sip_account.sip_accountable) + add_breadcrumb @sip_account, method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_account_path" ).(@sip_account.sip_accountable, @sip_account) + add_breadcrumb t("phone_numbers.index.page_title"), sip_account_phone_numbers_path(@sip_account) + add_breadcrumb @phone_number, sip_account_phone_number_path(@sip_account, @phone_number) + add_breadcrumb t("ringtones.index.page_title"), phone_number_ringtones_path(@phone_number) + if @ringtone && !@ringtone.new_record? + add_breadcrumb @ringtone, phone_number_ringtone_path(@phone_number, @ringtone) + end + end + end + +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..f92ae1c --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,44 @@ +class SessionsController < ApplicationController + + before_filter :redirect_to_https + skip_before_filter :home_breadcrumb + + def new + end + + def create + user = User.find_by_email(params[:sessions][:login_data].downcase.strip) + if user.nil? + user = User.find_by_user_name(params[:sessions][:login_data].downcase.strip) + end + if user && user.authenticate(params[:sessions][:password]) + session[:user_id] = user.id + redirect_to tenant_user_path(user.current_tenant, user), :notice => t('sessions.controller.successfully_created', :resource => user) + elsif user && !user.email.blank? && params[:sessions][:reset_password] =~ (/(1|t|y|yes|true)$/i) + password = SecureRandom.base64(8)[0..7] + if user.update_attributes(:password => password) + Notifications.new_password(user, password).deliver + flash.now.notice = t('sessions.flash_messages.password_recovery_successful', :resource => user) + else + flash.now.alert = t('sessions.flash_messages.password_recovery_failed', :resource => user) + end + render "new" + else + flash.now.alert = t('sessions.flash_messages.invalid_email_or_password', :resource => user) + render "new" + end + end + + def destroy + session[:user_id] = nil + redirect_to root_url, :notice => t('sessions.controller.successfully_destroyed') + end + + private + def redirect_to_https + if GUI_REDIRECT_HTTPS and ! request.ssl? + redirect_to :protocol => "https://" + end + end + +end diff --git a/app/controllers/sip_accounts_controller.rb b/app/controllers/sip_accounts_controller.rb new file mode 100644 index 0000000..1f3166e --- /dev/null +++ b/app/controllers/sip_accounts_controller.rb @@ -0,0 +1,98 @@ +class SipAccountsController < ApplicationController + load_resource :user + load_resource :tenant + load_and_authorize_resource :sip_account, :through => [:user, :tenant ] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @sip_account = @parent.sip_accounts.build + @sip_account.caller_name = @parent + @sip_account.call_waiting = CALL_WAITING + @sip_account.clir = DEFAULT_CLIR_SETTING + @sip_account.clip = DEFAULT_CLIP_SETTING + @sip_account.voicemail_pin = random_pin + @sip_account.callforward_rules_act_per_sip_account = CALLFORWARD_RULES_ACT_PER_SIP_ACCOUNT_DEFAULT + @sip_account.hotdeskable = true + + # Make sure that we don't use an already taken auth_name + # + loop do + @sip_account.auth_name = SecureRandom.hex(DEFAULT_LENGTH_SIP_AUTH_NAME) + + break unless SipAccount.exists?(:auth_name => @sip_account.auth_name) + end + @sip_account.password = SecureRandom.hex(DEFAULT_LENGTH_SIP_PASSWORD) + end + + def create + @sip_account = @parent.sip_accounts.build(params[:sip_account]) + + if @sip_account.auth_name.blank? + loop do + @sip_account.auth_name = SecureRandom.hex(DEFAULT_LENGTH_SIP_AUTH_NAME) + + break unless SipAccount.exists?(:auth_name => @sip_account.auth_name) + end + end + if @sip_account.password.blank? + @sip_account.password = SecureRandom.hex(DEFAULT_LENGTH_SIP_PASSWORD) + end + + if @sip_account.save + m = method( :"#{@parent.class.name.underscore}_sip_account_path" ) + redirect_to m.( @parent, @sip_account ), :notice => t('sip_accounts.controller.successfuly_created', :resource => @parent) + else + render :new + end + end + + def edit + end + + def update + if @sip_account.update_attributes(params[:sip_account]) + m = method( :"#{@parent.class.name.underscore}_sip_account_path" ) + redirect_to m.( @parent, @sip_account ), :notice => t('sip_accounts.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @sip_account.destroy + m = method( :"#{@parent.class.name.underscore}_sip_accounts_url" ) + redirect_to m.( @parent ), :notice => t('sip_accounts.controller.successfuly_destroyed') + end + + private + def set_and_authorize_parent + @parent = @user || @tenant + authorize! :read, @parent + end + + def spread_breadcrumbs + if @user + add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t("sip_accounts.index.page_title"), user_sip_accounts_path(@user) + if @sip_account && !@sip_account.new_record? + add_breadcrumb @sip_account, user_sip_account_path(@user, @sip_account) + end + end + if @tenant + add_breadcrumb t("sip_accounts.index.page_title"), tenant_sip_accounts_path(@tenant) + if @sip_account && !@sip_account.new_record? + add_breadcrumb @sip_account, tenant_sip_account_path(@tenant, @sip_account) + end + end + end + +end diff --git a/app/controllers/sip_domains_controller.rb b/app/controllers/sip_domains_controller.rb new file mode 100644 index 0000000..7301192 --- /dev/null +++ b/app/controllers/sip_domains_controller.rb @@ -0,0 +1,41 @@ +class SipDomainsController < ApplicationController + def index + @sip_domains = SipDomain.all + end + + def show + @sip_domain = SipDomain.find(params[:id]) + end + + def new + @sip_domain = SipDomain.new + end + + def create + @sip_domain = SipDomain.new(params[:sip_domain]) + if @sip_domain.save + redirect_to @sip_domain, :notice => t('sip_domains.controller.successfuly_created') + else + render :new + end + end + + def edit + @sip_domain = SipDomain.find(params[:id]) + end + + def update + @sip_domain = SipDomain.find(params[:id]) + if @sip_domain.update_attributes(params[:sip_domain]) + redirect_to @sip_domain, :notice => t('sip_domains.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @sip_domain = SipDomain.find(params[:id]) + @sip_domain.destroy + redirect_to sip_domains_url, :notice => t('sip_domains.controller.successfuly_destroyed') + end +end diff --git a/app/controllers/softkeys_controller.rb b/app/controllers/softkeys_controller.rb new file mode 100644 index 0000000..d2a2bb9 --- /dev/null +++ b/app/controllers/softkeys_controller.rb @@ -0,0 +1,91 @@ +class SoftkeysController < ApplicationController + load_and_authorize_resource :sip_account + load_and_authorize_resource :softkey, :through => [:sip_account] + + before_filter :set_available_call_forwards_and_softkey_functions, :only => [ :new, :edit, :update ] + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @softkey = @sip_account.softkeys.build + + delete_call_forward_softkey_if_no_callforward_is_available + end + + def create + @softkey = @sip_account.softkeys.build(params[:softkey]) + if @softkey.save + redirect_to sip_account_softkey_path(@softkey.sip_account, @softkey), :notice => t('softkeys.controller.successfuly_created') + else + render :new + end + end + + def edit + delete_call_forward_softkey_if_no_callforward_is_available + end + + def update + if @softkey.update_attributes(params[:softkey]) + redirect_to sip_account_softkey_path(@softkey.sip_account, @softkey), :notice => t('softkeys.controller.successfuly_updated') + else + delete_call_forward_softkey_if_no_callforward_is_available + render :edit + end + end + + def destroy + @softkey.destroy + redirect_to sip_account_softkeys_path(@softkey.sip_account), :notice => t('softkeys.controller.successfuly_destroyed') + end + + def move_higher + @softkey.move_higher + redirect_to :back + end + + def move_lower + @softkey.move_lower + redirect_to :back + end + + private + + def set_available_call_forwards_and_softkey_functions + @available_call_forwards = @softkey.possible_blf_call_forwards + + @softkey_functions = [] + SoftkeyFunction.accessible_by(current_ability, :read).each do |softkey_function| + if GuiFunction.display?("softkey_function_#{softkey_function.name.downcase}_field_in_softkey_form", @current_user) + @softkey_functions << softkey_function + end + end + end + + def spread_breadcrumbs + if @sip_account.sip_accountable.class == User + add_breadcrumb t('users.name'), tenant_users_path(@sip_account.sip_accountable.current_tenant) + add_breadcrumb @sip_account.sip_accountable, tenant_user_path(@sip_account.sip_accountable.current_tenant, @sip_account.sip_accountable) + add_breadcrumb t('sip_accounts.index.page_title'), user_sip_accounts_path(@sip_account.sip_accountable) + add_breadcrumb @sip_account, user_sip_account_path(@sip_account.sip_accountable, @sip_account) + add_breadcrumb t('softkeys.index.page_title'), sip_account_softkeys_path(@sip_account) + elsif @sip_account.sip_accountable.class == Tenant + add_breadcrumb t('sip_accounts.index.page_title'), tenant_sip_accounts_path(@sip_account.sip_accountable) + add_breadcrumb @sip_account, tenant_sip_account_path(@sip_account.sip_accountable, @sip_account) + add_breadcrumb t('softkeys.index.page_title'), sip_account_softkeys_path(@sip_account) + end + end + + def delete_call_forward_softkey_if_no_callforward_is_available + # Don't display the call_forward option if there aren't any call_forwards to choose from. + # + if @softkey.sip_account.phone_numbers.map{|phone_number| phone_number.call_forwards}.flatten.count == 0 + @softkey_functions.delete_if { |softkey_function| softkey_function == SoftkeyFunction.find_by_name('call_forwarding') } + end + end +end diff --git a/app/controllers/system_messages_controller.rb b/app/controllers/system_messages_controller.rb new file mode 100644 index 0000000..d7fe515 --- /dev/null +++ b/app/controllers/system_messages_controller.rb @@ -0,0 +1,30 @@ +class SystemMessagesController < ApplicationController + load_and_authorize_resource :user + load_and_authorize_resource :system_message, :through => [:user] + + def index + @system_messages = @system_messages.where(:created_at => Time.now - 6.hours .. Time.now) + end + + def show + end + + def new + @system_message = @user.system_messages.build + end + + def create + @system_message = @user.system_messages.build(params[:system_message]) + if @system_message.save + # Push the new message via AJAX to the browser. + # + # PrivatePub.publish_to("/users/#{@system_message.user.id}/system_messages", + # "$('#system_message').empty();$('#system_message').append('#{(I18n.l @system_message.created_at, :format => :short )} #{@system_message.content}');$('#system_message_display').fadeIn();" + # ) + + redirect_to user_system_message_path(@user, @system_message), :notice => t('system_messages.controller.successfuly_created') + else + render :new + end + end +end diff --git a/app/controllers/tenants_controller.rb b/app/controllers/tenants_controller.rb new file mode 100644 index 0000000..724d179 --- /dev/null +++ b/app/controllers/tenants_controller.rb @@ -0,0 +1,91 @@ +class TenantsController < ApplicationController + load_and_authorize_resource :tenant + + def index + end + + def show + end + + def new + @tenant.name = generate_a_new_name(@tenant) + @tenant.sip_domain = SipDomain.last + @tenant.country = GemeinschaftSetup.first.country + @tenant.language = GemeinschaftSetup.first.language + @tenant.internal_extension_ranges = '10-99' + @tenant.from_field_voicemail_email = 'admin@localhost' + @tenant.from_field_pin_change_email = 'admin@localhost' + end + + def create + if @tenant.save + # Become a member of this tenant. + # + @tenant.tenant_memberships.create(:user_id => @current_user.id) + + # Groups + # + admin_group = @tenant.user_groups.create(:name => t('gemeinschaft_setups.initial_setup.admin_group_name')) + admin_group.users << @current_user + + user_group = @tenant.user_groups.create(:name => t('gemeinschaft_setups.initial_setup.user_group_name')) + user_group.users << @current_user + + @current_user.update_attributes!(:current_tenant_id => @tenant.id) + + # Generate the internal_extensions + # + if !@tenant.internal_extension_ranges.blank? + if @tenant.array_of_internal_extension_numbers.count < 105 + # This can be done more or less quick. + @tenant.generate_internal_extensions + else + # Better be on the save side and start a delayed job for this. + @tenant.delay.generate_internal_extensions + end + end + + # Generate the external numbers (DIDs) + # + if !@tenant.did_list.blank? + if @tenant.array_of_dids.count < 105 + # This can be done more or less quick. + @tenant.generate_dids + else + # Better be on the save side and start a delayed job for this. + @tenant.delay.generate_dids + end + end + + if Delayed::Job.count > 0 + redirect_to @tenant, :notice => t('tenants.controller.successfuly_created_plus_delayed_jobs', + :resource => @tenant, + :amount_of_numbers => @tenant.array_of_internal_extension_numbers.count + @tenant.array_of_dids.count + ) + else + redirect_to @tenant, :notice => t('tenants.controller.successfuly_created', + :resource => @tenant + ) + end + else + render :new + end + end + + def edit + end + + def update + if @tenant.update_attributes(params[:tenant]) + redirect_to @tenant, :notice => t('tenants.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @tenant.destroy + redirect_to tenants_url, :notice => t('tenants.controller.successfuly_destroyed') + end + +end diff --git a/app/controllers/user_group_memberships_controller.rb b/app/controllers/user_group_memberships_controller.rb new file mode 100644 index 0000000..1cbbd48 --- /dev/null +++ b/app/controllers/user_group_memberships_controller.rb @@ -0,0 +1,48 @@ +class UserGroupMembershipsController < ApplicationController + load_and_authorize_resource :user_group + load_and_authorize_resource :user_group_membership, :through => [:user_group] + + before_filter :spread_breadcrumbs + + def index + @potential_users_count = @user_group.tenant.users.count - @user_group.users.count + end + + def show + end + + def new + @user_group_membership = @user_group.user_group_memberships.build + @potential_users = (@user_group.tenant.users.order(:last_name) - @user_group.users) + if @potential_users.count == 0 + redirect_to user_group_user_group_memberships_path(@user_group), :alert => t('user_group_memberships.controller.no_more_user_to_add') + end + end + + def create + @user_group_membership = @user_group.user_group_memberships.build(params[:user_group_membership]) + if @user_group_membership.save + redirect_to user_group_user_group_membership_path(@user_group, @user_group_membership), :notice => t('user_group_memberships.controller.successfuly_created') + else + render :new + end + end + + def destroy + @user_group_membership.destroy + redirect_to user_group_user_group_memberships_path(@user_group), :notice => t('user_group_memberships.controller.successfuly_destroyed') + end + + private + + def spread_breadcrumbs + add_breadcrumb t("user_groups.index.page_title"), tenant_user_groups_path(@user_group.tenant) + add_breadcrumb @user_group, tenant_user_group_path(@user_group.tenant, @user_group) + add_breadcrumb t("user_group_memberships.index.page_title"), user_group_user_group_memberships_path(@user_group) + + if @user_group_membership && !@user_group_membership.new_record? + add_breadcrumb @user_group_membership, user_group_user_group_membership_path(@user_group, @user_group_membership) + end + end + +end diff --git a/app/controllers/user_groups_controller.rb b/app/controllers/user_groups_controller.rb new file mode 100644 index 0000000..158abaa --- /dev/null +++ b/app/controllers/user_groups_controller.rb @@ -0,0 +1,69 @@ +class UserGroupsController < ApplicationController + load_resource :tenant + load_resource :user + load_and_authorize_resource :user_group, :through => [:tenant, :user] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @user_group = @parent.user_groups.build + end + + def create + @user_group = @parent.user_groups.build(params[:user_group]) + if @user_group.save + redirect_to @user_group, :notice => t('user_groups.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @user_group.update_attributes(params[:user_group]) + redirect_to @user_group, :notice => t('user_groups.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @user_group.destroy + redirect_to user_groups_url, :notice => t('user_groups.controller.successfuly_destroyed') + end + + private + + def set_and_authorize_parent + @parent = @user || @tenant + authorize! :read, @parent + end + + def spread_breadcrumbs + if @tenant + add_breadcrumb t("user_groups.index.page_title"), tenant_user_groups_path(@tenant) + if @user_group && !@user_group.new_record? + add_breadcrumb @user_group, tenant_user_group_path(@tenant, @user_group) + end + end + + if @user + add_breadcrumb t("users.index.page_title"), tenant_users_path(@parent) + add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user) + add_breadcrumb t("user_groups.index.page_title"), user_user_groups_path(@user) + if @user_group && !@user_group.new_record? + add_breadcrumb @user_group, user_user_group_path(@user, @user_group) + end + end + end + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..454c26b --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,85 @@ +class UsersController < ApplicationController + load_resource :tenant + load_resource :user_group + load_and_authorize_resource :user, :through => [:tenant, :user_group] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + def index + end + + def show + @phone_books = PhoneBook.accessible_by( Ability.new( @user ) ).all + end + + def new + @user = @parent.users.build(params[:user]) + @user.male = true + @user.send_voicemail_as_email_attachment = true + end + + def create + @user = @parent.users.build(params[:user]) + if @user.save + if @parent.class == Tenant + @parent.tenant_memberships.create(:user => @user) + if @parent.user_groups.exists?(:name => 'Users') + @parent.user_groups.where(:name => 'Users').first.user_group_memberships.create(:user => @user) + end + redirect_to tenant_user_url( @parent, @user), :notice => t('users.controller.successfuly_created', :resource => @user) + else + redirect_to tenant_user_path(@user.current_tenant, @user), :notice => t('users.controller.successfuly_created_and_login', :resource => @user) + end + else + render :new + end + end + + def edit + end + + def update + if @user.update_attributes(params[:user]) + # Make sure that the flash notice gets rendered in the correct language. + I18n.locale = @user.language.code.downcase + + redirect_to tenant_user_path(@user.current_tenant, @user), :notice => t('users.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @user.destroy + redirect_to @parent, :notice => t('users.controller.successfuly_destroyed') + end + + def destroy_avatar + user = User.find(params[:user_id]) + user.remove_image = true # https://github.com/jnicklas/carrierwave/issues/360 + user.remove_image! + user.save + user.reload + user.image.remove! + user.save + redirect_to @parent, :notice => t('users.controller.avatar_destroyed') + end + + private + def set_and_authorize_parent + @parent = @tenant || @user_group + authorize! :read, @parent + end + + def spread_breadcrumbs + if @tenant + add_breadcrumb t("users.index.page_title"), tenant_users_path(@tenant) + + if @user && !@user.new_record? + add_breadcrumb @user, tenant_user_path(@tenant, @user) + end + end + end + +end diff --git a/app/controllers/voicemail_messages_controller.rb b/app/controllers/voicemail_messages_controller.rb new file mode 100644 index 0000000..58f5265 --- /dev/null +++ b/app/controllers/voicemail_messages_controller.rb @@ -0,0 +1,140 @@ +class VoicemailMessagesController < ApplicationController + + load_resource :sip_account + load_and_authorize_resource :voicemail_message, :through => [:sip_account] + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + + before_filter { |controller| + if ! params[:type].blank? then + @type = params[:type].to_s + end + + if ! params[:page].blank? then + @pagination_page_number = params[:page].to_i + end + } + + def index + @messages_count = @sip_account.voicemail_messages.count + @messages_unread_count = @sip_account.voicemail_messages.where(:read_epoch => 0).count + @messages_read_count = @messages_count - @messages_unread_count + + if @type == 'read' + @voicemail_messages = @sip_account.voicemail_messages.where('read_epoch > 0').order('created_epoch DESC').paginate( + :page => @pagination_page_number, + :per_page => DEFAULT_PAGINATION_ENTRIES_PER_PAGE + ) + elsif @type == 'unread' + @voicemail_messages = @sip_account.voicemail_messages.where(:read_epoch => 0).order('created_epoch DESC').paginate( + :page => @pagination_page_number, + :per_page => DEFAULT_PAGINATION_ENTRIES_PER_PAGE + ) + else + @voicemail_messages = @sip_account.voicemail_messages.order('created_epoch DESC').paginate( + :page => @pagination_page_number, + :per_page => DEFAULT_PAGINATION_ENTRIES_PER_PAGE + ) + end + end + + def show + respond_to do |format| + format.wav { + if @voicemail_message.file_path + send_file @voicemail_message.file_path, :type => "audio/x-wav", + :filename => "#{Time.at(@voicemail_message.created_epoch).strftime('%Y%m%d-%H%M%S')}-#{@voicemail_message.cid_number}.wav" + else + render( + :status => 404, + :layout => false, + :content_type => 'text/plain', + :text => "", + ) + end + } + end + end + + def new + end + + def create + end + + def edit + end + + def update + end + + def destroy + @voicemail_message.destroy + m = method( :"#{@parent.class.name.underscore}_voicemail_messages_url" ) + redirect_to m.(), :notice => t('voicemail_messages.controller.successfuly_destroyed') + end + + def destroy_multiple + result = false + if ! params[:selected_uuids].blank? then + voicemail_messages = @sip_account.voicemail_messages.where(:uuid => params[:selected_uuids]) + voicemail_messages.each do |voicemail_message| + result = voicemail_message.destroy + end + end + + m = method( :"#{@parent.class.name.underscore}_voicemail_messages_url" ) + if result + redirect_to m.(), :notice => t('voicemail_messages.controller.successfuly_destroyed') + else + redirect_to m.() + end + end + + def call + phone_number = @voicemail_message.cid_number + if ! phone_number.blank? && @sip_account.registration + @sip_account.call(phone_number) + end + redirect_to(:back) + end + + def mark_read + @voicemail_message.mark_read + redirect_to(:back) + end + + def mark_unread + @voicemail_message.mark_read(false) + redirect_to(:back) + end + + private + def set_and_authorize_parent + @parent = @sip_account + + authorize! :read, @parent + + @show_path_method = method( :"#{@parent.class.name.underscore}_voicemail_message_path" ) + @index_path_method = method( :"#{@parent.class.name.underscore}_voicemail_messages_path" ) + @new_path_method = method( :"new_#{@parent.class.name.underscore}_voicemail_message_path" ) + @edit_path_method = method( :"edit_#{@parent.class.name.underscore}_voicemail_message_path" ) + end + + def spread_breadcrumbs + if @parent.class == SipAccount + if @sip_account.sip_accountable.class == User + add_breadcrumb t("#{@sip_account.sip_accountable.class.name.underscore.pluralize}.index.page_title"), method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore.pluralize}_path" ).(@sip_account.tenant) + add_breadcrumb @sip_account.sip_accountable, method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore}_path" ).(@sip_account.tenant, @sip_account.sip_accountable) + end + add_breadcrumb t("sip_accounts.index.page_title"), method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_accounts_path" ).(@sip_account.sip_accountable) + add_breadcrumb @sip_account, method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_account_path" ).(@sip_account.sip_accountable, @sip_account) + add_breadcrumb t("voicemail_messages.index.page_title"), sip_account_voicemail_messages_path(@sip_account) + if @voicemail_message && !@voicemail_message.new_record? + add_breadcrumb @voicemail_message, sip_account_voicemail_message_path(@sip_account, @voicemail_message) + end + end + end + +end diff --git a/app/controllers/voicemail_settings_controller.rb b/app/controllers/voicemail_settings_controller.rb new file mode 100644 index 0000000..d31de8f --- /dev/null +++ b/app/controllers/voicemail_settings_controller.rb @@ -0,0 +1,91 @@ +class VoicemailSettingsController < ApplicationController + load_resource :sip_account + load_and_authorize_resource :voicemail_setting, :through => :sip_account, :singleton => true + + before_filter :set_and_authorize_parent + before_filter :spread_breadcrumbs + before_filter :voicemail_defaults, :only => [:index, :show, :new, :create, :edit] + + def index + render :edit + end + + def show + render :edit + end + + def new + render :edit + end + + def create + @sip_account = SipAccount.where(:id => params[:sip_account_id]).first + params[:voicemail_setting][:username] = @sip_account.auth_name + params[:voicemail_setting][:domain] = @sip_account.sip_domain.try(:host) + @voicemail_setting = VoicemailSetting.new(params[:voicemail_setting]) + if @voicemail_setting.save + redirect_to sip_account_voicemail_settings_path(@sip_account), :notice => t('voicemail_settings.controller.successfuly_created') + else + render :action => 'edit' + end + end + + def edit + + end + + def update + if @voicemail_setting.update_attributes(params[:voicemail_setting]) + redirect_to sip_account_voicemail_settings_path(@sip_account), :notice => t('voicemail_settings.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + + end + + private + def set_and_authorize_parent + @parent = @sip_account + + authorize! :read, @parent + + @show_path_method = method( :"#{@parent.class.name.underscore}_voicemail_setting_path" ) + @index_path_method = method( :"#{@parent.class.name.underscore}_voicemail_settings_path" ) + @new_path_method = method( :"new_#{@parent.class.name.underscore}_voicemail_setting_path" ) + @edit_path_method = method( :"edit_#{@parent.class.name.underscore}_voicemail_setting_path" ) + end + + def spread_breadcrumbs + if @parent.class == SipAccount + if @sip_account.sip_accountable.class == User + add_breadcrumb t("#{@sip_account.sip_accountable.class.name.underscore.pluralize}.index.page_title"), method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore.pluralize}_path" ).(@sip_account.tenant) + add_breadcrumb @sip_account.sip_accountable, method( :"tenant_#{@sip_account.sip_accountable.class.name.underscore}_path" ).(@sip_account.tenant, @sip_account.sip_accountable) + end + add_breadcrumb t("sip_accounts.index.page_title"), method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_accounts_path" ).(@sip_account.sip_accountable) + add_breadcrumb @sip_account, method( :"#{@sip_account.sip_accountable.class.name.underscore}_sip_account_path" ).(@sip_account.sip_accountable, @sip_account) + add_breadcrumb t("voicemail_settings.index.page_title"), sip_account_voicemail_settings_path(@sip_account) + end + end + + def voicemail_defaults + path = "/opt/freeswitch/storage/voicemail/default/#{@sip_account.sip_domain.host}/#{@sip_account.auth_name}/" + @greeting_files = Dir.glob("#{path}*greeting*.wav").collect {|r| [ File.basename(r), File.expand_path(r) ] } + @name_files = Dir.glob("#{path}*name*.wav").collect {|r| [ File.basename(r), File.expand_path(r) ] } + + if @voicemail_setting.blank? then + @voicemail_setting = @sip_account.voicemail_setting + end + + if @voicemail_setting.blank? + @voicemail_setting = VoicemailSetting.new + @voicemail_setting.notify = true + @voicemail_setting.attachment = true + @voicemail_setting.mark_read = true + @voicemail_setting.purge = false + end + end + +end diff --git a/app/controllers/whitelists_controller.rb b/app/controllers/whitelists_controller.rb new file mode 100644 index 0000000..0526844 --- /dev/null +++ b/app/controllers/whitelists_controller.rb @@ -0,0 +1,61 @@ +class WhitelistsController < ApplicationController + load_and_authorize_resource :callthrough + load_and_authorize_resource :whitelist, :through => [:callthrough] + + before_filter :set_parent_and_path_methods + before_filter :spread_breadcrumbs + + def index + end + + def show + end + + def new + @whitelist.phone_numbers.build + end + + def create + @whitelist = @parent.whitelists.build(params[:whitelist]) + if @whitelist.save + redirect_to @show_path_method.(@parent, @whitelist), :notice => t('whitelists.controller.successfuly_created') + else + render :new + end + end + + def edit + end + + def update + if @whitelist.update_attributes(params[:whitelist]) + redirect_to @show_path_method.(@parent, @whitelist), :notice => t('whitelists.controller.successfuly_updated') + else + render :edit + end + end + + def destroy + @whitelist.destroy + redirect_to @index_path_method.(@parent), :notice => t('whitelists.controller.successfuly_destroyed') + end + + private + + def set_parent_and_path_methods + @parent = @callthrough + @show_path_method = method( :"#{@parent.class.name.underscore}_whitelist_path" ) + @index_path_method = method( :"#{@parent.class.name.underscore}_whitelists_path" ) + @new_path_method = method( :"new_#{@parent.class.name.underscore}_whitelist_path" ) + @edit_path_method = method( :"edit_#{@parent.class.name.underscore}_whitelist_path" ) + end + + def spread_breadcrumbs + if @parent && @parent.class == Callthrough + add_breadcrumb t("#{@parent.class.name.underscore.pluralize}.name").pluralize, tenant_callthroughs_path(@parent.tenant) + add_breadcrumb @callthrough, tenant_callthrough_path(@parent.tenant, @callthrough) + add_breadcrumb t("whitelists.index.page_title"), callthrough_whitelists_path(@parent) + end + end + +end diff --git a/app/helpers/access_authorizations_helper.rb b/app/helpers/access_authorizations_helper.rb new file mode 100644 index 0000000..d16f5c6 --- /dev/null +++ b/app/helpers/access_authorizations_helper.rb @@ -0,0 +1,2 @@ +module AccessAuthorizationsHelper +end diff --git a/app/helpers/acd_agents_helper.rb b/app/helpers/acd_agents_helper.rb new file mode 100644 index 0000000..5be92a8 --- /dev/null +++ b/app/helpers/acd_agents_helper.rb @@ -0,0 +1,2 @@ +module AcdAgentsHelper +end diff --git a/app/helpers/acd_callers_helper.rb b/app/helpers/acd_callers_helper.rb new file mode 100644 index 0000000..534b99c --- /dev/null +++ b/app/helpers/acd_callers_helper.rb @@ -0,0 +1,2 @@ +module AcdCallersHelper +end diff --git a/app/helpers/addresses_helper.rb b/app/helpers/addresses_helper.rb new file mode 100644 index 0000000..5f4dc13 --- /dev/null +++ b/app/helpers/addresses_helper.rb @@ -0,0 +1,2 @@ +module AddressesHelper +end diff --git a/app/helpers/api/rows_helper.rb b/app/helpers/api/rows_helper.rb new file mode 100644 index 0000000..a18dab4 --- /dev/null +++ b/app/helpers/api/rows_helper.rb @@ -0,0 +1,2 @@ +module Api::RowsHelper +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..de6be79 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/helpers/automatic_call_distributors_helper.rb b/app/helpers/automatic_call_distributors_helper.rb new file mode 100644 index 0000000..19a9828 --- /dev/null +++ b/app/helpers/automatic_call_distributors_helper.rb @@ -0,0 +1,2 @@ +module AutomaticCallDistributorsHelper +end diff --git a/app/helpers/call_forward_cases_helper.rb b/app/helpers/call_forward_cases_helper.rb new file mode 100644 index 0000000..63a4939 --- /dev/null +++ b/app/helpers/call_forward_cases_helper.rb @@ -0,0 +1,2 @@ +module CallForwardCasesHelper +end diff --git a/app/helpers/call_forwards_helper.rb b/app/helpers/call_forwards_helper.rb new file mode 100644 index 0000000..ffb6977 --- /dev/null +++ b/app/helpers/call_forwards_helper.rb @@ -0,0 +1,2 @@ +module CallForwardsHelper +end diff --git a/app/helpers/callthroughs_helper.rb b/app/helpers/callthroughs_helper.rb new file mode 100644 index 0000000..2ee0b9b --- /dev/null +++ b/app/helpers/callthroughs_helper.rb @@ -0,0 +1,2 @@ +module CallthroughsHelper +end diff --git a/app/helpers/conference_invitees_helper.rb b/app/helpers/conference_invitees_helper.rb new file mode 100644 index 0000000..dab6843 --- /dev/null +++ b/app/helpers/conference_invitees_helper.rb @@ -0,0 +1,2 @@ +module ConferenceInviteesHelper +end diff --git a/app/helpers/conferences_helper.rb b/app/helpers/conferences_helper.rb new file mode 100644 index 0000000..edfcfdd --- /dev/null +++ b/app/helpers/conferences_helper.rb @@ -0,0 +1,2 @@ +module ConferencesHelper +end diff --git a/app/helpers/config_siemens_helper.rb b/app/helpers/config_siemens_helper.rb new file mode 100644 index 0000000..7ff8c5b --- /dev/null +++ b/app/helpers/config_siemens_helper.rb @@ -0,0 +1,2 @@ +module ConfigSiemensHelper +end diff --git a/app/helpers/error_messages_helper.rb b/app/helpers/error_messages_helper.rb new file mode 100644 index 0000000..8e9c4d3 --- /dev/null +++ b/app/helpers/error_messages_helper.rb @@ -0,0 +1,23 @@ +module ErrorMessagesHelper + # Render error messages for the given objects. The :message and :header_message options are allowed. + def error_messages_for(*objects) + options = objects.extract_options! + options[:header_message] ||= I18n.t(:"activerecord.errors.header", :default => "Invalid Fields") + options[:message] ||= I18n.t(:"activerecord.errors.message", :default => "Correct the following errors and try again.") + messages = objects.compact.map { |o| o.errors.full_messages }.flatten + unless messages.empty? + content_tag(:div, :class => "error_messages") do + list_items = messages.map { |msg| content_tag(:li, msg.html_safe) } + content_tag(:h2, options[:header_message].html_safe) + content_tag(:p, options[:message].html_safe) + content_tag(:ul, list_items.join.html_safe) + end + end + end + + module FormBuilderAdditions + def error_messages(options = {}) + @template.error_messages_for(@object, options) + end + end +end + +ActionView::Helpers::FormBuilder.send(:include, ErrorMessagesHelper::FormBuilderAdditions) diff --git a/app/helpers/fax_accounts_helper.rb b/app/helpers/fax_accounts_helper.rb new file mode 100644 index 0000000..529c4fb --- /dev/null +++ b/app/helpers/fax_accounts_helper.rb @@ -0,0 +1,2 @@ +module FaxAccountsHelper +end diff --git a/app/helpers/fax_documents_helper.rb b/app/helpers/fax_documents_helper.rb new file mode 100644 index 0000000..c168948 --- /dev/null +++ b/app/helpers/fax_documents_helper.rb @@ -0,0 +1,2 @@ +module FaxDocumentsHelper +end diff --git a/app/helpers/gemeinschaft_setups_helper.rb b/app/helpers/gemeinschaft_setups_helper.rb new file mode 100644 index 0000000..f241900 --- /dev/null +++ b/app/helpers/gemeinschaft_setups_helper.rb @@ -0,0 +1,2 @@ +module GemeinschaftSetupsHelper +end diff --git a/app/helpers/gs_cluster_sync_log_entries_helper.rb b/app/helpers/gs_cluster_sync_log_entries_helper.rb new file mode 100644 index 0000000..9eef5de --- /dev/null +++ b/app/helpers/gs_cluster_sync_log_entries_helper.rb @@ -0,0 +1,2 @@ +module GsClusterSyncLogEntriesHelper +end diff --git a/app/helpers/gs_nodes_helper.rb b/app/helpers/gs_nodes_helper.rb new file mode 100644 index 0000000..9ba2a39 --- /dev/null +++ b/app/helpers/gs_nodes_helper.rb @@ -0,0 +1,2 @@ +module GsNodesHelper +end diff --git a/app/helpers/gui_functions_helper.rb b/app/helpers/gui_functions_helper.rb new file mode 100644 index 0000000..35324cd --- /dev/null +++ b/app/helpers/gui_functions_helper.rb @@ -0,0 +1,2 @@ +module GuiFunctionsHelper +end diff --git a/app/helpers/hunt_group_members_helper.rb b/app/helpers/hunt_group_members_helper.rb new file mode 100644 index 0000000..e198542 --- /dev/null +++ b/app/helpers/hunt_group_members_helper.rb @@ -0,0 +1,2 @@ +module HuntGroupMembersHelper +end diff --git a/app/helpers/hunt_groups_helper.rb b/app/helpers/hunt_groups_helper.rb new file mode 100644 index 0000000..d1b3b05 --- /dev/null +++ b/app/helpers/hunt_groups_helper.rb @@ -0,0 +1,2 @@ +module HuntGroupsHelper +end diff --git a/app/helpers/layout_helper.rb b/app/helpers/layout_helper.rb new file mode 100644 index 0000000..1dad619 --- /dev/null +++ b/app/helpers/layout_helper.rb @@ -0,0 +1,70 @@ +# These helper methods can be called in your template to set +# variables to be used in the layout. +# This module should be included in all views globally, +# to do so you may need to add this line to your +# ApplicationController +# helper :layout +# +module LayoutHelper + + def title( page_title, show_title = true ) + content_for(:title) { strip_tags(page_title.to_s) } + @show_title = show_title + end + + def show_title? + @show_title + end + + def stylesheet( *args ) + content_for(:head) { stylesheet_link_tag( *args ) } + end + + def javascript( *args ) + content_for(:head) { javascript_include_tag( *args ) } + end + + def translation_missing?( output ) + (output =~ /span/ or output.empty?) + end + + def conditional_hint( translation_key ) + output = t( translation_key ) + return output unless translation_missing?( output ) + false + end + + def conditional_t( translation_key ) + output = t( translation_key ) + strip_tags( output ) + end + + def resolve_flash_sign( type ) + return case type.to_s + when 'alert' ; '!' + when 'warning' ; '!' + else ; 'i' + end + end + + # Returns navigation as an array. + # + def navigation_items + unless @io + @io = [] + + if can?( :index, PhoneBookEntry ) + @io << { :url => phone_book_entries_path , :title => t('phone_book_entries.index.page_title' ) } + end + + # This could be a link to VoiceMails. + # + # if can?( :index, Object ) + # @io << { :url => "#" , :title => t('voice_mail') } + # end + + end + @io + end + +end diff --git a/app/helpers/manufacturers_helper.rb b/app/helpers/manufacturers_helper.rb new file mode 100644 index 0000000..3f9e083 --- /dev/null +++ b/app/helpers/manufacturers_helper.rb @@ -0,0 +1,2 @@ +module ManufacturersHelper +end diff --git a/app/helpers/page_helper.rb b/app/helpers/page_helper.rb new file mode 100644 index 0000000..625cfe4 --- /dev/null +++ b/app/helpers/page_helper.rb @@ -0,0 +1,2 @@ +module PageHelper +end diff --git a/app/helpers/phone_book_entries_helper.rb b/app/helpers/phone_book_entries_helper.rb new file mode 100644 index 0000000..db24cae --- /dev/null +++ b/app/helpers/phone_book_entries_helper.rb @@ -0,0 +1,2 @@ +module PhoneBookEntriesHelper +end diff --git a/app/helpers/phone_books_helper.rb b/app/helpers/phone_books_helper.rb new file mode 100644 index 0000000..55ebf19 --- /dev/null +++ b/app/helpers/phone_books_helper.rb @@ -0,0 +1,2 @@ +module PhoneBooksHelper +end diff --git a/app/helpers/phone_models_helper.rb b/app/helpers/phone_models_helper.rb new file mode 100644 index 0000000..2cc6545 --- /dev/null +++ b/app/helpers/phone_models_helper.rb @@ -0,0 +1,2 @@ +module PhoneModelsHelper +end diff --git a/app/helpers/phone_number_ranges_helper.rb b/app/helpers/phone_number_ranges_helper.rb new file mode 100644 index 0000000..f4d5897 --- /dev/null +++ b/app/helpers/phone_number_ranges_helper.rb @@ -0,0 +1,2 @@ +module PhoneNumberRangesHelper +end diff --git a/app/helpers/phone_numbers_helper.rb b/app/helpers/phone_numbers_helper.rb new file mode 100644 index 0000000..cb4f200 --- /dev/null +++ b/app/helpers/phone_numbers_helper.rb @@ -0,0 +1,2 @@ +module PhoneNumbersHelper +end diff --git a/app/helpers/phone_sip_accounts_helper.rb b/app/helpers/phone_sip_accounts_helper.rb new file mode 100644 index 0000000..f834d2c --- /dev/null +++ b/app/helpers/phone_sip_accounts_helper.rb @@ -0,0 +1,2 @@ +module PhoneSipAccountsHelper +end diff --git a/app/helpers/phones_helper.rb b/app/helpers/phones_helper.rb new file mode 100644 index 0000000..69ebd13 --- /dev/null +++ b/app/helpers/phones_helper.rb @@ -0,0 +1,2 @@ +module PhonesHelper +end diff --git a/app/helpers/phones_sip_accounts_helper.rb b/app/helpers/phones_sip_accounts_helper.rb new file mode 100644 index 0000000..03e3fd2 --- /dev/null +++ b/app/helpers/phones_sip_accounts_helper.rb @@ -0,0 +1,2 @@ +module PhonesSipAccountsHelper +end diff --git a/app/helpers/ringtones_helper.rb b/app/helpers/ringtones_helper.rb new file mode 100644 index 0000000..33deac9 --- /dev/null +++ b/app/helpers/ringtones_helper.rb @@ -0,0 +1,2 @@ +module RingtonesHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..309f8b2 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/sip_accounts_helper.rb b/app/helpers/sip_accounts_helper.rb new file mode 100644 index 0000000..1e666fd --- /dev/null +++ b/app/helpers/sip_accounts_helper.rb @@ -0,0 +1,2 @@ +module SipAccountsHelper +end diff --git a/app/helpers/sip_domains_helper.rb b/app/helpers/sip_domains_helper.rb new file mode 100644 index 0000000..c1d85ee --- /dev/null +++ b/app/helpers/sip_domains_helper.rb @@ -0,0 +1,2 @@ +module SipDomainsHelper +end diff --git a/app/helpers/softkeys_helper.rb b/app/helpers/softkeys_helper.rb new file mode 100644 index 0000000..e551779 --- /dev/null +++ b/app/helpers/softkeys_helper.rb @@ -0,0 +1,2 @@ +module SoftkeysHelper +end diff --git a/app/helpers/system_messages_helper.rb b/app/helpers/system_messages_helper.rb new file mode 100644 index 0000000..fef2386 --- /dev/null +++ b/app/helpers/system_messages_helper.rb @@ -0,0 +1,2 @@ +module SystemMessagesHelper +end diff --git a/app/helpers/tenants_helper.rb b/app/helpers/tenants_helper.rb new file mode 100644 index 0000000..b7bb45d --- /dev/null +++ b/app/helpers/tenants_helper.rb @@ -0,0 +1,2 @@ +module TenantsHelper +end diff --git a/app/helpers/user_groups_helper.rb b/app/helpers/user_groups_helper.rb new file mode 100644 index 0000000..83cd8f3 --- /dev/null +++ b/app/helpers/user_groups_helper.rb @@ -0,0 +1,2 @@ +module UserGroupsHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/app/helpers/whitelists_helper.rb b/app/helpers/whitelists_helper.rb new file mode 100644 index 0000000..083be60 --- /dev/null +++ b/app/helpers/whitelists_helper.rb @@ -0,0 +1,2 @@ +module WhitelistsHelper +end diff --git a/app/mailers/.gitkeep b/app/mailers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/mailers/notifications.rb b/app/mailers/notifications.rb new file mode 100644 index 0000000..2c7f2ce --- /dev/null +++ b/app/mailers/notifications.rb @@ -0,0 +1,110 @@ +class Notifications < ActionMailer::Base + default from: "admin@example.com" + + # Subject can be set in your I18n file at config/locales/en.yml + # with the following lookup: + # + # en.notifications.new_pin.subject + # + def new_pin(conference) + @conference = conference + + @pin = Hash.new() + if conference.conferenceable_type == 'User' + user = conference.conferenceable + + if ! user.first_name.blank? + @pin[:greeting] = user.first_name + else + @pin[:greeting] = user.user_name + end + else + @pin[:greeting] = conference.conferenceable.to_s + end + + @pin[:conference] = conference.to_s + @pin[:pin] = conference.pin + @pin[:phone_numbers] = conference.phone_numbers.join(', ') + + mail(from: Tenant.find(DEFAULT_API_TENANT_ID).from_field_pin_change_email,to: "#{conference.conferenceable.email}", :subject => "Conference PIN changed: #{@pin[:conference]}") + end + + def new_password(user, password) + @password = password + + @message = Hash.new() + if ! user.first_name.blank? + @message[:greeting] = user.first_name + else + @message[:greeting] = user.user_name + end + + mail(from: Tenant.find(DEFAULT_API_TENANT_ID).from_field_pin_change_email, to: "#{user.email}", :subject => "Password recovery") + end + + def new_voicemail(freeswitch_voicemail_msg, attach_file = false) + sip_account = SipAccount.find_by_auth_name(freeswitch_voicemail_msg.username) + user = sip_account.sip_accountable + + @voicemail = Hash.new() + if ! user.first_name.blank? + @voicemail[:greeting] = user.first_name + else + @voicemail[:greeting] = user.user_name + end + + @voicemail[:destination] = freeswitch_voicemail_msg.in_folder + @voicemail[:from] = "#{freeswitch_voicemail_msg.cid_number} #{freeswitch_voicemail_msg.cid_name}" + @voicemail[:to] = sip_account.to_s + @voicemail[:date] = Time.at(freeswitch_voicemail_msg.created_epoch).getlocal.to_s + @voicemail[:duration] = Time.at(freeswitch_voicemail_msg.message_len).utc.strftime('%T') + + if attach_file + caller_number = freeswitch_voicemail_msg.cid_number.gsub(/[^0-9]/, '') + if caller_number.blank? + caller_number = 'anonymous' + end + attachments["#{Time.at(freeswitch_voicemail_msg.created_epoch).getlocal.strftime('%Y%m%d-%H%M%S')}-#{caller_number}.wav"] = File.read(freeswitch_voicemail_msg.file_path) + end + + mail(from: Tenant.find(DEFAULT_API_TENANT_ID).from_field_voicemail_email, to: "#{user.email}", :subject => "New Voicemail from #{@voicemail[:from]}, received #{Time.at(freeswitch_voicemail_msg.created_epoch).getlocal.to_s}") + end + + def new_fax(fax_document) + fax_account = fax_document.fax_account + + if !fax_account || fax_account.email.blank? + return false + end + + caller_number = fax_document.caller_id_number.gsub(/[^0-9]/, '') + if caller_number.blank? + caller_number = 'anonymous' + end + + @fax = { + :greeting => '', + :account_name => fax_account.name, + :from => "#{caller_number} #{fax_document.caller_id_name}", + :remote_station_id => fax_document.remote_station_id, + :local_station_id => fax_document.local_station_id, + :date => fax_document.created_at, + } + + if fax_account.fax_accountable + if fax_account.fax_accountable_type == 'User' + user = fax_account.fax_accountable + if ! user.first_name.blank? + @fax[:greeting] = user.first_name + else + @fax[:greeting] = user.user_name + end + elsif fax_account.fax_accountable_type == 'Tenant' + @fax[:greeting] = fax_account.fax_accountable.name + end + end + attachments["#{fax_document.created_at.strftime('%Y%m%d-%H%M%S')}-#{caller_number}.pdf"] = File.read(fax_document.document.path) + mail(from: Tenant.find(DEFAULT_API_TENANT_ID).from_field_voicemail_email, to: "#{fax_account.email}", :subject => "New Fax Document from #{@fax[:from]}, received #{fax_document.created_at}") + end + +end diff --git a/app/models/.gitkeep b/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 0000000..d9ec74a --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,170 @@ +class Ability + include CanCan::Ability + + def initialize( user ) + # See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities + if user && user.current_tenant != nil + if GemeinschaftSetup.count == 1 && Tenant.count == 1 && User.count == 1 && UserGroup.count == 1 + # This is a new installation with a Master-Tenant and a Super-Admin. + # + can [:read, :create], Tenant + else + tenant = user.current_tenant + + if user.current_tenant.user_groups.where(:name => 'Admins').first \ + && user.current_tenant.user_groups.where(:name => 'Admins').first.users.include?(user) + # ADMIN ABILITIES + # With great power comes great responsibility! + # + can :manage, :all + + # Manufacturers and PhoneModels can not be changed + # + cannot [:create, :destroy, :edit, :update], Manufacturer + cannot [:create, :destroy, :edit, :update], PhoneModel + + # Super-Tenant can not be destroyed or edited + # + cannot [:create, :destroy, :edit, :update], Tenant, :id => 1 + + cannot :manage, PhoneBook + + # Phonebooks and PhoneBookEntries + # + can :manage, PhoneBook, :phone_bookable_type => 'Tenant', :phone_bookable_id => tenant.id + can :manage, PhoneBookEntry, :phone_book => { :phone_bookable_type => 'Tenant', :phone_bookable_id => tenant.id } + + can :manage, PhoneBook, :phone_bookable_type => 'UserGroup', :phone_bookable_id => tenant.user_group_ids + tenant.user_groups.each do |user_group| + can :manage, PhoneBookEntry, :phone_book => { :id => user_group.phone_book_ids } + end + + # Personal Phonebooks and PhoneBookEntries + # + can :manage, PhoneBook, :phone_bookable_type => 'User', :phone_bookable_id => user.id + can :manage, PhoneBookEntry, :phone_book => { :phone_bookable_type => 'User', :phone_bookable_id => user.id } + + can :read, PhoneBook, :phone_bookable_type => 'Tenant', :phone_bookable_id => tenant.id + can :read, PhoneBookEntry, :phone_book => { :phone_bookable_type => 'Tenant', :phone_bookable_id => tenant.id } + + can :read, PhoneBook, :phone_bookable_type => 'UserGroup', :phone_bookable_id => user.user_group_ids + user.user_groups.each do |user_group| + can :read, PhoneBookEntry, :phone_book => { :id => user_group.phone_book_ids } + end + + # SystemMessages + # + cannot [:destroy, :edit, :update], SystemMessage + + # A FacDocument can't be changed + # + cannot [:edit, :update], FaxDocument + + # Can manage GsNodes + # + can :manage, GsNode + + # Can't phones/1/phone_sip_accounts/1/edit + # + cannot :edit, PhoneSipAccount + + # Dirty hack to disable PhoneNumberRange in the GUI + # + if STRICT_INTERNAL_EXTENSION_HANDLING == false + cannot :manage, PhoneNumberRange + end + else + # Any user can do the following stuff. + # + + # Own Tenant and own User + # + can :read, Tenant, :id => user.current_tenant.id + can [ :read, :edit, :update ], User, :id => user.id + + # Destroy his own avatar + # + can :destroy_avatar, User, :id => user.id + + # Phonebooks and PhoneBookEntries + # + cannot :manage, PhoneBook + + can :manage, PhoneBook, :phone_bookable_type => 'User', :phone_bookable_id => user.id + can :manage, PhoneBookEntry, :phone_book => { :phone_bookable_type => 'User', :phone_bookable_id => user.id } + can :manage, PhoneNumber, :phone_numberable_type => 'PhoneBookEntry', :phone_numberable_id => user.phone_books.map{ |phone_book| phone_book.phone_book_entry_ids}.flatten + + can :read, PhoneBook, :phone_bookable_type => 'Tenant', :phone_bookable_id => tenant.id + can :read, PhoneBookEntry, :phone_book => { :phone_bookable_type => 'Tenant', :phone_bookable_id => tenant.id } + + can :read, PhoneBook, :phone_bookable_type => 'UserGroup', :phone_bookable_id => user.user_group_ids + user.user_groups.each do |user_group| + can :read, PhoneBookEntry, :phone_book => { :id => user_group.phone_book_ids } + end + + # UserGroups + # + can :read, UserGroupMembership, :user_id => user.id + can :read, UserGroup, :users => { :user_group_memberships => { :user_id => user.id }} + + # SipAccounts and Phones + # + can :read, SipAccount, :sip_accountable_type => 'User', :sip_accountable_id => user.id + user.sip_accounts.each do |sip_account| + can :read, PhoneNumber, :id => sip_account.phone_number_ids + can :manage, CallForward, :phone_number_id => sip_account.phone_number_ids + can :manage, Ringtone, :ringtoneable_type => 'PhoneNumber', :ringtoneable_id => sip_account.phone_number_ids + can [:read, :destroy, :call] , CallHistory, :id => sip_account.call_history_ids + end + can :read, Phone, :phoneable_type => 'User', :phoneable_id => user.id + + # Softkeys + # + can :manage, Softkey, :sip_account => { :id => user.sip_account_ids } + + # Fax + # + can :read, FaxAccount, :fax_accountable_type => 'User', :fax_accountable_id => user.id + user.fax_accounts.each do |fax_account| + can :read, PhoneNumber, :id => fax_account.phone_number_ids + can [:read, :create, :delete], FaxDocument, :fax_account_id => fax_account.id + end + + # Conferences + # + can [ :read, :edit, :update, :destroy ], Conference, :id => user.conference_ids + user.conferences.each do |conference| + can :read, PhoneNumber, :id => conference.phone_number_ids + can :manage, ConferenceInvitee, :conference_id => conference.id + end + + # User can manage CallForwards of the PhoneNumbers of his + # own SipAccounts: + # + can :manage, CallForward, :phone_number_id => user.phone_number_ids + + # SystemMessages + # + can :read, SystemMessage, :user_id => user.id + + # SoftkeyFunctions + # + can :read, SoftkeyFunction + + # Voicemail + # + can :manage, VoicemailMessage + can :manage, VoicemailSetting + end + end + else + if GemeinschaftSetup.count == 0 && Tenant.count == 0 && User.count == 0 + # This is a fresh system. + # + can :create, GemeinschaftSetup + can :manage, SipDomain + end + end + + end +end diff --git a/app/models/access_authorization.rb b/app/models/access_authorization.rb new file mode 100644 index 0000000..ef33115 --- /dev/null +++ b/app/models/access_authorization.rb @@ -0,0 +1,41 @@ +class AccessAuthorization < ActiveRecord::Base + attr_accessible :name, :login, :pin, :phone_numbers_attributes, :sip_account_id + + belongs_to :access_authorizationable, :polymorphic => true + + validates_uniqueness_of :name, :scope => [ :access_authorizationable_type, :access_authorizationable_id ], + :allow_nil => true, :allow_blank => true + + # The login is optional. But if set has to be done with digits only. + # + validates_format_of :login, :with => /\A([0-9]+)\Z/, + :allow_nil => true, :allow_blank => true, + :message => "must be numeric." + + # The PIN is optional. But when set it has to be a proper PIN. + # + validates_format_of :pin, :with => /\A([0-9]+)\Z/, + :allow_nil => true, :allow_blank => true, + :message => "must be numeric." + + validates_length_of :pin, :minimum => MINIMUM_PIN_LENGTH, :maximum => MAXIMUM_PIN_LENGTH, + :allow_nil => true, :allow_blank => true + + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + accepts_nested_attributes_for :phone_numbers, + :reject_if => lambda { |phone_number| phone_number[:number].blank? }, + :allow_destroy => true + + # Optional SIP account. + # + belongs_to :sip_account + + validates_presence_of :sip_account, :if => Proc.new{ |access_authorization| !access_authorization.sip_account_id.blank? }, + :message => 'Given SIP account does not exist.' + + acts_as_list :scope => [ :access_authorizationable_type, :access_authorizationable_id ] + + def to_s + self.name || I18n.t('access_authorizations.name') + ' ID ' + self.id.to_s + end +end diff --git a/app/models/acd_agent.rb b/app/models/acd_agent.rb new file mode 100644 index 0000000..a00ac4b --- /dev/null +++ b/app/models/acd_agent.rb @@ -0,0 +1,39 @@ +class AcdAgent < ActiveRecord::Base + DESTINATION_TYPES = ['SipAccount'] + STATUSES = ['active', 'inactive'] + + attr_accessible :uuid, :name, :status, :automatic_call_distributor_id, :last_call, :calls_answered, :destination_type, :destination_id + + belongs_to :automatic_call_distributor + + belongs_to :destination, :polymorphic => true + + after_save :set_presence + + def to_s + self.name || I18n.t('acd_agents.name') + ' ID ' + self.id.to_s + end + + private + def set_presence + dialplan_function = nil + + state = 'early' + if self.status == 'active' + state = 'confirmed' + elsif self.status == 'inactive' + state = 'terminated' + end + + require 'freeswitch_event' + event = FreeswitchEvent.new("PRESENCE_IN") + event.add_header("proto", "sip") + event.add_header("from", "f-acdmtg-#{self.id}@#{SipDomain.first.host}") + event.add_header("event_type", "presence") + event.add_header("alt_event_type", "dialog") + event.add_header("presence-call-direction", "outbound") + event.add_header("answer-state", state) + event.add_header("unique-id", "acd_agent_#{self.id}") + return event.fire() + end +end diff --git a/app/models/acd_caller.rb b/app/models/acd_caller.rb new file mode 100644 index 0000000..1be48b9 --- /dev/null +++ b/app/models/acd_caller.rb @@ -0,0 +1,6 @@ +class AcdCaller < ActiveRecord::Base + attr_accessible :channel_uuid, :automatic_call_distributor_id, :status, :enter_time, :agent_answer_time, :callback_number, :callback_attempts + + has_one :channel, :class_name => 'FreeswitchChannel', :foreign_key => 'uuid', :primary_key => 'channel_uuid' + belongs_to :automatic_call_distributor +end diff --git a/app/models/address.rb b/app/models/address.rb new file mode 100644 index 0000000..bcf3474 --- /dev/null +++ b/app/models/address.rb @@ -0,0 +1,8 @@ +class Address < ActiveRecord::Base + attr_accessible :phone_book_entry_id, :line1, :line2, :street, :zip_code, :city, :country_id, :position, :uuid + + belongs_to :country + + validates_presence_of :uuid + validates_uniqueness_of :uuid +end diff --git a/app/models/api.rb b/app/models/api.rb new file mode 100644 index 0000000..557d875 --- /dev/null +++ b/app/models/api.rb @@ -0,0 +1,5 @@ +module Api + def self.table_name_prefix + 'api_' + end +end diff --git a/app/models/api/row.rb b/app/models/api/row.rb new file mode 100644 index 0000000..ac35516 --- /dev/null +++ b/app/models/api/row.rb @@ -0,0 +1,152 @@ +class Api::Row < ActiveRecord::Base + + # This is the place to do some basic mapping. + # + alias_attribute :UserName, :user_name + alias_attribute :LastName, :last_name + alias_attribute :FirstName, :first_name + alias_attribute :PhoneOffice, :office_phone_number + alias_attribute :VoipNr, :internal_extension + alias_attribute :CellPhone, :mobile_phone_number + alias_attribute :Fax, :fax_phone_number + alias_attribute :Email, :email + alias_attribute :PIN, :pin + alias_attribute :PIN_LastUpdate, :pin_updated_at + alias_attribute :Photo, :photo_file_name + + belongs_to :user + + # Validations + # + validates_presence_of :user_name + validates_uniqueness_of :user_name + + after_destroy :destroy_user + + def to_s + self.user_name + end + + def create_a_new_gemeinschaft_user + tenant = Tenant.find(DEFAULT_API_TENANT_ID) + + # Find or create the user + # + if tenant.users.where(:user_name => self.user_name).count > 0 + user = tenant.users.where(:user_name => self.user_name).first + else + user = tenant.users.create( + :user_name => self.user_name, + :last_name => self.last_name, + :first_name => self.first_name, + :middle_name => self.middle_name, + :email => self.email, + :new_pin => self.pin, + :new_pin_confirmation => self.pin, + :password => self.pin, + :password_confirmation => self.pin, + :language_id => tenant.language_id, + ) + end + + self.update_attributes({:user_id => user.id}) + + # Find or create a sip_account + # + if user.sip_accounts.count > 0 + sip_account = user.sip_accounts.first + else + sip_account = user.sip_accounts.create( + :caller_name => self.user.to_s, + :voicemail_pin => self.pin, + ) + end + + # Create phone_numbers to this sip_account (BTW: phone_numbers are unqiue) + # + sip_account.phone_numbers.create(:number => self.internal_extension) + sip_account.phone_numbers.create(:number => self.office_phone_number) + + + # Find or create a fax account + # + if user.fax_accounts.count > 0 + fax_account = user.fax_accounts.first + else + fax_account = user.fax_accounts.create( + :name => 'Default Fax', + :station_id => user.to_s, + :email => self.email, + :days_till_auto_delete => 90, + :retries => 3, + ) + end + + # Create phone_numbers to this fax_account + # + fax_account.phone_numbers.create(:number => self.fax_phone_number) + + end + + def destroy_user + self.user.destroy + end + + def update_user_data + user = self.user + user.update_attributes( + :user_name => self.user_name, + :last_name => self.last_name, + :first_name => self.first_name, + :middle_name => self.middle_name, + :email => self.email, + :new_pin => self.pin, + :new_pin_confirmation => self.pin, + :password => self.pin, + :password_confirmation => self.pin, + ) + + # Find or create a sip_account + # + if user.sip_accounts.count > 0 + sip_account = user.sip_accounts.first + else + sip_account = user.sip_accounts.create( + :caller_name => self.user.to_s, + :voicemail_pin => self.pin, + ) + end + + # Delete old phone_numbers + # + sip_account.phone_numbers.destroy_all + + # Create phone_numbers to this sip_account (BTW: phone_numbers are unqiue) + # + sip_account.phone_numbers.create(:number => self.internal_extension) + sip_account.phone_numbers.create(:number => self.office_phone_number) + + # Find or create a fax account + # + if user.fax_accounts.count > 0 + fax_account = user.fax_accounts.first + else + fax_account = user.fax_accounts.create( + :name => 'Default Fax', + :station_id => user.to_s, + :email => self.email, + :days_till_auto_delete => 90, + :retries => 3, + ) + end + + # Delete old phone_number + # + fax_account.phone_numbers.destroy_all + + # Create phone_numbers to this fax_account + # + fax_account.phone_numbers.create(:number => self.fax_phone_number) + end + +end diff --git a/app/models/area_code.rb b/app/models/area_code.rb new file mode 100644 index 0000000..6a9d946 --- /dev/null +++ b/app/models/area_code.rb @@ -0,0 +1,22 @@ +class AreaCode < ActiveRecord::Base + + # Associations: + # + belongs_to :country + + # Validations: + # + validates_presence_of :country + validates_presence_of :name + validates_presence_of :area_code + + validates_uniqueness_of :area_code, :scope => [ :country_id, :central_office_code ] + + + def to_s + "#{self.name} (#{self.area_code}" + + (self.central_office_code.blank? ? '' : "-#{self.central_office_code}") + + ')' + end + +end diff --git a/app/models/automatic_call_distributor.rb b/app/models/automatic_call_distributor.rb new file mode 100644 index 0000000..678e0eb --- /dev/null +++ b/app/models/automatic_call_distributor.rb @@ -0,0 +1,21 @@ +class AutomaticCallDistributor < ActiveRecord::Base + attr_accessible :uuid, :name, :strategy, :automatic_call_distributorable_type, :automatic_call_distributorable_id, :max_callers, :agent_timeout, :retry_timeout, :join, :leave, :gs_node_id, :announce_position, :announce_call_agents, :greeting, :goodbye, :music + + belongs_to :automatic_call_distributorable, :polymorphic => true + + has_many :acd_agents, :dependent => :destroy + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + accepts_nested_attributes_for :phone_numbers, + :reject_if => lambda { |phone_number| phone_number[:number].blank? }, + :allow_destroy => true + + validates_presence_of :strategy + + STRATEGIES = ['ring_all', 'round_robin'] + JOIN_ON = ['agents_available', 'agents_active', 'always'] + LEAVE_ON = ['no_agents_available_timeout', 'no_agents_active_timeout', 'no_agents_available', 'no_agents_active', 'timeout', 'never'] + + def to_s + self.name + end +end diff --git a/app/models/call.rb b/app/models/call.rb new file mode 100644 index 0000000..57961ec --- /dev/null +++ b/app/models/call.rb @@ -0,0 +1,36 @@ +class Call < ActiveRecord::Base + self.table_name = 'channels' + self.primary_key = 'uuid' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end + + def sip_account + auth_name = self.name.match('^.+[/:](.+)@.+$') + if auth_name && ! auth_name[1].blank? + return SipAccount.where(:auth_name => auth_name[1]).first + end + end + + def kill + require 'freeswitch_event' + return FreeswitchAPI.execute('uuid_kill', self.uuid, true); + end +end diff --git a/app/models/call_forward.rb b/app/models/call_forward.rb new file mode 100644 index 0000000..0018cfb --- /dev/null +++ b/app/models/call_forward.rb @@ -0,0 +1,262 @@ +class CallForward < ActiveRecord::Base + + attr_accessor :to_voicemail, :hunt_group_id + + attr_accessible :phone_number_id, :call_forward_case_id, :timeout, + :destination, :source, :depth, :active, :to_voicemail, + :hunt_group_id, + :call_forwardable_type, :call_forwardable_id, + :call_forwarding_destination, :position, :uuid + + belongs_to :phone_number + belongs_to :call_forwardable, :polymorphic => true + has_many :softkeys + + acts_as_list :scope => [ :phone_number_id, :call_forward_case_id ] + + validates_presence_of :phone_number + validates_presence_of :call_forward_case_id + validates_presence_of :destination, :if => Proc.new { |cf| cf.call_forwardable_type.to_s.downcase == 'phonenumber' || cf.call_forwardable_type.blank? } + + validates_inclusion_of :destination, + :in => [ nil, '' ], + :if => Proc.new { |cf| cf.to_voicemail == true } + + belongs_to :call_forward_case + + validates_presence_of :depth + validates_numericality_of :depth, + :only_integer => true, + :greater_than_or_equal_to => 1, + :less_than_or_equal_to => MAX_CALL_FORWARD_DEPTH + + before_validation { + self.timeout = nil if self.call_forward_case_id != 3 + } + + validates_numericality_of :timeout, + :if => Proc.new { |cf| cf.call_forward_case_id == 3 }, + :only_integer => true, + :greater_than_or_equal_to => 1, + :less_than_or_equal_to => 120 + + validates_inclusion_of :timeout, + :in => [ nil ], + :if => Proc.new { |cf| cf.call_forward_case_id != 3 } + + validate :validate_empty_hunt_group, :if => Proc.new { |cf| cf.active == true && cf.call_forwardable_type == 'HuntGroup' && cf.call_forward_case.value == 'assistant' } + + validates_presence_of :uuid + validates_uniqueness_of :uuid + + # Make sure the call forward's parent can't be changed: + before_validation { |cfwd| + if cfwd.id \ + && cfwd.phone_number_id != cfwd.phone_number_id_was + errors.add( :phone_number_id, "cannot be changed." ) + end + } + + #before_validation :set_call_forwardable + before_save :split_and_format_destination_numbers + after_save :set_presence + after_save :work_through_callforward_rules_act_per_sip_account + after_save :deactivate_concurring_entries, :if => Proc.new { |cf| cf.active == true } + before_destroy :check_if_other_callforward_rules_have_to_be_destroyed + before_destroy :deactivate_connected_softkeys + + def case_string + return self.call_forward_case ? self.call_forward_case.value : nil + end + + def to_s + if self.call_forwardable_type.blank? + self.call_forwardable_type = '' + else + call_forwardable_type = " #{self.call_forwardable_type}" + end + if self.call_forwardable + destination = "#{self.call_forwardable}#{call_forwardable_type}" + else + destination = "#{self.destination}#{call_forwardable_type}" + end + "#{self.phone_number} (#{I18n.t("call_forward_cases.#{self.call_forward_case}")}) -> #{destination}" + end + + def set_this_callforward_rule_to_all_phone_numbers_of_the_parent_sip_account + # This is to make sure that no recursion kicks in. + # + if ! self.phone_number.phone_numberable.respond_to? :callforward_rules_act_per_sip_account + return false + end + + old_value_of_callforward_rules_act_per_sip_account = self.phone_number.phone_numberable.callforward_rules_act_per_sip_account + self.phone_number.phone_numberable.update_attributes({:callforward_rules_act_per_sip_account => false}) + + attributes_of_this_call_forward = self.attributes.delete_if {|key, value| ['id','updated_at','created_at','phone_number_id','call_forward_case_id', 'uuid'].include?(key)} + phone_numbers = self.phone_number.phone_numberable.phone_numbers.where('id != ?', self.phone_number.id) + + phone_numbers.each do |phone_number| + # Problem + call_forward = phone_number.call_forwards.find_or_create_by_call_forward_case_id_and_position(self.call_forward_case_id, self.position, attributes_of_this_call_forward) + call_forward.update_attributes(attributes_of_this_call_forward) + end + + self.phone_number.phone_numberable.update_attributes({:callforward_rules_act_per_sip_account => old_value_of_callforward_rules_act_per_sip_account}) + end + + def destroy_all_similar_callforward_rules_of_the_parent_sip_account + # This is to make sure that no recursion kicks in. + # + if ! self.phone_number.phone_numberable.respond_to? :callforward_rules_act_per_sip_account + return false + end + + old_value_of_callforward_rules_act_per_sip_account = self.phone_number.phone_numberable.callforward_rules_act_per_sip_account + self.phone_number.phone_numberable.update_attributes({:callforward_rules_act_per_sip_account => false}) + + phone_numbers_of_parent_sip_account = self.phone_number.phone_numberable.phone_numbers.where('id != ?', self.phone_number.id) + + phone_numbers_of_parent_sip_account.each do |phone_number| + if self.call_forwardable_type != 'Voicemail' + phone_number.call_forwards.where(:call_forward_case_id => self.call_forward_case_id, :destination => self.destination).destroy_all + else + phone_number.call_forwards.where(:call_forward_case_id => self.call_forward_case_id, :call_forwardable_type => self.call_forwardable_type).destroy_all + end + end + + self.phone_number.phone_numberable.update_attributes({:callforward_rules_act_per_sip_account => old_value_of_callforward_rules_act_per_sip_account}) + end + + def call_forwarding_destination + "#{self.call_forwardable_id}:#{self.call_forwardable_type}" + end + + def call_forwarding_destination=(destination_record) + self.call_forwardable_id, delimeter, self.call_forwardable_type = destination_record.to_s.partition(':') + end + + def toggle + self.active = ! self.active + return self.save + end + + def deactivate_connected_softkeys + softkey_function_deactivated = SoftkeyFunction.find_by_name('deactivated') + self.softkeys.each do |softkey| + if softkey.softkey_function_id != softkey_function_deactivated.id + softkey.update_attributes(:call_forward_id => nil, :softkey_function_id => softkey_function_deactivated.id) + end + end + end + + private + def split_and_format_destination_numbers + if !self.destination.blank? + destinations = self.destination.gsub(/[^+0-9\,]/,'').gsub(/[\,]+/,',').split(/\,/).delete_if{|x| x.blank?} + self.destination = nil + if destinations.count > 0 + destinations.each do |single_destination| + self.destination = self.destination.to_s + ", #{PhoneNumber.parse_and_format(single_destination)}" + end + end + self.destination = self.destination.to_s.gsub(/[^+0-9\,]/,'').gsub(/[\,]+/,',').split(/\,/).sort.delete_if{|x| x.blank?}.join(', ') + end + end + + def set_presence + state = 'terminated' + + if self.active + if self.call_forwardable_type and self.call_forwardable_type.downcase() == 'voicemail' + state = 'early' + else + state = 'confirmed' + end + end + + return send_presence_event(state) + + #if self.call_forward_case_id_changed? + # call_forwarding_service = CallForwardCase.where(:id => self.call_forward_case_id_was).first + # if call_forwarding_service + # send_presence_event(call_forwarding_service.value, state) + # end + #end + + #return send_presence_event(self.call_forward_case.value, state) + end + + def set_call_forwardable + if @hunt_group_id && HuntGroup.where(:id => @hunt_group_id.to_i).count > 0 + self.call_forwardable = HuntGroup.where(:id => @hunt_group_id.to_i).first + end + + if @to_voicemail && @to_voicemail.first.downcase == 'true' + self.call_forwardable_type = 'Voicemail' + self.call_forwardable_id = nil + end + end + + def work_through_callforward_rules_act_per_sip_account + if ! self.phone_number.phone_numberable.respond_to? :callforward_rules_act_per_sip_account + return false + end + + if self.phone_number.phone_numberable.callforward_rules_act_per_sip_account == true + self.set_this_callforward_rule_to_all_phone_numbers_of_the_parent_sip_account + end + end + + def check_if_other_callforward_rules_have_to_be_destroyed + if ! self.phone_number.phone_numberable.respond_to? :callforward_rules_act_per_sip_account + return false + end + + if self.phone_number.phone_numberable.callforward_rules_act_per_sip_account == true + self.destroy_all_similar_callforward_rules_of_the_parent_sip_account + end + end + + def send_presence_event(state, call_forwarding_service = nil) + dialplan_function = "cftg-#{self.id}" + unique_id = "call_forwarding_#{self.id}" + + if call_forwarding_service == 'always' + dialplan_function = "cfutg-#{self.phone_number.id}" + unique_id = "call_forwarding_number_#{self.phone_number.id}" + elsif call_forwarding_service == 'assistant' + dialplan_function = "cfatg-#{self.phone_number.id}" + unique_id = "call_forwarding_number_#{self.phone_number.id}" + end + + if dialplan_function + require 'freeswitch_event' + event = FreeswitchEvent.new("PRESENCE_IN") + event.add_header("proto", "sip") + event.add_header("from", "f-#{dialplan_function}@#{SipDomain.first.host}") + event.add_header("event_type", "presence") + event.add_header("alt_event_type", "dialog") + event.add_header("presence-call-direction", "outbound") + event.add_header("answer-state", state) + event.add_header("unique-id", unique_id) + return event.fire() + end + end + + def deactivate_concurring_entries + CallForward.where(:phone_number_id => self.phone_number_id, :call_forward_case_id => self.call_forward_case_id, :active => true).each do |call_forwarding_entry| + if call_forwarding_entry.id != self.id + call_forwarding_entry.update_attributes(:active => false) + end + end + end + + def validate_empty_hunt_group + hunt_group = self.call_forwardable + if hunt_group && hunt_group.hunt_group_members.where(:active => true).count == 0 + errors.add(:call_forwarding_destination, 'HuntGroup has no active members') + end + end + +end diff --git a/app/models/call_forward_case.rb b/app/models/call_forward_case.rb new file mode 100644 index 0000000..a0b872b --- /dev/null +++ b/app/models/call_forward_case.rb @@ -0,0 +1,13 @@ +class CallForwardCase < ActiveRecord::Base + + attr_accessible :value + + has_many :call_forwards + + validates_presence_of :value + + def to_s + self.value + end + +end diff --git a/app/models/call_history.rb b/app/models/call_history.rb new file mode 100644 index 0000000..4db056a --- /dev/null +++ b/app/models/call_history.rb @@ -0,0 +1,199 @@ +class CallHistory < ActiveRecord::Base + belongs_to :call_historyable, :polymorphic => true + belongs_to :caller_account, :polymorphic => true + belongs_to :callee_account, :polymorphic => true + belongs_to :auth_account, :polymorphic => true + + def display_number + if self.entry_type == 'dialed' + return self.destination_number.to_s + else + return self.caller_id_number.to_s + end + end + + def display_name + if self.entry_type == 'dialed' + begin + account = self.callee_account + rescue + account = nil + end + name_str = self.callee_id_name + else + begin + account = self.caller_account + rescue + account = nil + end + name_str = self.caller_id_name + end + + if name_str.blank? + if account.class == SipAccount + return account.caller_name.to_s + elsif account + return account.to_s + end + else + return name_str.to_s + end + end + + def display_auth_account_name + begin + account = self.auth_account + rescue + return nil + end + + if account.class == SipAccount + return account.caller_name.to_s + elsif account + return account.to_s + end + end + + def display_image(image_size = :mini, phone_book_entry) + if phone_book_entry + image = phone_book_entry.image_url(image_size) + if ! image.blank? + return image + end + end + + begin + if self.entry_type == 'dialed' + account = self.callee_account + else + account = self.caller_account + end + rescue + return nil + end + + if account.class == SipAccount && account.sip_accountable.class == User + return account.sip_accountable.image_url(image_size).to_s + end + end + + def display_call_date(date_format, date_today_format) + if self.start_stamp.strftime('%Y%m%d') == DateTime::now.strftime('%Y%m%d') + return self.start_stamp.strftime(date_today_format) + end + return self.start_stamp.strftime(date_format) + end + + def display_duration + if self.duration.to_i > 0 + minutes = (self.duration / 1.minutes).to_i + seconds = self.duration - minutes.minutes.seconds + return '%i:%02i' % [minutes, seconds] + end + end + + def phone_book_entry_by_number(number) + begin + call_historyable = self.call_historyable + rescue + return nil + end + + if ! call_historyable + return nil + end + + if call_historyable.class == SipAccount + owner = call_historyable.sip_accountable + end + + if owner.class == User + phone_books = owner.phone_books.all + phone_books.concat(owner.current_tenant.phone_books.all) + elsif owner.class == Tenant + phone_books = owner.phone_books.all + end + + if ! phone_books + return nil + end + + phone_books.each do |phone_book| + phone_book_entry = phone_book.find_entry_by_number(number) + if phone_book_entry + return phone_book_entry + end + end + + return nil + + end + + def voicemail_message + begin + return self.call_historyable.voicemail_messages.where(:forwarded_by => self.caller_channel_uuid).first + rescue + return nil + end + end + + def call_historyable_uuid + begin + return self.call_historyable.uuid + rescue + return nil + end + end + + def call_historyable_uuid=(uuid) + begin + return self.call_historyable_id = self.call_historyable_type.constantize.where(:uuid => uuid).first.id + rescue + end + end + + def caller_account_uuid + begin + return self.caller_account.uuid + rescue + return nil + end + end + + def caller_account_uuid=(uuid) + begin + return self.caller_account_id = self.caller_account_type.constantize.where(:uuid => uuid).first.id + rescue + end + end + + def callee_account_uuid + begin + return self.callee_account.uuid + rescue + return nil + end + end + + def callee_account_uuid=(uuid) + begin + return self.callee_account_id = self.callee_account_type.constantize.where(:uuid => uuid).first.id + rescue + end + end + + def auth_account_uuid + begin + return self.auth_account.uuid + rescue + return nil + end + end + + def auth_account_uuid=(uuid) + begin + return self.auth_account_id = self.auth_account_type.constantize.where(:uuid => uuid).first.id + rescue + end + end +end diff --git a/app/models/callthrough.rb b/app/models/callthrough.rb new file mode 100644 index 0000000..c057fa6 --- /dev/null +++ b/app/models/callthrough.rb @@ -0,0 +1,60 @@ +class Callthrough < ActiveRecord::Base + attr_accessible :name, :clip_no_screening, + :phone_numbers_attributes, :access_authorizations_attributes, + :whitelists_attributes + + # Validations and Associations + # + belongs_to :tenant + + validates_presence_of :tenant_id + validates_presence_of :tenant + + # These are the phone_numbers for this callthrough. + # One has to dial this number to access the callthrough. + # + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + + accepts_nested_attributes_for :phone_numbers, + :reject_if => lambda { |phone_number| phone_number[:number].blank? }, + :allow_destroy => true + + validate :requires_at_least_one_phone_number + + # These are the access authorizations for this callthrough. + # One has to be known by his phone number or by a login/pin or even both. + # + has_many :access_authorizations, :as => :access_authorizationable, :dependent => :destroy + + accepts_nested_attributes_for :access_authorizations, + :reject_if => lambda { |access_authorization| access_authorization[:login].blank? && access_authorization[:pin].blank? && access_authorization[:phone_numbers_attributes]['0'][:number].blank? }, + :allow_destroy => true + + has_many :access_authorization_phone_numbers, :source => :phone_numbers, + :through => :access_authorizations, :readonly => true + + # These are the whitelists of the phone numbers which can be called through this callthrough. + # + has_many :whitelists, :as => :whitelistable, :dependent => :destroy + + accepts_nested_attributes_for :whitelists, + :reject_if => lambda { |whitelist| whitelist[:phone_numbers_attributes]['0']['number'].blank? }, + :allow_destroy => true + + has_many :whitelisted_phone_numbers, :source => :phone_numbers, + :through => :whitelists, :readonly => true + + # Delegations: + # + delegate :sip_domain, :to => :tenant, :allow_nil => true + + def to_s + self.name || I18n.t('callthroughs.name') + ' ID ' + self.id + end + + + private + def requires_at_least_one_phone_number + errors.add(:base, "You must provide at least one phone number") if !self.phone_numbers.map{|phone_number| phone_number.valid?}.include?(true) + end +end diff --git a/app/models/conference.rb b/app/models/conference.rb new file mode 100644 index 0000000..8be9f21 --- /dev/null +++ b/app/models/conference.rb @@ -0,0 +1,63 @@ +class Conference < ActiveRecord::Base + attr_accessible :name, :start, :end, :description, :pin, + :open_for_anybody, :max_members, :announce_new_member_by_name, + :announce_left_member_by_name + + belongs_to :conferenceable, :polymorphic => true + has_many :conference_invitees, :dependent => :destroy + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + + validates_presence_of :conferenceable_type, :conferenceable_id + validates_presence_of :conferenceable + validates_presence_of :name + validates_presence_of :start, :if => Proc.new { |conference| !conference.end.blank? } + validates_presence_of :end, :if => Proc.new { |conference| !conference.start.blank? } + validates_presence_of :max_members + validates_numericality_of :max_members, :only_integer => true, + :greater_than => 0, + :less_than => (MAXIMUM_NUMBER_OF_PEOPLE_IN_A_CONFERENCE + 1), + :allow_nil => false, + :allow_blank => false + + validates_inclusion_of :open_for_anybody, :in => [true, false] + + validates_numericality_of :pin, :only_integer => true, + :greater_than => 0, + :allow_nil => true, + :allow_blank => true + validates_length_of :pin, :minimum => MINIMUM_PIN_LENGTH, + :allow_nil => true, + :allow_blank => true + + validate :start_and_end_dates_must_make_sense, :if => Proc.new { |conference| !conference.start.blank? && !conference.end.blank? } + + after_save :send_pin_email_when_pin_has_changed + + default_scope where(:state => 'active').order(:start) + + # State Machine stuff + state_machine :initial => :active do + end + + def sip_domain + self.conferenceable.try(:sip_domain) + end + + def to_s + name + end + + private + + def start_and_end_dates_must_make_sense + errors.add(:start, 'must be in the future') if self.start < Time.now - 10.minutes + errors.add(:end, 'must be later than the start') if self.end < self.start + end + + def send_pin_email_when_pin_has_changed + if self.conferenceable.class == User && self.pin_changed? + Notifications.new_pin(self).deliver + end + end + +end diff --git a/app/models/conference_invitee.rb b/app/models/conference_invitee.rb new file mode 100644 index 0000000..7de20de --- /dev/null +++ b/app/models/conference_invitee.rb @@ -0,0 +1,39 @@ +class ConferenceInvitee < ActiveRecord::Base + attr_accessible :pin, :speaker, :moderator, :phone_number, :phone_number_attributes + + belongs_to :conference + belongs_to :phone_book_entry + has_one :phone_number, :as => :phone_numberable, :dependent => :destroy + accepts_nested_attributes_for :phone_number + + validates_presence_of :conference_id + validates_presence_of :conference + validates_presence_of :phone_number + validates_numericality_of :pin, :only_integer => true, + :greater_than => 0, + :allow_nil => true, + :allow_blank => true + validates_length_of :pin, :minimum => MINIMUM_PIN_LENGTH, + :allow_nil => true, + :allow_blank => true + + validates_inclusion_of :speaker, :in => [true, false] + validates_inclusion_of :moderator, :in => [true, false] + + validate :uniqueness_of_phone_number_in_the_parent_conference + validates_uniqueness_of :phone_book_entry_id, :scope => :conference_id, :allow_nil => true + + def to_s + "ID #{self.id}" + end + + private + + def uniqueness_of_phone_number_in_the_parent_conference + if self.conference.conference_invitees.where('id != ?', self.id).count > 0 && + self.conference.conference_invitees.where('id != ?', self.id).map{|x| x.phone_number.number}. + include?(self.phone_number.number) + errors.add(:base, 'Phone number is not unique within the conference.') + end + end +end diff --git a/app/models/country.rb b/app/models/country.rb new file mode 100644 index 0000000..018e348 --- /dev/null +++ b/app/models/country.rb @@ -0,0 +1,21 @@ +class Country < ActiveRecord::Base + + has_many :area_codes, :dependent => :destroy + has_many :tenants + has_many :phone_number_ranges, :as => :phone_number_rangeable, :dependent => :destroy + + validates_presence_of :name + validates_presence_of :country_code + validates_presence_of :international_call_prefix + + validates_numericality_of :country_code, + :only_integer => true + + validates_uniqueness_of :name, :scope => [ :country_code ], + :case_sensitive => false + + def to_s + self.name + end + +end diff --git a/app/models/dial_in_number_store.rb b/app/models/dial_in_number_store.rb new file mode 100644 index 0000000..17c2202 --- /dev/null +++ b/app/models/dial_in_number_store.rb @@ -0,0 +1,16 @@ +class DialInNumberStore < ActiveRecord::Base + # Associations and Validations + # + validates_presence_of :dial_in_number_storeable_type + validates_presence_of :dial_in_number_storeable_id + + belongs_to :dial_in_number_storeable, :polymorphic => true + + validates_presence_of :dial_in_number_storeable + + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + + # Delegations: + # + delegate :tenant, :to => :dial_in_number_storeable, :allow_nil => true +end diff --git a/app/models/fax_account.rb b/app/models/fax_account.rb new file mode 100644 index 0000000..683447a --- /dev/null +++ b/app/models/fax_account.rb @@ -0,0 +1,77 @@ +# encoding: UTF-8 + +class FaxAccount < ActiveRecord::Base + attr_accessible :name, :email, :station_id, :days_till_auto_delete, :phone_numbers_attributes, :retries + + # Validations: + # + validates_presence_of :fax_accountable_type, :fax_accountable_id + validates_presence_of :fax_accountable + validates_presence_of :name + validates_presence_of :tenant_id + validates_presence_of :tenant + + validates_numericality_of :days_till_auto_delete, :allow_nil => true + validates_numericality_of :retries, :only_integer => true, :greater_than_or_equal_to => 0 + + validates_uniqueness_of :name, :scope => [:fax_accountable_type, :fax_accountable_id] + + # Associations: + # + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + has_many :fax_documents, :dependent => :destroy + + belongs_to :fax_accountable, :polymorphic => true + belongs_to :tenant + + accepts_nested_attributes_for :phone_numbers + + # Hooks + # + before_validation :find_and_set_tenant_id + before_validation :convert_umlauts + + def to_s + name + end + + private + def require_at_least_one_phone_number + if self.phone_numbers.count < 1 + errors.add(:base, 'needs at least one valid phone number') + end + end + + def find_and_set_tenant_id + if self.new_record? and self.tenant_id != nil + return + else + tenant = case self.fax_accountable_type + when 'UserGroup' ; fax_accountable.tenant + when 'User' ; fax_accountable.current_tenant || fax_accountable.tenants.last + else nil + end + self.tenant_id = tenant.id if tenant != nil + end + end + + def convert_umlauts + self.name = self.name.sub(/ä/,'ae'). + sub(/Ä/,'Ae'). + sub(/ü/,'ue'). + sub(/Ü/,'Ue'). + sub(/ö/,'oe'). + sub(/Ö/,'Oe'). + sub(/ß/,'ss') + self.name = self.name.gsub(/[^a-zA-Z0-9\-\,\:\.\+ ]/,'_') + self.station_id = self.station_id.sub(/ä/,'ae'). + sub(/Ä/,'Ae'). + sub(/ü/,'ue'). + sub(/Ü/,'Ue'). + sub(/ö/,'oe'). + sub(/Ö/,'Oe'). + sub(/ß/,'ss') + self.station_id = self.station_id.gsub(/[^a-zA-Z0-9\-\,\:\.\+ ]/,'_') + end + +end diff --git a/app/models/fax_document.rb b/app/models/fax_document.rb new file mode 100644 index 0000000..67bdea9 --- /dev/null +++ b/app/models/fax_document.rb @@ -0,0 +1,82 @@ +class FaxDocument < ActiveRecord::Base +# attr_accessible :inbound, :transmission_time, :sent_at, :document_total_pages, :document_transferred_pages, :ecm_requested, :ecm_used, :image_resolution, :image_size, :local_station_id, :result_code, :result_text, :remote_station_id, :success, :transfer_rate, :t38_gateway_format, :t38_peer, :document + + mount_uploader :document, DocumentUploader + mount_uploader :tiff, TiffUploader + + validates_presence_of :document + validates_numericality_of :retry_counter, :only_integer => true, :greater_than_or_equal_to => 0 + + belongs_to :fax_account + belongs_to :fax_resolution + + validates_presence_of :fax_resolution_id + validates_presence_of :fax_resolution + + has_one :destination_phone_number, :class_name => 'PhoneNumber', :as => :phone_numberable, :dependent => :destroy + accepts_nested_attributes_for :destination_phone_number + + has_many :fax_thumbnails, :order => :position, :dependent => :destroy + + after_create :render_thumbnails + after_create :convert_pdf_to_tiff + + # Scopes + scope :inbound, where(:state => 'inbound') + scope :outbound, where(:state => ['queued_for_sending','sending','successful','unsuccessful']) + + # State Machine stuff + state_machine :initial => :new do + event :queue_for_sending do + transition [:new] => :queued_for_sending + end + + event :send_now do + transition [:queued_for_sending] => :sending + end + + event :cancel do + transition [:sending, :queued_for_sending] => :unsuccessful + end + + event :successful_sent do + transition [:sending, :queued_for_sending] => :successful + end + + event :mark_as_inbound do + transition [:new] => :inbound + end + end + + def to_s + name + end + + private + def render_thumbnails + directory = "/tmp/GS-#{GEMEINSCHAFT_VERSION}/fax_thumbnails/#{self.id}" + system('mkdir -p ' + directory) + system("cd #{directory} && convert #{Rails.root.to_s}/public#{self.document.to_s}[0-100] -colorspace Gray PNG:'fax_page.png'") + number_of_thumbnails = Dir["#{directory}/fax_page-*.png"].count + (0..(number_of_thumbnails-1)).each do |i| + fax_thumbnail = self.fax_thumbnails.build + fax_thumbnail.thumbnail = File.open("#{directory}/fax_page-#{i}.png") + fax_thumbnail.save! + end + system("rm -rf #{directory}") + self.update_attributes(:document_total_pages => number_of_thumbnails) if self.document_total_pages.nil? + end + + def convert_pdf_to_tiff + page_size_a4 = '595 842' + page_size_command = "<< /Policies << /PageSize 3 >> /InputAttributes currentpagedevice /InputAttributes get dup { pop 1 index exch undef } forall dup 0 << /PageSize [ #{page_size_a4} ] >> put >> setpagedevice" + directory = "/tmp/GS-#{GEMEINSCHAFT_VERSION}/faxes/#{self.id}" + system('mkdir -p ' + directory) + tiff_file_name = File.basename(self.document.to_s.downcase, ".pdf") + '.tiff' + system "cd #{directory} && gs -q -r#{self.fax_resolution.resolution_value} -dNOPAUSE -dBATCH -dSAFER -sDEVICE=tiffg3 -sOutputFile=\"#{tiff_file_name}\" -c \"#{page_size_command}\" -- \"#{Rails.root.to_s}/public#{self.document.to_s}\"" + self.tiff = File.open("#{directory}/#{tiff_file_name}") + self.save + system("rm -rf #{directory}") + end + +end diff --git a/app/models/fax_resolution.rb b/app/models/fax_resolution.rb new file mode 100644 index 0000000..c9093fb --- /dev/null +++ b/app/models/fax_resolution.rb @@ -0,0 +1,15 @@ +class FaxResolution < ActiveRecord::Base + validates_presence_of :name + validates_presence_of :resolution_value + + validates_uniqueness_of :name + validates_uniqueness_of :resolution_value + + has_many :fax_documents, :dependent => :destroy + + acts_as_list + + def to_s + self.name + end +end diff --git a/app/models/fax_thumbnail.rb b/app/models/fax_thumbnail.rb new file mode 100644 index 0000000..a29c9ad --- /dev/null +++ b/app/models/fax_thumbnail.rb @@ -0,0 +1,8 @@ +class FaxThumbnail < ActiveRecord::Base + mount_uploader :thumbnail, ThumbnailUploader + validates_presence_of :thumbnail + + belongs_to :fax_document + + acts_as_list :scope => :fax_document +end diff --git a/app/models/freeswitch_alias.rb b/app/models/freeswitch_alias.rb new file mode 100644 index 0000000..9953edb --- /dev/null +++ b/app/models/freeswitch_alias.rb @@ -0,0 +1,23 @@ +class FreeswitchAlias < ActiveRecord::Base + self.table_name = 'aliases' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_call.rb b/app/models/freeswitch_call.rb new file mode 100644 index 0000000..95b2cdd --- /dev/null +++ b/app/models/freeswitch_call.rb @@ -0,0 +1,24 @@ +class FreeswitchCall < ActiveRecord::Base + self.table_name = 'calls' + self.primary_key = 'call_uuid' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_cdr.rb b/app/models/freeswitch_cdr.rb new file mode 100644 index 0000000..fd0eb75 --- /dev/null +++ b/app/models/freeswitch_cdr.rb @@ -0,0 +1,4 @@ +class FreeswitchCdr < ActiveRecord::Base + self.table_name = 'cdrs' + self.primary_key = 'uuid' +end diff --git a/app/models/freeswitch_channel.rb b/app/models/freeswitch_channel.rb new file mode 100644 index 0000000..489e17d --- /dev/null +++ b/app/models/freeswitch_channel.rb @@ -0,0 +1,24 @@ +class FreeswitchChannel < ActiveRecord::Base + self.table_name = 'channels' + self.primary_key = 'uuid' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_complete.rb b/app/models/freeswitch_complete.rb new file mode 100644 index 0000000..e7ff465 --- /dev/null +++ b/app/models/freeswitch_complete.rb @@ -0,0 +1,23 @@ +class FreeswitchComplete < ActiveRecord::Base + self.table_name = 'complete' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_fifo_bridge.rb b/app/models/freeswitch_fifo_bridge.rb new file mode 100644 index 0000000..06167f3 --- /dev/null +++ b/app/models/freeswitch_fifo_bridge.rb @@ -0,0 +1,23 @@ +class FreeswitchFifoBridge < ActiveRecord::Base + self.table_name = 'fifo_bridge' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_fifo_caller.rb b/app/models/freeswitch_fifo_caller.rb new file mode 100644 index 0000000..50c1fb5 --- /dev/null +++ b/app/models/freeswitch_fifo_caller.rb @@ -0,0 +1,23 @@ +class FreeswitchFifoCaller < ActiveRecord::Base + self.table_name = 'fifo_callers' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_fifo_outbound.rb b/app/models/freeswitch_fifo_outbound.rb new file mode 100644 index 0000000..029c21d --- /dev/null +++ b/app/models/freeswitch_fifo_outbound.rb @@ -0,0 +1,23 @@ +class FreeswitchFifoOutbound < ActiveRecord::Base + self.table_name = 'fifo_outbound' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_interface.rb b/app/models/freeswitch_interface.rb new file mode 100644 index 0000000..1602d62 --- /dev/null +++ b/app/models/freeswitch_interface.rb @@ -0,0 +1,23 @@ +class FreeswitchInterface < ActiveRecord::Base + self.table_name = 'interfaces' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_nat.rb b/app/models/freeswitch_nat.rb new file mode 100644 index 0000000..8baf2bf --- /dev/null +++ b/app/models/freeswitch_nat.rb @@ -0,0 +1,23 @@ +class FreeswitchNat < ActiveRecord::Base + self.table_name = 'nat' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_registration.rb b/app/models/freeswitch_registration.rb new file mode 100644 index 0000000..7e80815 --- /dev/null +++ b/app/models/freeswitch_registration.rb @@ -0,0 +1,23 @@ +class FreeswitchRegistration < ActiveRecord::Base + self.table_name = 'registrations' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_task.rb b/app/models/freeswitch_task.rb new file mode 100644 index 0000000..6e964d2 --- /dev/null +++ b/app/models/freeswitch_task.rb @@ -0,0 +1,23 @@ +class FreeswitchTask < ActiveRecord::Base + self.table_name = 'tasks' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/freeswitch_voicemail_pref.rb b/app/models/freeswitch_voicemail_pref.rb new file mode 100644 index 0000000..b2400e8 --- /dev/null +++ b/app/models/freeswitch_voicemail_pref.rb @@ -0,0 +1,23 @@ +class FreeswitchVoicemailPref < ActiveRecord::Base + self.table_name = 'voicemail_prefs' + + # Makes sure that this is a readonly model. + def readonly? + return true + end + + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def delete + raise ActiveRecord::ReadOnlyRecord + end +end diff --git a/app/models/gemeinschaft_setup.rb b/app/models/gemeinschaft_setup.rb new file mode 100644 index 0000000..b445b21 --- /dev/null +++ b/app/models/gemeinschaft_setup.rb @@ -0,0 +1,8 @@ +class GemeinschaftSetup < ActiveRecord::Base + belongs_to :user + accepts_nested_attributes_for :user + belongs_to :sip_domain + accepts_nested_attributes_for :sip_domain + belongs_to :country + belongs_to :language +end diff --git a/app/models/gs_cluster_sync_log_entry.rb b/app/models/gs_cluster_sync_log_entry.rb new file mode 100644 index 0000000..063ff23 --- /dev/null +++ b/app/models/gs_cluster_sync_log_entry.rb @@ -0,0 +1,99 @@ +class GsClusterSyncLogEntry < ActiveRecord::Base + attr_accessible :gs_node_id, :class_name, :action, :content, :status, :history, + :homebase_ip_address, :waiting_to_be_synced, :association_method, + :association_uuid + + validates :class_name, + :presence => true + + validates :action, + :presence => true + + validates :content, + :presence => true + + after_create :apply_to_local_database + + def apply_to_local_database + if self.homebase_ip_address != HOMEBASE_IP_ADDRESS + if self.class_name.constantize.new.attribute_names.include?('is_native') + case self.action + when 'create' + new_local_copy = self.class_name.constantize.new( + JSON(self.content). + delete_if{|key, value| ['id','updated_at','created_at']. + include?(key) }, + :without_protection => true) + new_local_copy.is_native = false + find_and_connect_to_an_association(new_local_copy) + if new_local_copy.save(:validate => false) + logger.info "Created local copy of #{self.class_name} with the ID #{new_local_copy.id}. #{new_local_copy.to_s}" + else + logger.error "Couldn't create a local copy of #{self.class_name} with the ID #{new_local_copy.id}. #{new_local_copy.errors.to_yaml}" + end + + when 'update' + local_copy = find_local_copy + if local_copy + # Only update an object if the update it self is newer than the local object. + # + if local_copy.updated_at < JSON(self.content)['updated_at'].to_time + local_copy.update_attributes(JSON(self.content).delete_if{|key, value| ['id','updated_at','created_at'].include?(key) }, :without_protection => true) + find_and_connect_to_an_association(local_copy) + if local_copy.save(:validate => false) + logger.info "Updated local copy of #{self.class_name} with the ID #{local_copy.id}. #{local_copy.to_s}" + else + logger.error "Couldn't update local copy of #{self.class_name} with the ID #{local_copy.id}. #{local_copy.errors.to_yaml}" + end + else + logger.error "Didn't update local copy of #{self.class_name} with the ID #{local_copy.id} because of a race condition (the local version was newer than the update). Please check GsClusterSyncLogEntry ID #{self.id}." + end + else + logger.error "Couldn't find local copy of #{self.class_name}. #{self.content}" + end + + when 'destroy' + local_copy = find_local_copy + if local_copy + local_copy.destroy + logger.info "Destroyed local copy of #{self.class_name} with the ID #{local_copy.id}. #{local_copy.to_s}" + else + logger.error "Couldn't find local copy of #{self.class_name}. #{self.content}" + end + end + else + logger.error "The class #{self.class_name} doesn't offer the attribute is_native. Can't synchronize without." + end + end + end + + def find_local_copy + self.class_name.constantize.find_by_uuid(JSON(self.content)['uuid']) + end + + # Connect to the association (e.g. User to a SipAccount) + # + def find_and_connect_to_an_association(local_copy) + if !(self.association_method.blank? || self.association_uuid.blank?) && (self.association_method_changed? || self.association_uuid_changed?) + name_of_the_association_type = local_copy.attribute_names.delete_if{|x| !x.include?('_type')}.first + association = local_copy.send(name_of_the_association_type).constantize.where(:uuid => self.association_uuid).first + if association + local_copy.send "#{association_method}=", association + end + end + end + + def populate_other_cluster_nodes + if self.homebase_ip_address == HOMEBASE_IP_ADDRESS && self.waiting_to_be_synced == true + if GsNode.where(:push_updates_to => true).count > 0 + GsNode.where(:push_updates_to => true).each do |gs_node| + RemoteGsNode::GsClusterSyncLogEntry.site = gs_node.site + remote_enty = RemoteGsNode::GsClusterSyncLogEntry.create(self.attributes.delete_if{|key, value| ['id','updated_at','created_at'].include?(key) }) + self.update_attributes(:waiting_to_be_synced => false) + self.save + end + end + end + end + +end diff --git a/app/models/gs_node.rb b/app/models/gs_node.rb new file mode 100644 index 0000000..229ceb2 --- /dev/null +++ b/app/models/gs_node.rb @@ -0,0 +1,30 @@ +class GsNode < ActiveRecord::Base + attr_accessible :name, :ip_address, :site, :element_name, :push_updates_to, :accepts_updates_from + + has_many :phone_numbers, :foreign_key => :gs_node_id, :dependent => :destroy + has_many :users, :foreign_key => :gs_node_id, :dependent => :destroy + has_many :sip_accounts, :foreign_key => :gs_node_id, :dependent => :destroy + has_many :hunt_groups, :foreign_key => :gs_node_id, :dependent => :destroy + + validates :name, + :presence => true + + validates :ip_address, + :presence => true + + validates :site, + :presence => true + + validates :element_name, + :presence => true + + def to_s + name + end + + def synced + self.last_sync = Time.now + return self.save + end + +end diff --git a/app/models/gui_function.rb b/app/models/gui_function.rb new file mode 100644 index 0000000..e27a8d2 --- /dev/null +++ b/app/models/gui_function.rb @@ -0,0 +1,40 @@ +class GuiFunction < ActiveRecord::Base + attr_accessible :category, :name, :description, :gui_function_memberships_attributes + + has_many :gui_function_memberships, :dependent => :destroy + has_many :user_groups, :through => :gui_function_memberships + + accepts_nested_attributes_for :gui_function_memberships + + validates :name, :presence => true, + :format => { :with => /\A[a-z_0-9]+\z/, :message => "Only lower case letters allowed" }, + :length => { :in => 3..255 }, + :uniqueness => true + + def to_s + self.name + end + + def self.display?(function_name = nil, user) + if function_name.blank? || GemeinschaftSetup.count == 0 + true + else + if !user || user.class != User || function_name.class != String + false + else + function_name = function_name.downcase + + activated_gui_function_names = GuiFunctionMembership.where(:user_group_id => user.user_group_ids, :activated => true).map{|gui_function_membership| gui_function_membership.gui_function.name}.uniq + deactivated_gui_function_names = GuiFunctionMembership.where(:user_group_id => user.user_group_ids, :activated => false).map{|gui_function_membership| gui_function_membership.gui_function.name}.uniq + + deactivated_gui_function_names = deactivated_gui_function_names - activated_gui_function_names + + if deactivated_gui_function_names.include?(function_name) + false + else + true + end + end + end + end +end diff --git a/app/models/gui_function_membership.rb b/app/models/gui_function_membership.rb new file mode 100644 index 0000000..d2bc7cd --- /dev/null +++ b/app/models/gui_function_membership.rb @@ -0,0 +1,7 @@ +class GuiFunctionMembership < ActiveRecord::Base + belongs_to :gui_function + belongs_to :user_group + + validates_associated :gui_function + validates_associated :user_group +end diff --git a/app/models/hunt_group.rb b/app/models/hunt_group.rb new file mode 100644 index 0000000..276ae53 --- /dev/null +++ b/app/models/hunt_group.rb @@ -0,0 +1,43 @@ +class HuntGroup < ActiveRecord::Base + attr_accessible :name, :strategy, :seconds_between_jumps, :phone_numbers_attributes + + belongs_to :tenant + has_many :call_forwards, :as => :call_forwardable, :dependent => :destroy + + validates_uniqueness_of :name, :scope => :tenant_id, + :allow_nil => true, :allow_blank => true + + validates_presence_of :strategy + validates_inclusion_of :strategy, :in => HUNT_GROUP_STRATEGIES + + validates_presence_of :seconds_between_jumps, + :if => Proc.new{ |hunt_group| hunt_group.strategy != 'ring_all' } + validates_numericality_of :seconds_between_jumps, + :only_integer => true, + :greater_than_or_equal_to => VALID_SECONDS_BETWEEN_JUMPS_VALUES.min, + :less_than_or_equal_to => VALID_SECONDS_BETWEEN_JUMPS_VALUES.max, + :if => Proc.new{ |hunt_group| hunt_group.strategy != 'ring_all' } + validates_inclusion_of :seconds_between_jumps, + :in => VALID_SECONDS_BETWEEN_JUMPS_VALUES, + :if => Proc.new{ |hunt_group| hunt_group.strategy != 'ring_all' } + validates_inclusion_of :seconds_between_jumps, + :in => [nil], + :if => Proc.new{ |hunt_group| hunt_group.strategy == 'ring_all' } + + validates_presence_of :uuid + validates_uniqueness_of :uuid + + has_many :hunt_group_members, :dependent => :destroy, :order => :position + + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + accepts_nested_attributes_for :phone_numbers, + :reject_if => lambda { |phone_number| phone_number[:number].blank? }, + :allow_destroy => true + + has_many :hunt_group_members, :dependent => :destroy + + def to_s + self.name || I18n.t('hunt_groups.name') + ' ID ' + self.id.to_s + end + +end diff --git a/app/models/hunt_group_member.rb b/app/models/hunt_group_member.rb new file mode 100644 index 0000000..7d9d3e0 --- /dev/null +++ b/app/models/hunt_group_member.rb @@ -0,0 +1,67 @@ +class HuntGroupMember < ActiveRecord::Base + attr_accessible :name, :active, :can_switch_status_itself, :phone_numbers_attributes + + belongs_to :hunt_group + validates_presence_of :hunt_group + + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + accepts_nested_attributes_for :phone_numbers, + :reject_if => lambda { |phone_number| phone_number[:number].blank? }, + :allow_destroy => true + + acts_as_list :scope => :hunt_group + + after_save :set_presence + after_save :trigger_connected_call_forward_if_necessary + + def to_s + self.name || I18n.t('hunt_group_members.name') + ' ID ' + self.id.to_s + end + + private + def set_presence + dialplan_function = nil + state = 'terminated' + + if self.active + state = 'confirmed' + end + + require 'freeswitch_event' + event = FreeswitchEvent.new("PRESENCE_IN") + event.add_header("proto", "sip") + event.add_header("from", "f-hgmtg-#{self.id}@#{SipDomain.first.host}") + event.add_header("event_type", "presence") + event.add_header("alt_event_type", "dialog") + event.add_header("presence-call-direction", "outbound") + event.add_header("answer-state", state) + event.add_header("unique-id", "hunt_group_member_#{self.id}") + return event.fire() + end + + # Turn on/off a connected CallForward. + # The last member who leaves the hunt_group deactivates the CallForward and the + # first member actives it. + # + def trigger_connected_call_forward_if_necessary + if self.active_changed? && self.hunt_group.hunt_group_members.count > 0 + # deactive CallForward + # + if self.hunt_group.hunt_group_members.where(:active => false).count == self.hunt_group.hunt_group_members.count + self.hunt_group.call_forwards.where(:active => true).each do |x| + x.update_attributes({:active => false}) + end + end + + # active CallForward + # + if self.hunt_group.hunt_group_members.where(:active => true).count > 0 + self.hunt_group.call_forwards.where(:active => false).each do |x| + x.update_attributes({:active => true}) + end + end + end + end + + +end diff --git a/app/models/language.rb b/app/models/language.rb new file mode 100644 index 0000000..1b9c2c0 --- /dev/null +++ b/app/models/language.rb @@ -0,0 +1,11 @@ +class Language < ActiveRecord::Base + has_many :tenants + has_many :users + + validates_presence_of :name + validates_presence_of :code + + def to_s + name + end +end diff --git a/app/models/manufacturer.rb b/app/models/manufacturer.rb new file mode 100644 index 0000000..03d2bb7 --- /dev/null +++ b/app/models/manufacturer.rb @@ -0,0 +1,46 @@ +class Manufacturer < ActiveRecord::Base + attr_accessible :name, :ieee_name, :homepage_url + + # Associations: + # + has_many :ouis, :dependent => :destroy + has_many :phone_models, :order => :name, :dependent => :destroy + + + # Validations: + # + validates_presence_of :name + validates_presence_of :ieee_name + + validates_uniqueness_of :name, :case_sensitive => false + + validate :validate_homepage_url + + # State Machine stuff + default_scope where(:state => 'active').order(:name) + state_machine :initial => :active do + + event :deactivate do + transition [:active] => :deactivated + end + + event :activate do + transition [:deactivated] => :active + end + end + + def to_s + self.name + end + + private + + def validate_homepage_url + if ! self.homepage_url.blank? + if ! CustomValidators.validate_url( self.homepage_url ) + errors.add( :homepage_url, "is invalid." ) + end + end + end + +end diff --git a/app/models/oui.rb b/app/models/oui.rb new file mode 100644 index 0000000..9a5bb1f --- /dev/null +++ b/app/models/oui.rb @@ -0,0 +1,17 @@ +class Oui < ActiveRecord::Base + attr_accessible :value + + validates_presence_of :manufacturer + validates_presence_of :value + + belongs_to :manufacturer + + # State Machine stuff + default_scope where(:state => 'active') + state_machine :initial => :active do + end + + def to_s + value + end +end diff --git a/app/models/phone.rb b/app/models/phone.rb new file mode 100644 index 0000000..89371eb --- /dev/null +++ b/app/models/phone.rb @@ -0,0 +1,240 @@ +require 'scanf' + +class Phone < ActiveRecord::Base + + attr_accessible :mac_address, :ip_address, :http_user, :http_password, + :phone_model_id, :hot_deskable, :nightly_reboot, + :provisioning_key, :provisioning_key_active + + # Associations + # + belongs_to :phone_model + belongs_to :phoneable, :polymorphic => true + + has_many :phone_sip_accounts, :dependent => :destroy, :uniq => true, :order => :position + has_many :sip_accounts, :through => :phone_sip_accounts + + # Validations + # + before_validation :sanitize_mac_address + + validates_presence_of :mac_address + validate_mac_address :mac_address + validates_uniqueness_of :mac_address + + validates_uniqueness_of :ip_address, + :if => Proc.new { |me| ! me.ip_address.blank? } + validate_ip_address :ip_address, + :if => Proc.new { |me| ! me.ip_address.blank? } + + validates_presence_of :phone_model + validates_presence_of :phoneable + + before_save :save_last_ip_address + before_save :destroy_phones_sip_accounts_if_phoneable_changed + before_save :remove_ip_address_when_mac_address_was_changed + + # State machine: + # + default_scope where(:state => 'active') + state_machine :initial => :active do + + event :deactivate do + transition [:active] => :deactivated + end + + event :activate do + transition [:deactivated] => :active + end + end + + def to_s + "%s %s %s" % [ + pretty_mac_address, + "(#{self.phone_model})", + self.ip_address ? "(#{self.ip_address})" : "", + ] + end + + def pretty_mac_address + return [].fill('%02X', 0, 6).join(':') % self.mac_address.scanf( '%2X' * 6 ) + end + + + def resync(reboot = false, sip_account = nil) + if ! self.phone_model || ! self.phone_model.manufacturer + return false + end + + if self.phone_model.manufacturer.ieee_name == 'SNOM Technology AG' + if !sip_account + self.sip_accounts.where(:sip_accountable_type => self.phoneable_type).each do |sip_account_associated| + if sip_account_associated.registration + sip_account = sip_account_associated + break + end + end + end + + if ! sip_account or ! sip_account.registration + require 'open-uri' + begin + if open("http://#{self.ip_address}/advanced_update.htm?reboot=Reboot", :http_basic_authentication=>[self.http_user, self.http_password], :proxy => nil) + return true + end + rescue + return false + end + end + + require 'freeswitch_event' + event = FreeswitchEvent.new("NOTIFY") + event.add_header("profile", "gemeinschaft") + event.add_header("event-string", "check-sync;reboot=#{reboot.to_s}") + event.add_header("user", sip_account.auth_name) + event.add_header("host", sip_account.sip_domain.host) + event.add_header("content-type", "application/simple-message-summary") + return event.fire() + + elsif self.phone_model.manufacturer.ieee_name == 'Siemens Enterprise CommunicationsGmbH & Co. KG' + require 'open-uri' + begin + if open("http://#{self.ip_address}:8085/contact_dls.html/ContactDLS", :http_basic_authentication=>[self.http_user, self.http_password], :proxy => nil) + return true + end + rescue + return false + end + end + + return false + end + + + # OPTIMIZE i18n translations + def user_login(user, sip_account = nil) + if ! self.hot_deskable + errors.add(:hot_deskable, "Phone not hot-deskable") + return false + end + + phones_affected = Hash.new() + sip_accounts = Array.new(1, sip_account) + + if !sip_account + sip_accounts = user.sip_accounts.where(:hotdeskable => true).all + end + + if sip_accounts.blank? + errors.add(:sip_accounts, "No hot-deskable Sip Accounts available") + return false + end + + sip_account_resync = self.sip_accounts.where(:sip_accountable_type => self.phoneable_type).first + + phones_affected.each_pair do |id,phone| + if phone.id != self.id + phone.user_logout() + end + end + + self.phoneable = user + sip_accounts.each do |sip_account| + if ! self.sip_accounts.where(:id => sip_account.id).first + self.sip_accounts.push(sip_account) + end + end + + @not_destroy_phones_sip_accounts = true + if ! self.save + return false + end + + sleep(0.5) + + if ! self.resync(true, sip_account_resync) + errors.add(:resync, "Resync failed") + return false + end + + return true + end + + + # OPTIMIZE i18n translations + def user_logout + if ! self.hot_deskable or self.phoneable_type == 'Tenant' + errors.add(:hot_deskable, "Phone not hot-deskable") + return false + end + + sip_account = self.sip_accounts.where(:sip_accountable_type => self.phoneable_type).first + + tenant_sip_account = self.sip_accounts.where(:sip_accountable_type => 'Tenant').first + if tenant_sip_account + tenant = tenant_sip_account.sip_accountable + end + + sip_account_ids = Array.new() + self.sip_accounts.where(:sip_accountable_type => 'User', :hotdeskable => true).each do |sip_account| + sip_account_ids.push(sip_account.id) + end + + if tenant + self.phoneable = tenant + @not_destroy_phones_sip_accounts = true + if ! self.save + errors.add(:phoneable, "Could not change owner") + return false + end + end + + if ! PhoneSipAccount.destroy_all(:sip_account_id => sip_account_ids) + errors.add(:sip_accounts, "Could not delete sip_accounts") + return false + end + + sleep(0.5) + + if ! self.resync(true, sip_account) + errors.add(:resync, "Resync failed") + return false + end + + return true + end + + private + + # Sanitize MAC address. + # + def sanitize_mac_address + self.mac_address = self.mac_address.to_s.upcase.gsub( /[^A-F0-9]/, '' ) + end + + # Saves the last IP address. + # + def save_last_ip_address + if self.ip_address_changed? \ + && self.ip_address != self.ip_address_was + self.last_ip_address = self.ip_address_was + end + end + + # When ever the parent of a phone changes all the SIP accounts associations + # are destroyed unless this is a user logout operation + # + def destroy_phones_sip_accounts_if_phoneable_changed + if (self.phoneable_type_changed? || self.phoneable_id_changed?) && ! @not_destroy_phones_sip_accounts + self.phone_sip_accounts.destroy_all + end + end + + def remove_ip_address_when_mac_address_was_changed + if self.mac_address_changed? + self.ip_address = nil + self.last_ip_address = nil + end + end + +end diff --git a/app/models/phone_book.rb b/app/models/phone_book.rb new file mode 100644 index 0000000..3603eae --- /dev/null +++ b/app/models/phone_book.rb @@ -0,0 +1,33 @@ +class PhoneBook < ActiveRecord::Base + attr_accessible :name, :description, :uuid + + belongs_to :phone_bookable, :polymorphic => true + has_many :phone_book_entries, :dependent => :destroy + + validates_presence_of :name + validates_uniqueness_of :name, :scope => [ :phone_bookable_type, :phone_bookable_id ] + + validates_length_of :name, :within => 1..50 + + validates_presence_of :uuid + validates_uniqueness_of :uuid + + # State Machine stuff + default_scope where(:state => 'active') + state_machine :initial => :active do + end + + def to_s + name + end + + def find_entry_by_number(number) + phone_book_entries_ids = self.phone_book_entries.map{|phone_book_entry| phone_book_entry.id} + + phone_number = PhoneNumber.where(:phone_numberable_id => phone_book_entries_ids, :phone_numberable_type => 'PhoneBookEntry', :number => number).first + + if phone_number + return phone_number.phone_numberable + end + end +end diff --git a/app/models/phone_book_entry.rb b/app/models/phone_book_entry.rb new file mode 100644 index 0000000..db2b44b --- /dev/null +++ b/app/models/phone_book_entry.rb @@ -0,0 +1,109 @@ +# encoding: UTF-8 + +class PhoneBookEntry < ActiveRecord::Base + before_save :run_phonetic_algorithm + before_save :save_value_of_to_s + + attr_accessible :first_name, :middle_name, :last_name, :title, :nickname, :organization, :is_organization, :department, :job_title, :is_male, :birthday, :birth_name, :description, :homepage_personal, :homepage_organization, :twitter_account, :facebook_account, :google_plus_account, :xing_account, :linkedin_account, :mobileme_account, :image + + belongs_to :phone_book + has_many :conference_invitees, :dependent => :destroy + + acts_as_list :scope => :phone_book + + validates_presence_of :phone_book + + validates_presence_of :last_name, + :unless => Proc.new { |entry| entry.is_organization } + + validates_presence_of :organization, + :if => Proc.new { |entry| entry.is_organization } + + validates_inclusion_of :is_male, :in => [true, false, 1, '1', 'on'], + :unless => Proc.new { |entry| entry.is_organization } + + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + + has_many :addresses, :dependent => :destroy + + # Avatar like photo + mount_uploader :image, ImageUploader + + # TODO Validate homepage URLs and social media accounts. + + + default_scope where(:state => 'active') + + # State Machine stuff + state_machine :initial => :active do + end + + def to_s + if self.is_organization + "#{self.organization}".strip + else + [self.last_name.strip, self.first_name.strip].join(', ') + end + end + + def self.koelner_phonetik(input) + if input.blank? + nil + else + # TODO: koelner_phonetik() needs to be tested. + + # Umwandeln in Grossbuchstaben + phonetik = input.upcase.gsub(/[^A-ZÜüÖöÄäß]/,'').strip + + # Umwandeln anhand der Tabelle auf + # http://de.wikipedia.org/wiki/K%C3%B6lner_Verfahren + phonetik = phonetik.gsub(/([XKQ])X/, '\1'+'8') + phonetik = phonetik.gsub(/[DT]([CSZ])/, '8'+'\1') + phonetik = phonetik.gsub(/C([^AHKOQUX])/, '8'+'\1') + phonetik = phonetik.gsub(/^C([^AHKLOQRUX])/, '8'+'\1') + phonetik = phonetik.gsub(/([SZ])C/, '\1'+'8') + phonetik = phonetik.gsub(/[SZß]/, '8') + phonetik = phonetik.gsub(/R/, '7') + phonetik = phonetik.gsub(/[MN]/, '6') + phonetik = phonetik.gsub(/L/, '5') + phonetik = phonetik.gsub(/X/, '48') + phonetik = phonetik.gsub(/([^SZ])C([AHKOQUX])/, '\1'+'4'+'\2' ) + phonetik = phonetik.gsub(/^C([AHKLOQRUX])/, '4'+'\1') + phonetik = phonetik.gsub(/[GKQ]/, '4') + phonetik = phonetik.gsub(/PH/, '3H') + phonetik = phonetik.gsub(/[FVW]/, '3') + phonetik = phonetik.gsub(/[DT]([^CSZ])/, '2'+'\1') + phonetik = phonetik.gsub(/[BP]/, '1') + phonetik = phonetik.gsub(/H/, '') + phonetik = phonetik.gsub(/[AEIJOUYÜüÖöÄä]/, '0') + + # Regeln für Buchstaben am Ende des Wortes + phonetik = phonetik.gsub(/P/, '1') + phonetik = phonetik.gsub(/[DT]/, '2') + phonetik = phonetik.gsub(/C/, '8') + + # Entfernen aller doppelten + phonetik = phonetik.gsub(/([0-9])\1+/, '\1') + + # Entfernen aller Codes "0" außer am Anfang. + phonetik = phonetik.gsub(/^0/, 'X') + phonetik = phonetik.gsub(/0/, '') + phonetik = phonetik.gsub(/^X/, '0') + + phonetik + end + end + + private + + def run_phonetic_algorithm + self.first_name_phonetic = PhoneBookEntry.koelner_phonetik(self.first_name) if self.first_name_changed? + self.last_name_phonetic = PhoneBookEntry.koelner_phonetik(self.last_name) if self.last_name_changed? + self.organization_phonetic = PhoneBookEntry.koelner_phonetik(self.organization) if self.organization_changed? + end + + def save_value_of_to_s + self.value_of_to_s = self.to_s + end + +end diff --git a/app/models/phone_model.rb b/app/models/phone_model.rb new file mode 100644 index 0000000..e00e0e3 --- /dev/null +++ b/app/models/phone_model.rb @@ -0,0 +1,56 @@ +class PhoneModel < ActiveRecord::Base + attr_accessible :name, :product_manual_homepage_url, :product_homepage_url, :uuid + + # Associations + # + belongs_to :manufacturer + + has_many :phones, :dependent => :destroy + + # Validations + # + validates_presence_of :name + validate :validate_product_manual_homepage_url + validate :validate_product_homepage_url + + validates_presence_of :uuid + validates_uniqueness_of :uuid + + def to_s + self.name + end + + # State machine: + # + default_scope where(:state => 'active') + state_machine :initial => :active do + + event :deactivate do + transition [:active] => :deactivated + end + + event :activate do + transition [:deactivated] => :active + end + end + + + private + + def validate_product_manual_homepage_url + if ! self.product_manual_homepage_url.blank? + if ! CustomValidators.validate_url( self.product_manual_homepage_url ) + errors.add( :product_manual_homepage_url, "is invalid." ) + end + end + end + + def validate_product_homepage_url + if ! self.product_homepage_url.blank? + if ! CustomValidators.validate_url( self.product_homepage_url ) + errors.add( :product_homepage_url, "is invalid." ) + end + end + end + +end diff --git a/app/models/phone_number.rb b/app/models/phone_number.rb new file mode 100644 index 0000000..4c0cf46 --- /dev/null +++ b/app/models/phone_number.rb @@ -0,0 +1,304 @@ +class PhoneNumber < ActiveRecord::Base + NUMBER_TYPES_INBOUND = ['SipAccount', 'Conference', 'FaxAccount', 'Callthrough', 'HuntGroup'] + + attr_accessible :name, :number, :gs_node_id, :access_authorization_user_id + + has_many :call_forwards, :dependent => :destroy + + has_many :ringtones, :as => :ringtoneable, :dependent => :destroy + + belongs_to :phone_numberable, :polymorphic => true + + belongs_to :gs_node + + validates_uniqueness_of :number, :scope => [:phone_numberable_type, :phone_numberable_id] + + validate :validate_inbound_uniqueness + + before_save :save_value_of_to_s + after_create :copy_existing_call_forwards_if_necessary + before_validation :'parse_and_split_number!' + validate :validate_number, :if => Proc.new { |phone_number| STRICT_INTERNAL_EXTENSION_HANDLING && STRICT_DID_HANDLING } + validate :check_if_number_is_available, :if => Proc.new { |phone_number| STRICT_INTERNAL_EXTENSION_HANDLING && STRICT_DID_HANDLING } + + acts_as_list :scope => [:phone_numberable_id, :phone_numberable_type] + + # Sync other nodes when this is a cluster. + # + validates_presence_of :uuid + validates_uniqueness_of :uuid + after_create { self.create_on_other_gs_nodes('phone_numberable', self.phone_numberable.try(:uuid)) } + after_destroy :destroy_on_other_gs_nodes + after_update { self.update_on_other_gs_nodes('phone_numberable', self.phone_numberable.try(:uuid)) } + + # State machine: + # + default_scope where(:state => 'active') + state_machine :initial => :active do + + event :deactivate do + transition [:active] => :deactivated + end + + event :activate do + transition [:deactivated] => :active + end + end + + + def to_s + parts = [] + parts << "+#{self.country_code}" if self.country_code + parts << self.area_code if self.area_code + parts << self.central_office_code if self.central_office_code + parts << self.subscriber_number if self.subscriber_number + + if parts.empty? + return self.number + end + return parts.join("-") + end + + # Parse a number in a tenant's context (respect the tenant's country) + # + def self.parse( number, tenant=nil ) + number = number.to_s.gsub( /[^0-9+]/, '' ) + + if tenant.class.name == 'Tenant' + country = tenant.country + else + tenant = nil + country = GemeinschaftSetup.first.try(:country) + country ||= Country.where(:name => "Germany").first + end + + parts = { + :country_code => nil, + :area_code => nil, + :central_office_code => nil, + :subscriber_number => nil, + :extension => nil, + } + + if country + if ! country.international_call_prefix.blank? + number = number.gsub( /^#{Regexp.escape( country.international_call_prefix )}/, '+' ) + end + if ! country.trunk_prefix.blank? + number = number.gsub( /^#{Regexp.escape( country.trunk_prefix )}/, "+#{country.country_code}" ) + end + end + + if number.match( /^[+]/ ) + parts = self.parse_international_number( number.gsub(/[^0-9]/,'') ) + return nil if parts.nil? + else + # Check if the number is an internal extension. + if tenant + internal_extension_range = tenant.phone_number_ranges.where(:name => INTERNAL_EXTENSIONS).first + if internal_extension_range + if internal_extension_range.phone_numbers.where(:number => number).length > 0 + parts[:extension] = number + end + end + end + + # Otherwise assume the number is a special number such as an emergency number. + if ! parts[:extension] + parts[:subscriber_number] = number + end + end + + # return nil if all parts are blank: + return nil if ( + parts[:country_code].blank? && + parts[:area_code].blank? && + parts[:central_office_code].blank? && + parts[:subscriber_number].blank? && + parts[:extension].blank? + ) + parts # return value + end + + def self.parse_and_format( number, tenant=nil ) + attributes = PhoneNumber.parse(number, tenant) + if attributes + formated_number = attributes.map{|key,value| value}.delete_if{|x| x.nil?}.join('-') + formated_number = "+#{formated_number}" if attributes[:country_code] + return formated_number + end + return number + end + + # Parse an international number. + # Assumed format for +number+ is e.g. "49261200000" + # + def self.parse_international_number( number ) + number = number.to_s.gsub( /[^0-9]/, '' ) + + parts = { + :country_code => nil, + :area_code => nil, + :central_office_code => nil, + :subscriber_number => nil, + :extension => nil, + } + + # Find country by country code: + country = Country.where( :country_code => number[0, 3]).first + country ||= Country.where( :country_code => number[0, 2]).first + country ||= Country.where( :country_code => number[0, 1]).first + + return nil if ! country # invalid number format + + parts[:country_code] = country.country_code + remainder = number[ parts[:country_code].length, 999 ] # e.g. "261200000" + + case parts[:country_code] + + when '1' + # Assure an NANP number + return nil if ! remainder.match(/[2-9]{1}[0-9]{2}[2-9]{1}[0-9]{2}[0-9]{4}/) + + # Shortcut for NANPA closed dialplan: + parts[:area_code ] = remainder[ 0, 3] + parts[:central_office_code ] = remainder[ 3, 3] + parts[:subscriber_number ] = remainder[ 6, 4] + else + # variable-length dialplan, e.g. Germany + + # Find longest area_code for the country: + longest_area_code = country.area_codes.order( "LENGTH(area_code) DESC" ).first + + # Find a matching area_code: + if longest_area_code + longest_area_code.area_code.length.downto(1) do |area_code_length| + area_code = country.area_codes.where( :area_code => remainder[ 0, area_code_length ] ).first + if area_code + parts[:area_code] = area_code.area_code + break + end + end + + return nil if ! parts[:area_code] # No matching area_code for the country. + + remainder = remainder.gsub( /^#{parts[:area_code]}/, '' ) + #remainder = number[ parts[:area_code].length, 999 ] # e.g. "200000" + end + parts[:subscriber_number] = remainder + end + + parts # return value + end + + def parse_and_split_number! + if self.phone_numberable_type == 'PhoneNumberRange' && self.phone_numberable.name == INTERNAL_EXTENSIONS + # The parent is the PhoneNumberRange INTERNAL_EXTENSIONS. Therefor it must be an extensions. + # + self.country_code = nil + self.area_code = nil + self.subscriber_number = nil + self.central_office_code = nil + self.extension = self.number.to_s.strip + else + if self.tenant && + self.tenant.phone_number_ranges.exists?(:name => INTERNAL_EXTENSIONS) && + self.tenant.phone_number_ranges.where(:name => INTERNAL_EXTENSIONS).first.phone_numbers.exists?(:number => self.number) + self.country_code = nil + self.area_code = nil + self.subscriber_number = nil + self.central_office_code = nil + self.extension = self.number.to_s.strip + else + parsed_number = PhoneNumber.parse( self.number ) + if parsed_number + self.country_code = parsed_number[:country_code] + self.area_code = parsed_number[:area_code] + self.subscriber_number = parsed_number[:subscriber_number] + self.extension = parsed_number[:extension] + self.central_office_code = parsed_number[:central_office_code] + + self.number = self.to_s.gsub( /[^\+0-9]/, '' ) + end + end + end + end + + # Find the (grand-)parent tenant of this phone number: + # + def tenant + #OPTIMIZE Add a tenant_id to SipAccount + case self.phone_numberable + when SipAccount + self.phone_numberable.tenant + when Conference + case self.phone_numberable.conferenceable + when Tenant + self.phone_numberable.conferenceable + when User + self.phone_numberable.conferenceable.current_tenant #OPTIMIZE + when UserGroup + self.phone_numberable.conferenceable.tenant + end + end + end + + def move_up? + return self.position.to_i > PhoneNumber.where(:phone_numberable_id => self.phone_numberable_id, :phone_numberable_type => self.phone_numberable_type ).order(:position).first.position.to_i + end + + def move_down? + return self.position.to_i < PhoneNumber.where(:phone_numberable_id => self.phone_numberable_id, :phone_numberable_type => self.phone_numberable_type ).order(:position).last.position.to_i + end + + private + + def validate_number + if ! PhoneNumber.parse( self.number ) + errors.add( :number, "is invalid." ) + end + end + + def check_if_number_is_available + if self.phone_numberable_type != 'PhoneBookEntry' && self.tenant + + phone_number_ranges = self.tenant.phone_number_ranges.where( + :name => [INTERNAL_EXTENSIONS, DIRECT_INWARD_DIALING_NUMBERS] + ) + if !phone_number_ranges.empty? + if !PhoneNumber.where(:phone_numberable_type => 'PhoneNumberRange'). + where(:phone_numberable_id => phone_number_ranges). + exists?(:number => self.number) + errors.add(:number, "isn't defined as an extenation or DID for the tenant '#{self.tenant}'. #{phone_number_ranges.inspect}") + end + end + end + end + + def validate_inbound_uniqueness + if NUMBER_TYPES_INBOUND.include?(self.phone_numberable_type) + numbering_scope = PhoneNumber.where(:state => 'active', :number => self.number, :phone_numberable_type => NUMBER_TYPES_INBOUND) + if numbering_scope.where(:id => self.id).count == 0 && numbering_scope.count > 0 + errors.add(:number, 'not unique') + end + end + end + + def save_value_of_to_s + self.value_of_to_s = self.to_s + end + + def copy_existing_call_forwards_if_necessary + if self.phone_numberable.class == SipAccount && self.phone_numberable.callforward_rules_act_per_sip_account == true + sip_account = SipAccount.find(self.phone_numberable) + if sip_account.phone_numbers.where('id != ?', self.id).count > 0 + if sip_account.phone_numbers.where('id != ?', self.id).order(:created_at).first.call_forwards.count > 0 + sip_account.phone_numbers.where('id != ?', self.id).first.call_forwards.each do |call_forward| + call_forward.set_this_callforward_rule_to_all_phone_numbers_of_the_parent_sip_account + end + end + end + end + end + +end diff --git a/app/models/phone_number_range.rb b/app/models/phone_number_range.rb new file mode 100644 index 0000000..2fdd9b6 --- /dev/null +++ b/app/models/phone_number_range.rb @@ -0,0 +1,16 @@ +class PhoneNumberRange < ActiveRecord::Base + attr_accessible :name, :description + + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + belongs_to :phone_number_rangeable, :polymorphic => true + + validates_presence_of :name + validates_uniqueness_of :name, :scope => [:phone_number_rangeable_id, :phone_number_rangeable_type] + validates_inclusion_of :name, :in => [INTERNAL_EXTENSIONS, DIRECT_INWARD_DIALING_NUMBERS, SERVICE_NUMBERS] + validates_presence_of :phone_number_rangeable_id + validates_presence_of :phone_number_rangeable + + def to_s + name + end +end diff --git a/app/models/phone_sip_account.rb b/app/models/phone_sip_account.rb new file mode 100644 index 0000000..449ba39 --- /dev/null +++ b/app/models/phone_sip_account.rb @@ -0,0 +1,17 @@ +class PhoneSipAccount < ActiveRecord::Base + attr_accessible :sip_account_id + + belongs_to :phone + belongs_to :sip_account + + validates_presence_of :phone + validates_presence_of :sip_account + + validates_uniqueness_of :sip_account_id, :scope => :phone_id + + acts_as_list :scope => :phone + + def to_s + "Position #{self.position}" + end +end diff --git a/app/models/remote_gs_node/gs_cluster_sync_log_entry.rb b/app/models/remote_gs_node/gs_cluster_sync_log_entry.rb new file mode 100644 index 0000000..843494e --- /dev/null +++ b/app/models/remote_gs_node/gs_cluster_sync_log_entry.rb @@ -0,0 +1,9 @@ +# Find docu about ActiveResource at +# http://ofps.oreilly.com/titles/9780596521424/activeresource_id59243.html +# test = RemoteGSNode::GcLogEntry.first.attributes.delete_if{|key, value| ['id','updated_at','created_at'].include?(key) }) + +module RemoteGsNode + class GsClusterSyncLogEntry < ActiveResource::Base + self.site = 'http://0.0.0.0:3000' + end +end \ No newline at end of file diff --git a/app/models/ringtone.rb b/app/models/ringtone.rb new file mode 100644 index 0000000..36053c0 --- /dev/null +++ b/app/models/ringtone.rb @@ -0,0 +1,15 @@ +class Ringtone < ActiveRecord::Base + attr_accessible :audio, :bellcore_id + + mount_uploader :audio, AudioUploader + validates_presence_of :audio, :if => Proc.new{ |ringtone| ringtone.bellcore_id.blank? } + validates_presence_of :ringtoneable_type + validates_presence_of :ringtoneable_id + validates_presence_of :ringtoneable + + belongs_to :ringtoneable, :polymorphic => true + + def to_s + self.bellcore_id.to_s + end +end diff --git a/app/models/sip_account.rb b/app/models/sip_account.rb new file mode 100644 index 0000000..8459265 --- /dev/null +++ b/app/models/sip_account.rb @@ -0,0 +1,221 @@ +# encoding: UTF-8 + +class SipAccount < ActiveRecord::Base + include ActionView::Helpers::TextHelper + + attr_accessible :auth_name, :caller_name, :password, :voicemail_pin, + :tenant_id, :call_waiting, :clir, :clip_no_screening, + :clip, :description, :callforward_rules_act_per_sip_account, + :hotdeskable, :gs_node_id + + # Associations: + # + belongs_to :sip_accountable, :polymorphic => true + + has_many :phone_sip_accounts, :uniq => true + has_many :phones, :through => :phone_sip_accounts + + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + has_many :call_forwards, :through => :phone_numbers + + belongs_to :tenant + belongs_to :sip_domain + + has_many :softkeys, :dependent => :destroy + + has_many :voicemail_messages, :foreign_key => 'username', :primary_key => 'auth_name' + + has_many :call_histories, :as => :call_historyable, :dependent => :destroy + + has_one :voicemail_setting, :class_name => "VoicemailSetting", :primary_key => 'auth_name', :foreign_key => 'username', :dependent => :destroy + + belongs_to :gs_node + + # Delegations: + # + delegate :host, :to => :sip_domain, :allow_nil => true + delegate :realm, :to => :sip_domain, :allow_nil => true + + # Validations: + # + validates_presence_of :caller_name + validates_presence_of :sip_accountable + validates_presence_of :tenant + validates_presence_of :sip_domain + + validate_sip_password :password + + validates_format_of :voicemail_pin, :with => /[0-9]+/, + :allow_nil => true, :allow_blank => true + + validates_uniqueness_of :auth_name, :scope => :sip_domain_id + + # Before and after hooks: + # + before_save :save_value_of_to_s + after_save :create_voicemail_setting, :if => :'voicemail_setting == nil' + before_validation :find_and_set_tenant_id + before_validation :set_sip_domain_id + before_validation :convert_umlauts_in_caller_name + before_destroy :remove_sip_accounts_or_logout_phones + + # Sync other nodes when this is a cluster. + # + validates_presence_of :uuid + validates_uniqueness_of :uuid + + after_create { self.create_on_other_gs_nodes('sip_accountable', self.sip_accountable.try(:uuid)) } + after_destroy :destroy_on_other_gs_nodes + after_update { self.update_on_other_gs_nodes('sip_accountable', self.sip_accountable.try(:uuid)) } + + after_update :log_out_phone_if_not_local + + def to_s + truncate((self.caller_name || "SipAccount ID #{self.id}"), :length => TO_S_MAX_CALLER_NAME_LENGTH) + " (#{truncate(self.auth_name, :length => TO_S_MAX_LENGTH_OF_AUTH_NAME)}@...#{self.host.split(/\./)[2,3].to_a.join('.') if self.host })" + end + + def call_forwarding_toggle( call_forwarding_service, to_voicemail = nil ) + if ! self.phone_numbers.first + errors.add(:base, "You must provide at least one phone number") + end + + service_id = CallForwardCase.where(:value => call_forwarding_service).first.id + + call_forwarding_master = self.phone_numbers.first.call_forwards.where(:call_forward_case_id => service_id).order(:active).all(:conditions => 'source IS NULL OR source = ""').first + if ! call_forwarding_master + errors.add(:base, "No call forwarding entries found that could be toggled") + return false + end + + if call_forwarding_master.active + call_forwarding_master.active = false + else + if call_forwarding_service = 'assistant' && call_forwarding_master.call_forwardable_type == 'HuntGroup' && call_forwarding_master.call_forwardable + if call_forwarding_master.call_forwardable.hunt_group_members.where(:active => true).count > 0 + call_forwarding_master.active = true + else + call_forwarding_master.active = false + end + end + end + + self.phone_numbers.each do |phone_number| + call_forwarding = phone_number.call_forwards.where(:call_forward_case_id => service_id).order(:active).all(:conditions => 'source IS NULL OR source = ""').first + if ! call_forwarding + call_forwarding = CallForward.new() + call_forwarding.phone_number_id = phone_number.id + end + + if to_voicemail == nil + to_voicemail = call_forwarding_master.to_voicemail + end + + call_forwarding.call_forward_case_id = call_forwarding_master.call_forward_case_id + call_forwarding.timeout = call_forwarding_master.timeout + call_forwarding.destination = call_forwarding_master.destination + call_forwarding.source = call_forwarding_master.source + call_forwarding.depth = call_forwarding_master.depth + call_forwarding.active = call_forwarding_master.active + call_forwarding.to_voicemail = to_voicemail + + if ! call_forwarding.save + call_forwarding.errors.messages.each_with_index do |(error_key, error_message), index| + errors.add(error_key, "number: #{phone_number}: #{error_message}") + end + end + end + + if errors.empty? + return call_forwarding_master + end + + return false + end + + def registration + return FreeswitchRegistration.where(:reg_user => self.auth_name).first + end + + def call( phone_number ) + require 'freeswitch_event' + return FreeswitchAPI.execute( + 'originate', + "{origination_uuid=#{UUID.new.generate},origination_caller_id_number='#{phone_number}',origination_caller_id_name='Call'}user/#{self.auth_name} #{phone_number}", + true + ); + end + + + private + + def save_value_of_to_s + self.value_of_to_s = self.to_s + end + + def find_and_set_tenant_id + if self.new_record? and self.tenant_id != nil + return + else + tenant = case self.sip_accountable_type + when 'Tenant' ; sip_accountable + when 'UserGroup' ; sip_accountable.tenant + when 'User' ; sip_accountable.try(:current_tenant) || sip_accountable.try(:tenants).try(:last) + else nil + end + self.tenant_id = tenant.id if tenant != nil + end + end + + def set_sip_domain_id + self.sip_domain_id = self.tenant.try(:sip_domain_id) + end + + def convert_umlauts_in_caller_name + if !self.caller_name.blank? + self.caller_name = self.caller_name.sub(/ä/,'ae'). + sub(/Ä/,'Ae'). + sub(/ü/,'ue'). + sub(/Ü/,'Ue'). + sub(/ö/,'oe'). + sub(/Ö/,'Oe'). + sub(/ß/,'ss') + + self.caller_name = self.caller_name.gsub(/[^a-zA-Z0-9\-\,\:\. ]/,'_') + end + end + + # Make sure that a tenant phone goes back to the tenant and doesn't + # get deleted with this user. + # + def remove_sip_accounts_or_logout_phones + self.phones.each do |phone| + if phone.sip_accounts.where(:sip_accountable_type => 'Tenant').count > 0 + phone.user_logout + else + PhoneSipAccount.delete_all(:sip_account_id => self.id) + end + end + self.reload + end + + # log out phone if sip_account is not on this node + def log_out_phone_if_not_local + if self.gs_node_id && ! GsNode.where(:ip_address => HOMEBASE_IP_ADDRESS, :id => self.gs_node_id).first + self.phones.each do |phone| + phone.user_logout; + end + end + end + + def create_voicemail_setting + voicemail_setting = VoicemailSetting.new() + voicemail_setting.username = self.auth_name + voicemail_setting.domain = self.sip_domain.try(:host) + voicemail_setting.password = self.voicemail_pin + voicemail_setting.notify = true + voicemail_setting.attachment = true + voicemail_setting.mark_read = true + voicemail_setting.purge = false + voicemail_setting.save + end +end diff --git a/app/models/sip_domain.rb b/app/models/sip_domain.rb new file mode 100644 index 0000000..252fe4a --- /dev/null +++ b/app/models/sip_domain.rb @@ -0,0 +1,16 @@ +class SipDomain < ActiveRecord::Base + attr_accessible :host, :realm + + has_many :tenants, :dependent => :restrict + has_many :sip_accounts, :dependent => :restrict + + validates_presence_of :host + validates_uniqueness_of :host, :case_sensitive => false + + validates_presence_of :realm + validates_uniqueness_of :realm + + def to_s + self.host + end +end diff --git a/app/models/softkey.rb b/app/models/softkey.rb new file mode 100644 index 0000000..a709036 --- /dev/null +++ b/app/models/softkey.rb @@ -0,0 +1,100 @@ +class Softkey < ActiveRecord::Base + attr_accessible :softkey_function_id, :number, :label, :call_forward_id, :uuid + + belongs_to :sip_account + belongs_to :softkey_function + belongs_to :call_forward + + # Any CallForward BLF must have a valid softkey_call_forward_id. + # + validates_presence_of :call_forward_id, :if => Proc.new{ |softkey| self.softkey_function_id != nil && + self.softkey_function_id == SoftkeyFunction.find_by_name('call_forwarding').try(:id) } + + # These functions need a number to act. + # + validates_presence_of :number, :if => Proc.new{ |softkey| self.softkey_function_id != nil && + ['blf','speed_dial','dtmf','conference'].include?(softkey.softkey_function.name) } + + validates_presence_of :uuid + validates_uniqueness_of :uuid + + acts_as_list :scope => :sip_account + + before_validation :clean_up_and_leave_only_values_which_make_sense_for_the_current_softkey_function_id + after_validation :save_function_name_in_function, :if => Proc.new{ |softkey| self.call_forward_id.blank? } + after_save :resync_phone + after_destroy :resync_phone + + def possible_blf_call_forwards + if self.sip_account.phone_numbers.count == 0 + nil + else + if self.sip_account.callforward_rules_act_per_sip_account == true + # We pick one phone_number and display the rules of it. + # + phone_number = self.sip_account.phone_numbers.order(:number).first + call_forwards = self.sip_account.call_forwards.where(:phone_number_id => phone_number.id) + else + call_forwards = self.sip_account.call_forwards + end + + phone_numbers_ids = self.sip_account.phone_number_ids + phone_numbers = PhoneNumber.where(:id => phone_numbers_ids).pluck(:number) + + hunt_group_ids = PhoneNumber.where(:phone_numberable_type => 'HuntGroupMember', :number => phone_numbers). + map{ |phone_number| phone_number.phone_numberable.hunt_group.id }. + uniq + + call_forwards + CallForward.where(:call_forwardable_type => 'HuntGroup', :call_forwardable_id => hunt_group_ids). + where('phone_number_id NOT IN (?)', phone_numbers_ids) + end + end + + def to_s + if (['call_forwarding'].include?(self.softkey_function.name)) + "#{self.call_forward}" + else + if ['log_out', 'log_in'].include?(self.softkey_function.name) + I18n.t("softkeys.functions.#{self.softkey_function.name}") + else + "#{self.softkey_function.name} : #{self.number.to_s}" + end + end + end + + def resync_phone + phone_sip_account = PhoneSipAccount.find_by_sip_account_id(self.sip_account_id) + if phone_sip_account && phone_sip_account.phone + phone_sip_account.phone.resync() + end + end + + def move_up? + return self.position.to_i > Softkey.where(:sip_account_id => self.sip_account_id ).order(:position).first.position.to_i + end + + def move_down? + return self.position.to_i < Softkey.where(:sip_account_id => self.sip_account_id ).order(:position).last.position.to_i + end + + private + + def save_function_name_in_function + self.function = self.softkey_function.name + end + + # Make sure that no number is set when there is no need for one. + # And make sure that there is no CallForward connected when not needed. + # + def clean_up_and_leave_only_values_which_make_sense_for_the_current_softkey_function_id + if self.softkey_function_id != nil + if ['blf','speed_dial','dtmf','conference'].include?(self.softkey_function.name) + self.call_forward_id = nil + end + if ['call_forwarding'].include?(self.softkey_function.name) + self.number = nil + end + end + end + +end diff --git a/app/models/softkey_function.rb b/app/models/softkey_function.rb new file mode 100644 index 0000000..976827d --- /dev/null +++ b/app/models/softkey_function.rb @@ -0,0 +1,13 @@ +class SoftkeyFunction < ActiveRecord::Base + validates_presence_of :name + + validates_uniqueness_of :name + + acts_as_list + + default_scope order(:position) + + def to_s + self.name + end +end diff --git a/app/models/system_message.rb b/app/models/system_message.rb new file mode 100644 index 0000000..0d9e862 --- /dev/null +++ b/app/models/system_message.rb @@ -0,0 +1,7 @@ +class SystemMessage < ActiveRecord::Base + attr_accessible :content + + belongs_to :user + + validates_presence_of :content +end diff --git a/app/models/tenant.rb b/app/models/tenant.rb new file mode 100644 index 0000000..6f98603 --- /dev/null +++ b/app/models/tenant.rb @@ -0,0 +1,243 @@ +# encoding: UTF-8 + +class Tenant < ActiveRecord::Base + attr_accessible :name, :description, :sip_domain_id, :country_id, :language_id, :from_field_pin_change_email, :from_field_voicemail_email + + if STRICT_INTERNAL_EXTENSION_HANDLING == true + attr_accessible :internal_extension_ranges + end + + if STRICT_DID_HANDLING == true + attr_accessible :did_list + end + + # Associations: + # + has_many :tenant_memberships, :dependent => :destroy + has_many :users, :through => :tenant_memberships, :validate => true + + has_many :user_groups, :dependent => :destroy + + has_many :phone_books, :as => :phone_bookable, :dependent => :destroy + has_many :phone_book_entries, :through => :phone_books + + has_many :phone_number_ranges, :as => :phone_number_rangeable, :dependent => :destroy + + has_many :phones, :as => :phoneable, :dependent => :destroy + has_many :users_phones, :through => :users, :source => :phones, :readonly => true + + has_many :callthroughs, :dependent => :destroy + + has_many :fax_accounts, :dependent => :destroy # A tenant can't have a FaxAccount by itself! + + belongs_to :country + belongs_to :language + + belongs_to :sip_domain + + has_many :sip_accounts, :as => :sip_accountable, :dependent => :destroy + has_many :users_sip_accounts, :through => :users, :source => :sip_accounts, :readonly => true + + has_many :conferences, :as => :conferenceable, :dependent => :destroy + + has_many :hunt_groups, :dependent => :destroy + has_many :hunt_group_members, :through => :hunt_groups + + has_many :automatic_call_distributors, :as => :automatic_call_distributorable, :dependent => :destroy + has_many :acd_agents, :through => :automatic_call_distributors + + # Phone numbers of the tenant. + # + has_many :phone_number_ranges_phone_numbers, :through => :phone_number_ranges, :source => :phone_numbers, :readonly => true + has_many :phone_numbers, :through => :sip_accounts + has_many :conferences_phone_numbers, :through => :conferences, :source => :phone_numbers, :readonly => true + has_many :callthroughs_phone_numbers, :through => :conferences, :source => :phone_numbers, :readonly => true + has_many :huntgroups_phone_numbers, :through => :conferences, :source => :phone_numbers, :readonly => true + has_many :fax_accounts_phone_numbers, :through => :fax_accounts, :source => :phone_numbers, :readonly => true + + # Phone numbers of users of the tenant. + # + has_many :users_phone_numbers, :through => :users, :source => :phone_numbers, :readonly => true + has_many :user_groups_phone_numbers, :through => :users, :source => :phone_numbers, :readonly => true + has_many :users_conferences, :through => :users, :source => :conferences, :readonly => true + has_many :users_conferences_phone_numbers, :through => :users_conferences, :source => :phone_numbers, :readonly => true + has_many :users_fax_accounts, :through => :users, :source => :fax_accounts, :readonly => true + has_many :users_fax_accounts_phone_numbers, :through => :users_fax_accounts, :source => :phone_numbers, :readonly => true + + # Validations: + # + validates_presence_of :name, :state, :country, :language + validates_length_of :name, :within => 1..255 + validates_uniqueness_of :name + + validates_length_of :name, :within => 1..100 + + # Before and after hooks: + # + after_create :create_a_default_phone_book + + # State machine: + default_scope where(:state => 'active') + state_machine :initial => :active do + + event :deactivate do + transition [:active] => :deactivated + end + + event :activate do + transition [:deactivated] => :active + end + end + + def to_s + name + end + + if STRICT_INTERNAL_EXTENSION_HANDLING == true + def array_of_internal_extension_numbers + ranges = self.internal_extension_ranges.gsub(/[^0-9\-,]/,'').gsub(/[\-]+/,'-').gsub(/[,]+/,',').split(/,/) + output = [] + ranges.each do |range| + mini_range = range.split(/-/).map{|x| x.to_i}.sort + if mini_range.size == 1 + output << mini_range[0] + else + output = output + (mini_range[0]..mini_range[1]).to_a + end + output = output.try(:flatten) + end + output.try(:sort).try(:uniq).map{|number| number.to_s } + end + + # Generate the internal_extensions + # + def generate_internal_extensions + internal_extensions = self.phone_number_ranges.find_or_create_by_name(INTERNAL_EXTENSIONS, :description => 'A list of all available internal extensions.') + + phone_number_list = Array.new + + if self.array_of_internal_extension_numbers.size > 0 + if self.country.phone_number_ranges.first.try(:phone_numbers) == nil + phone_number_list = self.array_of_internal_extension_numbers + elsif + # Don't create extensions like 911, 110 or 112 (at least by default) + # + phone_number_list = (self.array_of_internal_extension_numbers - self.country.phone_number_ranges.where(:name => SERVICE_NUMBERS).first.phone_numbers.map{|entry| entry.number}) + end + end + + phone_number_list.each do |number| + internal_extensions.phone_numbers.find_or_create_by_name_and_number('Extension', number) + end + end + + end + + if STRICT_DID_HANDLING == true + def array_of_dids_generated_from_did_list + numbers = self.did_list.downcase.gsub(/[^0-9,x\+]/,'').gsub(/[,]+/,',').split(/,/) + array_of_all_external_numbers = [] + numbers.each do |number| + if number.include?('x') + self.array_of_internal_extension_numbers.each do |internal_extension| + array_of_all_external_numbers << number.gsub(/x/, "-#{internal_extension.to_s}") + end + else + array_of_all_external_numbers << number + end + end + array_of_all_external_numbers.try(:sort).try(:uniq).map{|number| number.to_s } + end + + # Generate the external numbers (DIDs) + # + def generate_dids + dids = self.phone_number_ranges.find_or_create_by_name(DIRECT_INWARD_DIALING_NUMBERS, :description => 'A list of all available DIDs.') + self.array_of_dids_generated_from_did_list.each do |number| + dids.phone_numbers.find_or_create_by_name_and_number('DID', number) + end + end + + end + + + # All phone_numbers which can be used + # + def internal_extensions_and_dids + @internal_extensions_and_dids ||= self.phone_number_ranges_phone_numbers. + where(:phone_numberable_type => 'PhoneNumberRange'). + where(:phone_numberable_id => self.phone_number_ranges. + where(:name => [INTERNAL_EXTENSIONS, DIRECT_INWARD_DIALING_NUMBERS]). + map{|pnr| pnr.id }) + end + + def array_of_internal_extensions + @array_of_internal_extensions ||= self.phone_number_ranges_phone_numbers. + where(:phone_numberable_type => 'PhoneNumberRange'). + where(:phone_numberable_id => self.phone_number_ranges. + where(:name => INTERNAL_EXTENSIONS). + map{|pnr| pnr.id }). + map{|phone_number| phone_number.number }. + sort.uniq + end + + def array_of_dids + @array_of_dids ||= self.phone_number_ranges_phone_numbers. + where(:phone_numberable_type => 'PhoneNumberRange'). + where(:phone_numberable_id => self.phone_number_ranges.where(:name => DIRECT_INWARD_DIALING_NUMBERS).map{|pnr| pnr.id }). + map{|phone_number| phone_number.to_s }. + sort.uniq + end + + def array_of_assigned_phone_numbers + (self.phone_numbers + self.conferences_phone_numbers + + self.callthroughs_phone_numbers + self.huntgroups_phone_numbers + + self.fax_accounts_phone_numbers + self.users_phone_numbers + + self.user_groups_phone_numbers + self.users_conferences_phone_numbers + + self.users_fax_accounts_phone_numbers). + map{|phone_number| phone_number.number }. + sort.uniq + end + + def array_of_available_internal_extensions + (self.array_of_internal_extensions - self.array_of_assigned_phone_numbers).sort.uniq + end + + def array_of_available_dids + (self.array_of_dids - self.array_of_assigned_phone_numbers).sort.uniq + end + + def array_of_available_internal_extensions_and_dids + self.array_of_available_internal_extensions + self.array_of_available_dids + end + + private + + # Create a public phone book for this tenant + def create_a_default_phone_book + if self.name != SUPER_TENANT_NAME + general_phone_book = self.phone_books.find_or_create_by_name_and_description( + I18n.t('phone_books.general_phone_book.name'), + I18n.t('phone_books.general_phone_book.description', :resource => self.to_s) + ) + amooma = general_phone_book.phone_book_entries.create( + :organization => 'AMOOMA GmbH', + :is_organization => true, + :description => "Hersteller von Gemeinschaft. Rufen Sie uns an, falls Sie kommerziellen Support oder Consulting für Gemeinschaft benötigen.", + :homepage_organization => 'http://www.amooma.de', + :twitter_account => 'amooma_de', + :facebook_account => 'https://www.facebook.com/AMOOMA.GmbH', + ) + amooma.phone_numbers.create( + :name => 'Office', + :number => '+492622706480' + ) + amooma.addresses.create( + :street => 'Bachstr. 124', + :zip_code => '56566', + :city => 'Neuwied', + :country_id => Country.where(:country_code => 49).first.try(:id), + ) + end + end +end diff --git a/app/models/tenant_membership.rb b/app/models/tenant_membership.rb new file mode 100644 index 0000000..122f702 --- /dev/null +++ b/app/models/tenant_membership.rb @@ -0,0 +1,25 @@ +class TenantMembership < ActiveRecord::Base + belongs_to :tenant + belongs_to :user + + validates_presence_of :tenant + validates_presence_of :user + + after_create :set_current_tenant_if_necessary + + # State Machine stuff + default_scope where(:state => 'active') + state_machine :initial => :active do + end + + private + # The first TenantMembership becomes the current_tenant by default. + # + def set_current_tenant_if_necessary + if !self.user.current_tenant + self.user.current_tenant = self.tenant + self.user.save + end + end + +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..2d0256f --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,208 @@ +require 'digest/sha2' + +class User < ActiveRecord::Base + after_create :create_a_default_phone_book, :if => :'is_native != false' + + # Sync other nodes when this is a cluster. + # + after_create :create_on_other_gs_nodes + after_destroy :destroy_on_other_gs_nodes + after_update :update_on_other_gs_nodes + + attr_accessible :user_name, :email, :password, :password_confirmation, + :first_name, :middle_name, :last_name, :male, + :image, :current_tenant_id, :language_id, + :new_pin, :new_pin_confirmation, :send_voicemail_as_email_attachment, + :importer_checksum, :gs_node_id + + attr_accessor :new_pin, :new_pin_confirmation + + before_validation { + # If the PIN and PIN confirmation are left blank in the GUI + # then the user/admin does not want to change the PIN. + if self.new_pin.blank? && self.new_pin_confirmation.blank? + self.new_pin = nil + self.new_pin_confirmation = nil + end + } + + validates_length_of [:new_pin, :new_pin_confirmation], + :minimum => MINIMUM_PIN_LENGTH, :maximum => MAXIMUM_PIN_LENGTH, + :allow_blank => true, :allow_nil => true + validates_format_of [:new_pin, :new_pin_confirmation], + :with => /^[0-9]+$/, + :allow_blank => true, :allow_nil => true, + :message => "must be numeric." + + validates_confirmation_of :new_pin, :if => :'pin_changed?' + before_save :hash_new_pin, :if => :'pin_changed?' + + has_secure_password + + validates_presence_of :password, :password_confirmation, :on => :create, :if => :'password_digest.blank?' + validates_presence_of :email + validates_presence_of :last_name + validates_presence_of :first_name + validates_presence_of :user_name + + validates_uniqueness_of :user_name, :case_sensitive => false + validates_uniqueness_of :email, :allow_nil => true, :case_sensitive => false + + validates_length_of :user_name, :within => 0..50 + + validates_presence_of :uuid + validates_uniqueness_of :uuid + + # Associations: + # + has_many :tenant_memberships, :dependent => :destroy + has_many :tenants, :through => :tenant_memberships + + has_many :user_group_memberships, :dependent => :destroy, :uniq => true + has_many :user_groups, :through => :user_group_memberships + + has_many :phone_books, :as => :phone_bookable, :dependent => :destroy + has_many :phone_book_entries, :through => :phone_books + + has_many :phones, :as => :phoneable + has_many :sip_accounts, :as => :sip_accountable, :dependent => :destroy + has_many :phone_numbers, :through => :sip_accounts + + has_many :conferences, :as => :conferenceable, :dependent => :destroy + + has_many :fax_accounts, :as => :fax_accountable, :dependent => :destroy + + has_many :system_messages, :dependent => :destroy + + has_many :auto_destroy_access_authorization_phone_numbers, :class_name => 'PhoneNumber', :foreign_key => 'access_authorization_user_id', :dependent => :destroy + + belongs_to :current_tenant, :class_name => 'Tenant' + validates_presence_of :current_tenant, :if => Proc.new{ |user| user.current_tenant_id } + + belongs_to :language + validates_presence_of :language_id + validates_presence_of :language + + validate :current_tenant_is_included_in_tenants, :if => Proc.new{ |user| user.current_tenant_id } + + belongs_to :gs_node + + # Avatar like photo + mount_uploader :image, ImageUploader + + before_save :format_email_and_user_name + + before_destroy :destroy_or_logout_phones + + def destroy + clean_whitelist_entries + super + end + + def pin_changed? + ! @new_pin.blank? + end + + def sip_domain + if self.current_tenant + return self.current_tenant.sip_domain + end + return nil + end + + def to_s + max_first_name_length = 10 + max_last_name_length = 20 + if self.first_name.blank? + self.last_name.strip + else + "#{self.first_name.strip} #{self.last_name.strip}" + end + end + + def self.find_user_by_phone_number( number, tenant ) + tenant = Tenant.where( :id => tenant.id ).first + if tenant + if tenant.sip_domain + user = tenant.sip_domain.sip_accounts. + joins(:phone_numbers). + where(:phone_numbers => { :number => number }). + first. + try(:sip_accountable) + if user.class.name == 'User' + return user + end + end + end + return nil + end + + def authenticate_by_pin?( entered_pin ) + self.pin_hash == Digest::SHA2.hexdigest( "#{self.pin_salt}#{entered_pin}" ) + end + + + private + + def hash_new_pin + if @new_pin \ + && @new_pin_confirmation \ + && @new_pin_confirmation == @new_pin + self.pin_salt = SecureRandom.base64(8) + self.pin_hash = Digest::SHA2.hexdigest(self.pin_salt + @new_pin) + end + end + + def format_email_and_user_name + self.email = self.email.downcase.strip if !self.email.blank? + self.user_name = self.user_name.downcase.strip if !self.user_name.blank? + end + + # Create a personal phone book for this user: + def create_a_default_phone_book + private_phone_book = self.phone_books.find_or_create_by_name_and_description( + I18n.t('phone_books.private_phone_book.name', :resource => self.to_s), + I18n.t('phone_books.private_phone_book.description') + ) + end + + # Check if a current_tenant_id is possible tenant_membership wise. + def current_tenant_is_included_in_tenants + if !self.tenants.include?(Tenant.find(self.current_tenant_id)) + errors.add(:current_tenant_id, "is not possible (no TenantMembership)") + end + end + + # Make sure that there are no whitelist entries with phone_numbers of + # a just destroyed user. + # + def clean_whitelist_entries + phone_numbers = PhoneNumber.where( :phone_numberable_type => 'Whitelist'). + where( :number => self.phone_numbers.map{ |x| x.number } ) + phone_numbers.each do |phone_number| + if phone_number.phone_numberable.whitelistable.class == Callthrough + whitelist = Whitelist.find(phone_number.phone_numberable) + phone_number.destroy + if whitelist.phone_numbers.count == 0 + # Very lickly that this Whitelist doesn't make sense any more. + # + whitelist.destroy + end + end + end + end + + # Make sure that a tenant phone goes back to the tenant and doesn't + # get deleted with this user. + # + def destroy_or_logout_phones + self.phones.each do |phone| + if phone.sip_accounts.where(:sip_accountable_type => 'Tenant').count > 0 + phone.user_logout + else + phone.destroy + end + end + end + +end diff --git a/app/models/user_group.rb b/app/models/user_group.rb new file mode 100644 index 0000000..44f2fd8 --- /dev/null +++ b/app/models/user_group.rb @@ -0,0 +1,33 @@ +class UserGroup < ActiveRecord::Base + attr_accessible :name, :description + + belongs_to :tenant + + validates_presence_of :name + validates_uniqueness_of :name, :scope => :tenant_id + + validates_presence_of :tenant + + validates_length_of :name, :within => 1..50 + + has_many :user_group_memberships, :dependent => :destroy, :uniq => true + has_many :users, :through => :user_group_memberships + + has_many :gui_function_memberships, :dependent => :destroy + has_many :gui_functions, :through => :gui_function_memberships + + has_many :phone_books, :as => :phone_bookable, :dependent => :destroy + has_many :phone_book_entries, :through => :phone_books + + has_many :sip_accounts, :as => :sip_accountable, :dependent => :destroy + + has_many :conferences, :as => :conferenceable, :dependent => :destroy + + has_many :fax_accounts, :as => :fax_accountable, :dependent => :destroy + + acts_as_list :scope => :tenant_id + + def to_s + name + end +end diff --git a/app/models/user_group_membership.rb b/app/models/user_group_membership.rb new file mode 100644 index 0000000..18a8d48 --- /dev/null +++ b/app/models/user_group_membership.rb @@ -0,0 +1,26 @@ +class UserGroupMembership < ActiveRecord::Base + belongs_to :user + belongs_to :user_group + + validates_uniqueness_of :user_id, :scope => :user_group_id + validates_presence_of :user + validates_presence_of :user_group + + validate :user_belongs_to_the_tenant_of_the_user_group + + # State Machine stuff + default_scope where(:state => 'active') + state_machine :initial => :active do + end + + def to_s + "#{self.user} / #{self.user_group}" + end + + private + def user_belongs_to_the_tenant_of_the_user_group + if !self.user_group.tenant.users.include?(self.user) + errors.add(:user_id, "not a member of the tenant which this group belongs to") + end + end +end diff --git a/app/models/voicemail_message.rb b/app/models/voicemail_message.rb new file mode 100644 index 0000000..91ba457 --- /dev/null +++ b/app/models/voicemail_message.rb @@ -0,0 +1,52 @@ +class VoicemailMessage < ActiveRecord::Base + self.table_name = 'voicemail_msgs' + self.primary_key = 'uuid' + +# belongs_to :sip_account, :foreign_key => 'username', :primary_key => 'auth_name', :readonly => true + # Prevent objects from being destroyed + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end + + # Prevent objects from being deleted + def self.delete_all + raise ActiveRecord::ReadOnlyRecord + end + + # Delete Message on FreeSWITCH over EventAPI + def delete + require 'freeswitch_event' + result = FreeswitchAPI.execute('vm_delete', "#{self.username}@#{self.domain} #{self.uuid}"); + end + + # Alias for delete + def destroy + self.delete + end + + # Mark Message read + def mark_read(mark_read_or_unread = true) + read_status = mark_read_or_unread ? 'read' : 'unread' + require 'freeswitch_event' + result = FreeswitchAPI.execute('vm_read', "#{self.username}@#{self.domain} #{read_status} #{self.uuid}"); + end + + def format_date(epoch, date_format = '%m/%d/%Y %H:%M', date_today_format = '%H:%M') + if epoch && epoch > 0 + time = Time.at(epoch) + if time.strftime('%Y%m%d') == Time.now.strftime('%Y%m%d') + return time.in_time_zone.strftime(date_today_format) + end + return time.in_time_zone.strftime(date_format) + end + end + + def display_duration + if self.message_len.to_i > 0 + minutes = (self.message_len / 1.minutes).to_i + seconds = self.message_len - minutes.minutes.seconds + return '%i:%02i' % [minutes, seconds] + end + end + +end diff --git a/app/models/voicemail_setting.rb b/app/models/voicemail_setting.rb new file mode 100644 index 0000000..a8bb304 --- /dev/null +++ b/app/models/voicemail_setting.rb @@ -0,0 +1,12 @@ +class VoicemailSetting < ActiveRecord::Base + self.table_name = 'voicemail_prefs' + self.primary_key = 'username' + + attr_accessible :username, :domain, :name_path, :greeting_path, :password, :notify, :attachment, :mark_read, :purge, :sip_account + + has_one :sip_account, :foreign_key => 'auth_name' + + validates_presence_of :username + validates_presence_of :domain + validates :username, :uniqueness => {:scope => :domain} +end diff --git a/app/models/whitelist.rb b/app/models/whitelist.rb new file mode 100644 index 0000000..8303728 --- /dev/null +++ b/app/models/whitelist.rb @@ -0,0 +1,23 @@ +class Whitelist < ActiveRecord::Base + attr_accessible :name, :phone_numbers_attributes, :uuid + + belongs_to :whitelistable, :polymorphic => true + + # These are the phone_numbers for this whitelist. + # + has_many :phone_numbers, :as => :phone_numberable, :dependent => :destroy + + accepts_nested_attributes_for :phone_numbers, + :reject_if => lambda { |phone_number| phone_number[:number].blank? }, + :allow_destroy => true + + acts_as_list :scope => [ :whitelistable_type, :whitelistable_id ] + + validates_presence_of :uuid + validates_uniqueness_of :uuid + + def to_s + self.name || I18n.t('whitelists.name') + ' ID ' + self.id.to_s + end + +end diff --git a/app/uploaders/audio_uploader.rb b/app/uploaders/audio_uploader.rb new file mode 100644 index 0000000..2ddbeac --- /dev/null +++ b/app/uploaders/audio_uploader.rb @@ -0,0 +1,49 @@ +# encoding: utf-8 + +class AudioUploader < CarrierWave::Uploader::Base + + # Include RMagick or ImageScience support: + # include CarrierWave::RMagick + # include CarrierWave::MiniMagick + # include CarrierWave::ImageScience + + # Choose what kind of storage to use for this uploader: + storage :file + # 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 + # "/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/document_uploader.rb b/app/uploaders/document_uploader.rb new file mode 100644 index 0000000..05d70ca --- /dev/null +++ b/app/uploaders/document_uploader.rb @@ -0,0 +1,49 @@ +# encoding: utf-8 + +class DocumentUploader < CarrierWave::Uploader::Base + + # Include RMagick or ImageScience support: + # include CarrierWave::RMagick + include CarrierWave::MiniMagick + # include CarrierWave::ImageScience + + # Choose what kind of storage to use for this uploader: + storage :file + # 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 + # "/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(pdf) + 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/image_uploader.rb b/app/uploaders/image_uploader.rb new file mode 100644 index 0000000..114ccc6 --- /dev/null +++ b/app/uploaders/image_uploader.rb @@ -0,0 +1,63 @@ +# encoding: utf-8 + +class ImageUploader < CarrierWave::Uploader::Base + + # Include RMagick or ImageScience support: + # include CarrierWave::RMagick + include CarrierWave::MiniMagick + # include CarrierWave::ImageScience + + # Choose what kind of storage to use for this uploader: + storage :file + # 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 + "/images/fallback/" + [version_name, "default.jpg"].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 :mini do + process :resize_to_fill => [32, 32] + end + + version :small do + process :resize_to_fill => [72, 72] + end + + version :profile do + process :resize_to_fill => [200, 200] + end + + version :snom_caller_picture do + process :resize_to_fill => [100, 135] + 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 + + # TODO Secure filename + +end diff --git a/app/uploaders/thumbnail_uploader.rb b/app/uploaders/thumbnail_uploader.rb new file mode 100644 index 0000000..a401a91 --- /dev/null +++ b/app/uploaders/thumbnail_uploader.rb @@ -0,0 +1,62 @@ +# encoding: utf-8 + +class ThumbnailUploader < CarrierWave::Uploader::Base + + # Include RMagick or ImageScience support: + # include CarrierWave::RMagick + include CarrierWave::MiniMagick + # include CarrierWave::ImageScience + + # Choose what kind of storage to use for this uploader: + storage :file + # 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 + # "/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 :mini do + process :resize_to_limit => [75, 75] + end + + version :thumb do + process :resize_to_limit => [150, 150] + end + + version :medium do + process :resize_to_limit => [400, 400] + end + + version :big do + process :resize_to_limit => [800, 800] + 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(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/tiff_uploader.rb b/app/uploaders/tiff_uploader.rb new file mode 100644 index 0000000..d482aed --- /dev/null +++ b/app/uploaders/tiff_uploader.rb @@ -0,0 +1,49 @@ +# encoding: utf-8 + +class TiffUploader < CarrierWave::Uploader::Base + + # Include RMagick or ImageScience support: + # include CarrierWave::RMagick + # include CarrierWave::MiniMagick + # include CarrierWave::ImageScience + + # Choose what kind of storage to use for this uploader: + storage :file + # 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 + # "/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/views/access_authorizations/_form.html.haml b/app/views/access_authorizations/_form.html.haml new file mode 100644 index 0000000..fa417d9 --- /dev/null +++ b/app/views/access_authorizations/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@parent, @access_authorization]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('access_authorizations.form.submit') \ No newline at end of file diff --git a/app/views/access_authorizations/_form_core.html.haml b/app/views/access_authorizations/_form_core.html.haml new file mode 100644 index 0000000..10530d7 --- /dev/null +++ b/app/views/access_authorizations/_form_core.html.haml @@ -0,0 +1,11 @@ +.inputs + = f.input :name, :label => t('access_authorizations.form.name.label'), :hint => conditional_hint('access_authorizations.form.name.hint') + = f.input :login, :label => t('access_authorizations.form.login.label'), :hint => conditional_hint('access_authorizations.form.login.hint') + = f.input :pin, :label => t('access_authorizations.form.pin.label'), :hint => conditional_hint('access_authorizations.form.pin.hint') + - if SipAccount.count < 50 + = f.association :sip_account, :label => t('callthroughs.form.sip_account.label'), :hint => conditional_hint('callthroughs.form.sip_account.hint') + - else + = f.input :sip_account_id, :label => t('callthroughs.form.sip_account_id.label'), :hint => conditional_hint('callthroughs.form.sip_account_id.hint') + + = f.simple_fields_for :phone_numbers do |phone_number| + = render "phone_numbers/form_core", :f => phone_number \ No newline at end of file diff --git a/app/views/access_authorizations/_index_core.html.haml b/app/views/access_authorizations/_index_core.html.haml new file mode 100644 index 0000000..083b16b --- /dev/null +++ b/app/views/access_authorizations/_index_core.html.haml @@ -0,0 +1,21 @@ +%table + %tr + %th= t('access_authorizations.index.name') + %th= t('access_authorizations.index.login') + %th= t('access_authorizations.index.pin') + %th= t('callthroughs.index.phone_numbers') + + - reset_cycle + - for access_authorization in access_authorizations + - show_path_method = method( :"#{access_authorization.access_authorizationable.class.name.underscore}_access_authorization_path" ) + - edit_path_method = method( :"edit_#{access_authorization.access_authorizationable.class.name.underscore}_access_authorization_path" ) + %tr{:class => cycle('odd', 'even')} + %td= access_authorization.name + %td= access_authorization.login + %td= access_authorization.pin + %td + =render 'phone_numbers/listing', :phone_numbers => access_authorization.phone_numbers + - if access_authorization.phone_numbers.count > 0 + %br + = link_to t('phone_numbers.index.actions.create'), new_access_authorization_phone_number_path(access_authorization) + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => access_authorization.access_authorizationable, :child => access_authorization} \ No newline at end of file diff --git a/app/views/access_authorizations/edit.html.haml b/app/views/access_authorizations/edit.html.haml new file mode 100644 index 0000000..414f094 --- /dev/null +++ b/app/views/access_authorizations/edit.html.haml @@ -0,0 +1,3 @@ +- title t("access_authorizations.edit.page_title") + += render "form" diff --git a/app/views/access_authorizations/index.html.haml b/app/views/access_authorizations/index.html.haml new file mode 100644 index 0000000..05b27db --- /dev/null +++ b/app/views/access_authorizations/index.html.haml @@ -0,0 +1,6 @@ +- title t("access_authorizations.index.page_title") + +- if @access_authorizations.count > 0 + = render "index_core", :access_authorizations => @access_authorizations + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => AccessAuthorization} \ No newline at end of file diff --git a/app/views/access_authorizations/new.html.haml b/app/views/access_authorizations/new.html.haml new file mode 100644 index 0000000..0bbf16c --- /dev/null +++ b/app/views/access_authorizations/new.html.haml @@ -0,0 +1,3 @@ +- title t("access_authorizations.new.page_title") + += render "form" diff --git a/app/views/access_authorizations/show.html.haml b/app/views/access_authorizations/show.html.haml new file mode 100644 index 0000000..17d1d9b --- /dev/null +++ b/app/views/access_authorizations/show.html.haml @@ -0,0 +1,22 @@ +- title t("access_authorizations.show.page_title") + +%p + %strong= t('access_authorizations.show.name') + ":" + = @access_authorization.name +%p + %strong= t('access_authorizations.show.login') + ":" + = @access_authorization.login +%p + %strong= t('access_authorizations.show.pin') + ":" + = @access_authorization.pin +%p + %strong= t('access_authorizations.show.sip_account_id') + ":" + = @access_authorization.sip_account || t('access_authorizations.none') + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @parent, :child => @access_authorization } + +%h2= t('callthroughs.form.phone_numbers.label') +- if @access_authorization.phone_numbers.count > 0 + = render 'phone_numbers/index_core', :phone_numbers => @access_authorization.phone_numbers + %br += render :partial => 'shared/create_link', :locals => {:parent => @access_authorization, :child_class => PhoneNumber} \ No newline at end of file diff --git a/app/views/acd_agents/_form.html.haml b/app/views/acd_agents/_form.html.haml new file mode 100644 index 0000000..3b78bac --- /dev/null +++ b/app/views/acd_agents/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@automatic_call_distributor, @acd_agent]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('acd_agents.form.submit') \ No newline at end of file diff --git a/app/views/acd_agents/_form_core.html.haml b/app/views/acd_agents/_form_core.html.haml new file mode 100644 index 0000000..ab4657c --- /dev/null +++ b/app/views/acd_agents/_form_core.html.haml @@ -0,0 +1,7 @@ +.inputs + = f.input :name, :label => t('acd_agents.form.name.label'), :hint => conditional_hint('acd_agents.form.name.hint') + = f.input :status, :label => t('acd_agents.form.status.label'), :hint => conditional_hint('acd_agents.form.status.hint'), :include_blank => false, :collection => AcdAgent::STATUSES + = f.input :last_call, :label => t('acd_agents.form.last_call.label'), :hint => conditional_hint('acd_agents.form.last_call.hint') + = f.input :calls_answered, :label => t('acd_agents.form.calls_answered.label'), :hint => conditional_hint('acd_agents.form.calls_answered.hint') + = f.input :destination_type, :label => t('acd_agents.form.destination_type.label'), :hint => conditional_hint('acd_agents.form.destination_type.hint'), :include_blank => false, :collection => AcdAgent::DESTINATION_TYPES + = f.input :destination_id, :label => t('acd_agents.form.destination_id.label'), :hint => conditional_hint('acd_agents.form.destination_id.hint') diff --git a/app/views/acd_agents/_index_core.html.haml b/app/views/acd_agents/_index_core.html.haml new file mode 100644 index 0000000..7cb1aae --- /dev/null +++ b/app/views/acd_agents/_index_core.html.haml @@ -0,0 +1,18 @@ +%table + %tr + %th= t('acd_agents.index.name') + %th= t('acd_agents.index.status') + %th= t('acd_agents.index.last_call') + %th= t('acd_agents.index.calls_answered') + %th= t('acd_agents.index.destination') + + - reset_cycle + - for acd_agent in acd_agents + %tr{:class => cycle('odd', 'even')} + %td= acd_agent.name + %td= acd_agent.status + %td= acd_agent.last_call + %td= acd_agent.calls_answered + %td= acd_agent.destination + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => acd_agent.automatic_call_distributor, :child => acd_agent} + \ No newline at end of file diff --git a/app/views/acd_agents/_listing.html.haml b/app/views/acd_agents/_listing.html.haml new file mode 100644 index 0000000..0495ec2 --- /dev/null +++ b/app/views/acd_agents/_listing.html.haml @@ -0,0 +1,8 @@ +- amount_of_acd_agents = acd_agents.count +- if amount_of_acd_agents > 0 + - if amount_of_acd_agents < 30 + = acd_agents.map{|acd_agent| acd_agent}.join(', ') + - else + = acd_agents.limit(15).map{|acd_agent| acd_agent}.join(', ') + ', ' + = '[...]' + = acd_agents.offset(amount_of_acd_agents - 15).map{|acd_agent| acd_agent}.join(', ') \ No newline at end of file diff --git a/app/views/acd_agents/edit.html.haml b/app/views/acd_agents/edit.html.haml new file mode 100644 index 0000000..8ab14b8 --- /dev/null +++ b/app/views/acd_agents/edit.html.haml @@ -0,0 +1,3 @@ +- title t("acd_agents.edit.page_title") + += render "form" \ No newline at end of file diff --git a/app/views/acd_agents/index.html.haml b/app/views/acd_agents/index.html.haml new file mode 100644 index 0000000..d586dcf --- /dev/null +++ b/app/views/acd_agents/index.html.haml @@ -0,0 +1,6 @@ +- title t("acd_agents.index.page_title") + +- if @acd_agents && @acd_agents.count > 0 + = render "index_core", :acd_agents => @acd_agents + += render :partial => 'shared/create_link', :locals => {:parent => @automatic_call_distributor, :child_class => AcdAgent} diff --git a/app/views/acd_agents/new.html.haml b/app/views/acd_agents/new.html.haml new file mode 100644 index 0000000..546136b --- /dev/null +++ b/app/views/acd_agents/new.html.haml @@ -0,0 +1,3 @@ +- title t("acd_agents.new.page_title") + += render "form" \ No newline at end of file diff --git a/app/views/acd_agents/show.html.haml b/app/views/acd_agents/show.html.haml new file mode 100644 index 0000000..97881f6 --- /dev/null +++ b/app/views/acd_agents/show.html.haml @@ -0,0 +1,28 @@ +- title t("acd_agents.show.page_title") + +%p + %strong= t('acd_agents.show.uuid') + ":" + = @acd_agent.uuid +%p + %strong= t('acd_agents.show.name') + ":" + = @acd_agent.name +%p + %strong= t('acd_agents.show.status') + ":" + = @acd_agent.status +%p + %strong= t('acd_agents.show.automatic_call_distributor_id') + ":" + = @acd_agent.automatic_call_distributor_id +%p + %strong= t('acd_agents.show.last_call') + ":" + = @acd_agent.last_call +%p + %strong= t('acd_agents.show.calls_answered') + ":" + = @acd_agent.calls_answered +%p + %strong= t('acd_agents.show.destination_type') + ":" + = @acd_agent.destination_type +%p + %strong= t('acd_agents.show.destination_id') + ":" + = @acd_agent.destination_id + += render :partial => 'shared/show_edit_destroy_part', :locals => {:parent => @automatic_call_distributor, :child => @acd_agent } \ No newline at end of file diff --git a/app/views/acd_callers/_index_core.html.haml b/app/views/acd_callers/_index_core.html.haml new file mode 100644 index 0000000..958b3ff --- /dev/null +++ b/app/views/acd_callers/_index_core.html.haml @@ -0,0 +1,21 @@ +%table + %tr + %th= t('acd_callers.index.channel_uuid') + %th= t('acd_callers.index.automatic_call_distributor_id') + %th= t('acd_callers.index.status') + %th= t('acd_callers.index.enter_time') + %th= t('acd_callers.index.agent_answer_time') + %th= t('acd_callers.index.callback_number') + %th= t('acd_callers.index.callback_attempts') + + - reset_cycle + - for acd_caller in acd_callers + %tr{:class => cycle('odd', 'even')} + %td= acd_caller.channel_uuid + %td= acd_caller.automatic_call_distributor_id + %td= acd_caller.status + %td= acd_caller.enter_time + %td= acd_caller.agent_answer_time + %td= acd_caller.callback_number + %td= acd_caller.callback_attempts + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => acd_caller} \ No newline at end of file diff --git a/app/views/acd_callers/index.html.haml b/app/views/acd_callers/index.html.haml new file mode 100644 index 0000000..70439ed --- /dev/null +++ b/app/views/acd_callers/index.html.haml @@ -0,0 +1,6 @@ +- title t("acd_callers.index.page_title") + +- if @acd_callers && @acd_callers.count > 0 + = render "index_core", :acd_callers => @acd_callers + += render :partial => 'shared/create_link', :locals => {:child_class => AcdCaller} \ No newline at end of file diff --git a/app/views/acd_callers/show.html.haml b/app/views/acd_callers/show.html.haml new file mode 100644 index 0000000..0ce8345 --- /dev/null +++ b/app/views/acd_callers/show.html.haml @@ -0,0 +1,25 @@ +- title t("acd_callers.show.page_title") + +%p + %strong= t('acd_callers.show.channel_uuid') + ":" + = @acd_caller.channel_uuid +%p + %strong= t('acd_callers.show.automatic_call_distributor_id') + ":" + = @acd_caller.automatic_call_distributor_id +%p + %strong= t('acd_callers.show.status') + ":" + = @acd_caller.status +%p + %strong= t('acd_callers.show.enter_time') + ":" + = @acd_caller.enter_time +%p + %strong= t('acd_callers.show.agent_answer_time') + ":" + = @acd_caller.agent_answer_time +%p + %strong= t('acd_callers.show.callback_number') + ":" + = @acd_caller.callback_number +%p + %strong= t('acd_callers.show.callback_attempts') + ":" + = @acd_caller.callback_attempts + += render :partial => 'shared/show_edit_destroy_part', :locals => { :child => @acd_caller } \ No newline at end of file diff --git a/app/views/addresses/_form.html.haml b/app/views/addresses/_form.html.haml new file mode 100644 index 0000000..eff9930 --- /dev/null +++ b/app/views/addresses/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for(@address) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('addresses.form.submit') \ No newline at end of file diff --git a/app/views/addresses/_form_core.html.haml b/app/views/addresses/_form_core.html.haml new file mode 100644 index 0000000..f01ac1c --- /dev/null +++ b/app/views/addresses/_form_core.html.haml @@ -0,0 +1,9 @@ +.inputs + = f.input :phone_book_entry_id, :label => t('addresses.form.phone_book_entry_id.label'), :hint => conditional_hint('addresses.form.phone_book_entry_id.hint') + = f.input :line1, :label => t('addresses.form.line1.label'), :hint => conditional_hint('addresses.form.line1.hint') + = f.input :line2, :label => t('addresses.form.line2.label'), :hint => conditional_hint('addresses.form.line2.hint') + = f.input :street, :label => t('addresses.form.street.label'), :hint => conditional_hint('addresses.form.street.hint') + = f.input :zip_code, :label => t('addresses.form.zip_code.label'), :hint => conditional_hint('addresses.form.zip_code.hint') + = f.input :city, :label => t('addresses.form.city.label'), :hint => conditional_hint('addresses.form.city.hint') + = f.input :country_id, :label => t('addresses.form.country_id.label'), :hint => conditional_hint('addresses.form.country_id.hint') + = f.input :position, :label => t('addresses.form.position.label'), :hint => conditional_hint('addresses.form.position.hint') diff --git a/app/views/addresses/_index_core.html.haml b/app/views/addresses/_index_core.html.haml new file mode 100644 index 0000000..2050ded --- /dev/null +++ b/app/views/addresses/_index_core.html.haml @@ -0,0 +1,23 @@ +%table + %tr + %th= t('addresses.index.phone_book_entry_id') + %th= t('addresses.index.line1') + %th= t('addresses.index.line2') + %th= t('addresses.index.street') + %th= t('addresses.index.zip_code') + %th= t('addresses.index.city') + %th= t('addresses.index.country_id') + %th= t('addresses.index.position') + + - reset_cycle + - for address in addresses + %tr{:class => cycle('odd', 'even')} + %td= address.phone_book_entry_id + %td= address.line1 + %td= address.line2 + %td= address.street + %td= address.zip_code + %td= address.city + %td= address.country_id + %td= address.position + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => address.phone_book_entry, :child => address} \ No newline at end of file diff --git a/app/views/addresses/edit.html.haml b/app/views/addresses/edit.html.haml new file mode 100644 index 0000000..3d85ae6 --- /dev/null +++ b/app/views/addresses/edit.html.haml @@ -0,0 +1,9 @@ +- title t("addresses.edit.page_title") + += render "form" + +%p + - if can? :edit, @address + = link_to t('addresses.edit.actions.edit'), @address + | + = link_to t('addresses.edit.actions.view_all'), addresses_path diff --git a/app/views/addresses/index.html.haml b/app/views/addresses/index.html.haml new file mode 100644 index 0000000..ecebc65 --- /dev/null +++ b/app/views/addresses/index.html.haml @@ -0,0 +1,6 @@ +- title t("addresses.index.page_title") + +- if @addresses.count > 0 + = render "index_core", :addresses => @addresses + += render :partial => 'shared/create_link', :locals => {:child_class => Address} \ No newline at end of file diff --git a/app/views/addresses/new.html.haml b/app/views/addresses/new.html.haml new file mode 100644 index 0000000..280de55 --- /dev/null +++ b/app/views/addresses/new.html.haml @@ -0,0 +1,3 @@ +- title t("addresses.new.page_title") + += render "form" diff --git a/app/views/addresses/show.html.haml b/app/views/addresses/show.html.haml new file mode 100644 index 0000000..211d020 --- /dev/null +++ b/app/views/addresses/show.html.haml @@ -0,0 +1,28 @@ +- title t("addresses.show.page_title") + +%p + %strong= t('addresses.show.phone_book_entry_id') + ":" + = @address.phone_book_entry_id +%p + %strong= t('addresses.show.line1') + ":" + = @address.line1 +%p + %strong= t('addresses.show.line2') + ":" + = @address.line2 +%p + %strong= t('addresses.show.street') + ":" + = @address.street +%p + %strong= t('addresses.show.zip_code') + ":" + = @address.zip_code +%p + %strong= t('addresses.show.city') + ":" + = @address.city +%p + %strong= t('addresses.show.country_id') + ":" + = @address.country_id +%p + %strong= t('addresses.show.position') + ":" + = @address.position + += render :partial => 'shared/show_edit_destroy_part', :locals => { :child => @address } \ No newline at end of file diff --git a/app/views/api/rows/_form.html.erb b/app/views/api/rows/_form.html.erb new file mode 100644 index 0000000..465b105 --- /dev/null +++ b/app/views/api/rows/_form.html.erb @@ -0,0 +1,21 @@ +<%= simple_form_for(@row) do |f| %> + <%= f.error_notification %> + +
    + <%= f.input :user_name %> + <%= f.input :last_name %> + <%= f.input :middle_name %> + <%= f.input :first_name %> + <%= f.input :office_phone_number %> + <%= f.input :internal_extension %> + <%= f.input :mobile_phone_number %> + <%= f.input :fax_phone_number %> + <%= f.input :email %> + <%= f.input :pin %> + <%= f.input :photo_file_name %> +
    + +
    + <%= f.button :submit %> +
    +<% end %> diff --git a/app/views/api/rows/edit.html.erb b/app/views/api/rows/edit.html.erb new file mode 100644 index 0000000..bce5694 --- /dev/null +++ b/app/views/api/rows/edit.html.erb @@ -0,0 +1,3 @@ +

    Editing row

    + +<%= render 'form' %> \ No newline at end of file diff --git a/app/views/api/rows/index.html.erb b/app/views/api/rows/index.html.erb new file mode 100644 index 0000000..d65e059 --- /dev/null +++ b/app/views/api/rows/index.html.erb @@ -0,0 +1,29 @@ +

    Listing rows

    + + + + + + + + + + + + + + +<% @rows.each do |row| %> + '> + + + + + + + + + + +<% end %> +
    IDUser nameLast nameFirst nameInternal extensionOffice phone numberFax phone numberEmailPin
    <%= row.id %><%= link_to row.user_name, tenant_user_path(row.user.current_tenant, row.user) %><%= row.last_name %><%= row.first_name %><%= row.internal_extension %><%= row.office_phone_number %><%= row.fax_phone_number %><%= row.email %><%= row.pin %>
    \ No newline at end of file diff --git a/app/views/api/rows/new.html.erb b/app/views/api/rows/new.html.erb new file mode 100644 index 0000000..fccd964 --- /dev/null +++ b/app/views/api/rows/new.html.erb @@ -0,0 +1,3 @@ +

    New row

    + +<%= render 'form' %> \ No newline at end of file diff --git a/app/views/api/rows/show.html.erb b/app/views/api/rows/show.html.erb new file mode 100644 index 0000000..aad60b5 --- /dev/null +++ b/app/views/api/rows/show.html.erb @@ -0,0 +1,56 @@ +

    <%= notice %>

    + +

    + User name: + <%= @row.user_name %> +

    + +

    + Last name: + <%= @row.last_name %> +

    + +

    + Middle name: + <%= @row.middle_name %> +

    + +

    + First name: + <%= @row.first_name %> +

    + +

    + Office phone number: + <%= @row.office_phone_number %> +

    + +

    + Internal extension: + <%= @row.internal_extension %> +

    + +

    + Mobile phone number: + <%= @row.mobile_phone_number %> +

    + +

    + Fax phone number: + <%= @row.fax_phone_number %> +

    + +

    + Email: + <%= @row.email %> +

    + +

    + Pin: + <%= @row.pin %> +

    + +

    + Photo file name: + <%= @row.photo_file_name %> +

    \ No newline at end of file diff --git a/app/views/automatic_call_distributors/_form.html.haml b/app/views/automatic_call_distributors/_form.html.haml new file mode 100644 index 0000000..fcf133c --- /dev/null +++ b/app/views/automatic_call_distributors/_form.html.haml @@ -0,0 +1,8 @@ += simple_form_for([@parent, @automatic_call_distributor]) do |f| + = f.error_notification + + = render "form_core", :f => f, :join_on => @join_on, :leave_on => @leave_on, :strategies => @strategies + + + .actions + = f.button :submit, conditional_t('automatic_call_distributors.form.submit') \ No newline at end of file diff --git a/app/views/automatic_call_distributors/_form_core.html.haml b/app/views/automatic_call_distributors/_form_core.html.haml new file mode 100644 index 0000000..77a38a6 --- /dev/null +++ b/app/views/automatic_call_distributors/_form_core.html.haml @@ -0,0 +1,16 @@ +.inputs + = f.input :name, :label => t('automatic_call_distributors.form.name.label'), :hint => conditional_hint('automatic_call_distributors.form.name.hint') + = f.input :strategy, :label => t('automatic_call_distributors.form.strategy.label'), :hint => conditional_hint('automatic_call_distributors.form.strategy.hint'), :include_blank => false, :as => :select, :collection => strategies + = f.input :max_callers, :label => t('automatic_call_distributors.form.max_callers.label'), :hint => conditional_hint('automatic_call_distributors.form.max_callers.hint') + = f.input :agent_timeout, :label => t('automatic_call_distributors.form.agent_timeout.label'), :hint => conditional_hint('automatic_call_distributors.form.agent_timeout.hint') + = f.input :retry_timeout, :label => t('automatic_call_distributors.form.retry_timeout.label'), :hint => conditional_hint('automatic_call_distributors.form.retry_timeout.hint') + = f.input :join, :label => t('automatic_call_distributors.form.join.label'), :hint => conditional_hint('automatic_call_distributors.form.join.hint'), :include_blank => false, :as => :select, :collection => join_on + = f.input :leave, :label => t('automatic_call_distributors.form.leave.label'), :hint => conditional_hint('automatic_call_distributors.form.leave.hint'), :include_blank => false, :as => :select, :collection => leave_on + + = f.input :announce_position, :label => t('automatic_call_distributors.form.announce_position.label'), :hint => conditional_hint('automatic_call_distributors.announce_position.hint'), :collection => [[t('automatic_call_distributors.announce_position.on_change_only'), 0],[t('automatic_call_distributors.announce_position.never'), nil],[t('automatic_call_distributors.announce_position.every_x_seconds', :x_seconds => 30), 30],[t('automatic_call_distributors.announce_position.every_x_seconds', :x_seconds => 60), 60], [t('automatic_call_distributors.announce_position.every_x_seconds', :x_seconds => 120), 120], [t('automatic_call_distributors.announce_position.every_x_seconds', :x_seconds => 300), 300], [t('automatic_call_distributors.announce_position.every_x_seconds', :x_seconds => 600), 600]], :include_blank => false + + = f.input :announce_call_agents, :label => t('automatic_call_distributors.form.announce_call_agents.label'), :hint => conditional_hint('automatic_call_distributors.form.announce_call_agents.hint') + = f.input :greeting, :label => t('automatic_call_distributors.form.greeting.label'), :hint => conditional_hint('automatic_call_distributors.form.greeting.hint') + = f.input :goodbye, :label => t('automatic_call_distributors.form.goodbye.label'), :hint => conditional_hint('automatic_call_distributors.form.goodbye.hint') + = f.input :music, :label => t('automatic_call_distributors.form.music.label'), :hint => conditional_hint('automatic_call_distributors.form.music.hint') + \ No newline at end of file diff --git a/app/views/automatic_call_distributors/_index_core.html.haml b/app/views/automatic_call_distributors/_index_core.html.haml new file mode 100644 index 0000000..c31a648 --- /dev/null +++ b/app/views/automatic_call_distributors/_index_core.html.haml @@ -0,0 +1,39 @@ +%table + %tr + %th= t('automatic_call_distributors.index.name') + %th= t('automatic_call_distributors.index.strategy') + %th= t('automatic_call_distributors.index.max_callers') + %th= t('automatic_call_distributors.index.agent_timeout') + %th= t('automatic_call_distributors.index.retry_timeout') + %th= t('automatic_call_distributors.index.join') + %th= t('automatic_call_distributors.index.leave') + %th= t('automatic_call_distributors.index.phone_numbers') + %th= t('automatic_call_distributors.index.acd_agents') + + - reset_cycle + - for automatic_call_distributor in automatic_call_distributors + %tr{:class => cycle('odd', 'even')} + %td= automatic_call_distributor.name + %td= t("automatic_call_distributors.strategies.#{automatic_call_distributor.strategy}") + %td= automatic_call_distributor.max_callers + %td= automatic_call_distributor.agent_timeout + %td= automatic_call_distributor.retry_timeout + %td= t("automatic_call_distributors.join_on.#{automatic_call_distributor.join}") + %td= t("automatic_call_distributors.leave_on.#{automatic_call_distributor.leave}") + + %td + - if automatic_call_distributor.phone_numbers.count > 0 + = render 'phone_numbers/listing', :phone_numbers => automatic_call_distributor.phone_numbers + %br + = render :partial => 'shared/create_link', :locals => {:parent => automatic_call_distributor, :child_class => PhoneNumber, :short_link => true} + + %td + - if automatic_call_distributor.acd_agents.count > 3 + = link_to automatic_call_distributor.acd_agents.count, automatic_call_distributor_acd_agents_path(automatic_call_distributor) + %br + - elsif automatic_call_distributor.acd_agents.count > 0 + = render 'acd_agents/listing', :acd_agents => automatic_call_distributor.acd_agents + %br + = render :partial => 'shared/create_link', :locals => {:parent => automatic_call_distributor, :child_class => AcdAgent, :short_link => true} + + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => automatic_call_distributor.automatic_call_distributorable, :child => automatic_call_distributor} \ No newline at end of file diff --git a/app/views/automatic_call_distributors/edit.html.haml b/app/views/automatic_call_distributors/edit.html.haml new file mode 100644 index 0000000..28cba74 --- /dev/null +++ b/app/views/automatic_call_distributors/edit.html.haml @@ -0,0 +1,3 @@ +- title t("automatic_call_distributors.edit.page_title") + += render "form" \ No newline at end of file diff --git a/app/views/automatic_call_distributors/index.html.haml b/app/views/automatic_call_distributors/index.html.haml new file mode 100644 index 0000000..f3f8b2b --- /dev/null +++ b/app/views/automatic_call_distributors/index.html.haml @@ -0,0 +1,6 @@ +- title t("automatic_call_distributors.index.page_title") + +- if @automatic_call_distributors && @automatic_call_distributors.count > 0 + = render "index_core", :automatic_call_distributors => @automatic_call_distributors + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => AutomaticCallDistributor} diff --git a/app/views/automatic_call_distributors/new.html.haml b/app/views/automatic_call_distributors/new.html.haml new file mode 100644 index 0000000..96a2d93 --- /dev/null +++ b/app/views/automatic_call_distributors/new.html.haml @@ -0,0 +1,3 @@ +- title t("automatic_call_distributors.new.page_title") + += render "form" \ No newline at end of file diff --git a/app/views/automatic_call_distributors/show.html.haml b/app/views/automatic_call_distributors/show.html.haml new file mode 100644 index 0000000..e5bf785 --- /dev/null +++ b/app/views/automatic_call_distributors/show.html.haml @@ -0,0 +1,59 @@ +- title t("automatic_call_distributors.show.page_title") + +%table + %tr + %th= t('automatic_call_distributors.show.uuid') + ":" + %td= @automatic_call_distributor.uuid + %tr + %th= t('automatic_call_distributors.show.name') + ":" + %td= @automatic_call_distributor.name + %tr + %th= t('automatic_call_distributors.show.strategy') + ":" + %td= t("automatic_call_distributors.strategies.#{@automatic_call_distributor.strategy}") + %tr + %th= t('automatic_call_distributors.show.max_callers') + ":" + %td= @automatic_call_distributor.max_callers + %tr + %th= t('automatic_call_distributors.show.agent_timeout') + ":" + %td= @automatic_call_distributor.agent_timeout + %tr + %th= t('automatic_call_distributors.show.retry_timeout') + ":" + %td= @automatic_call_distributor.retry_timeout + %tr + %th= t('automatic_call_distributors.show.join') + ":" + %td= t("automatic_call_distributors.join_on.#{@automatic_call_distributor.join}") + %tr + %th= t('automatic_call_distributors.show.leave') + ":" + %td= t("automatic_call_distributors.leave_on.#{@automatic_call_distributor.leave}") + + %tr + %th= t('automatic_call_distributors.show.announce_position') + ":" + %td= @automatic_call_distributor.announce_position + + %tr + %th= t('automatic_call_distributors.show.announce_call_agents') + ":" + %td= @automatic_call_distributor.announce_call_agents + + %tr + %th= t('automatic_call_distributors.show.greeting') + ":" + %td= @automatic_call_distributor.greeting + + %tr + %th= t('automatic_call_distributors.show.goodbye') + ":" + %td= @automatic_call_distributor.goodbye + + %tr + %th= t('automatic_call_distributors.show.music') + ":" + %td= @automatic_call_distributor.music + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @automatic_call_distributor.automatic_call_distributorable, :child => @automatic_call_distributor } + +- if can?( :index, @automatic_call_distributor.phone_numbers ) + %h3= t('automatic_call_distributors.index.phone_numbers') + = render 'phone_numbers/index_core', :phone_numbers => @automatic_call_distributor.phone_numbers + = render :partial => 'shared/create_link', :locals => {:parent => @automatic_call_distributor, :child_class => PhoneNumber, :short_link => true} + +- if can?( :index, @automatic_call_distributor.acd_agents ) + %h3= t('automatic_call_distributors.index.acd_agents') + = render 'acd_agents/index_core', :acd_agents => @automatic_call_distributor.acd_agents + = render :partial => 'shared/create_link', :locals => {:parent => @automatic_call_distributor, :child_class => AcdAgent, :short_link => true} diff --git a/app/views/call_forwards/_form.html.haml b/app/views/call_forwards/_form.html.haml new file mode 100644 index 0000000..7310af3 --- /dev/null +++ b/app/views/call_forwards/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([ @phone_number, @call_forward ]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('call_forwards.form.submit') \ No newline at end of file diff --git a/app/views/call_forwards/_form_core.html.haml b/app/views/call_forwards/_form_core.html.haml new file mode 100644 index 0000000..3dadb68 --- /dev/null +++ b/app/views/call_forwards/_form_core.html.haml @@ -0,0 +1,15 @@ +.inputs + = f.input :call_forward_case_id, :as => :select, :collection => @available_call_forward_cases.map {|x| [I18n.t("call_forward_cases.#{x.value}"), x.id] }, :label => t('call_forwards.form.call_forward_case_id.label'), :hint => conditional_hint('call_forwards.form.call_forward_case_id.hint'), :include_blank => false + = f.input :timeout, :label => t('call_forwards.form.timeout.label'), :hint => conditional_hint('call_forwards.form.timeout.hint') + + = f.input :call_forwarding_destination , :as => :select, :collection => @call_forwarding_destinations, :label => t('call_forwards.form.call_forwarding_destination.label'), :hint => conditional_hint('call_forwards.form.call_forwarding_destination.hint'), :include_blank => false + + = f.input :destination, :label => t('call_forwards.form.destination.label'), :hint => conditional_hint('call_forwards.form.destination.hint') + + + = f.input :source, :label => t('call_forwards.form.source.label'), :hint => conditional_hint('call_forwards.form.source.hint') + - if GuiFunction.display?('depth_field_in_call_forward_form', current_user) + = f.input :depth, :collection => 1..MAX_CALL_FORWARD_DEPTH, :label => t('call_forwards.form.depth.label'), :hint => conditional_hint('call_forwards.form.depth.hint') + - else + = f.hidden_field :depth + = f.input :active, :label => t('call_forwards.form.active.label'), :hint => conditional_hint('call_forwards.form.active.hint') diff --git a/app/views/call_forwards/_index_core.html.haml b/app/views/call_forwards/_index_core.html.haml new file mode 100644 index 0000000..7733855 --- /dev/null +++ b/app/views/call_forwards/_index_core.html.haml @@ -0,0 +1,31 @@ +%table + %tr + - if !@phone_number + %th= t('call_forwards.index.phone_number_id') + %th= t('call_forwards.index.call_forward_case_id') + %th= t('call_forwards.index.timeout') + %th= t('call_forwards.index.destination') + %th= t('call_forwards.index.source') + - if GuiFunction.display?('depth_field_value_in_index_table', current_user) + %th= t('call_forwards.index.depth') + %th= t('call_forwards.index.active') + + - reset_cycle + - for call_forward in call_forwards + %tr{:class => cycle('odd', 'even')} + - if !@phone_number + %td= call_forward.phone_number + %td= t("call_forward_cases.#{call_forward.call_forward_case.value}") + %td= call_forward.timeout + %td + = call_forward.destination + - if call_forward.call_forwardable_type + %br + = call_forward.call_forwardable_type + - if call_forward.call_forwardable + = ": #{call_forward.call_forwardable}" + %td= call_forward.source + - if GuiFunction.display?('depth_field_value_in_index_table', current_user) + %td= call_forward.depth + %td= call_forward.active + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => call_forward.phone_number, :child => call_forward} \ No newline at end of file diff --git a/app/views/call_forwards/edit.html.haml b/app/views/call_forwards/edit.html.haml new file mode 100644 index 0000000..5fa9dcd --- /dev/null +++ b/app/views/call_forwards/edit.html.haml @@ -0,0 +1,3 @@ +- title t("call_forwards.edit.page_title", :resource => " for phone number #{@phone_number}" ) + += render "form" \ No newline at end of file diff --git a/app/views/call_forwards/index.html.haml b/app/views/call_forwards/index.html.haml new file mode 100644 index 0000000..93d64f2 --- /dev/null +++ b/app/views/call_forwards/index.html.haml @@ -0,0 +1,6 @@ +- title t("call_forwards.index.page_title") + +- if @call_forwards.count > 0 + = render "index_core", :call_forwards => @call_forwards + += render :partial => 'shared/create_link', :locals => {:parent => @phone_number, :child_class => CallForward} \ No newline at end of file diff --git a/app/views/call_forwards/new.html.haml b/app/views/call_forwards/new.html.haml new file mode 100644 index 0000000..960a9e6 --- /dev/null +++ b/app/views/call_forwards/new.html.haml @@ -0,0 +1,3 @@ +- title t("call_forwards.new.page_title") + += render "form" diff --git a/app/views/call_forwards/show.html.haml b/app/views/call_forwards/show.html.haml new file mode 100644 index 0000000..6d1a0c6 --- /dev/null +++ b/app/views/call_forwards/show.html.haml @@ -0,0 +1,33 @@ +- title t("call_forwards.show.page_title") + +%p + %strong= t('call_forwards.show.phone_number_id') + ":" + = @call_forward.phone_number +%p + %strong= t('call_forwards.show.call_forward_case_id') + ":" + = t("call_forward_cases.#{@call_forward.call_forward_case.value}") +%p + %strong= t('call_forwards.show.timeout') + ":" + = @call_forward.timeout +%p + %strong= t('call_forwards.show.destination') + ":" + = @call_forward.destination +- if @call_forward.call_forwardable_type == 'HuntGroup' && @call_forward.call_forwardable.class == HuntGroup + %p + %strong= t('call_forwards.show.hunt_group') + ":" + = @call_forward.call_forwardable +- if @call_forward.call_forwardable_type == 'Voicemail' + %p + %strong= t('call_forwards.show.to_voicemail') + ":" + = 'active' +%p + %strong= t('call_forwards.show.source') + ":" + = @call_forward.source +%p + %strong= t('call_forwards.show.depth') + ":" + = @call_forward.depth +%p + %strong= t('call_forwards.show.active') + ":" + = @call_forward.active + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @phone_number, :child => @call_forward } \ No newline at end of file diff --git a/app/views/call_histories/_index_core.html.haml b/app/views/call_histories/_index_core.html.haml new file mode 100644 index 0000000..2d7658a --- /dev/null +++ b/app/views/call_histories/_index_core.html.haml @@ -0,0 +1,65 @@ += form_tag(destroy_multiple_sip_account_call_histories_path(@sip_account), :method => :delete, :id => 'call_history_form') do + %header.entries-nav= render :partial => "call_histories/navigation" + .content + %table + - reset_cycle + - for call_history in call_histories + - phone_number = call_history.display_number + - voicemail_message = call_history.voicemail_message + - if phone_number + - phone_book_entry = call_history.phone_book_entry_by_number(phone_number) + %tr.call-history-entry{:class => cycle('odd', 'even')} + %td.select_box= check_box_tag("selected_ids[]", call_history.id, false, :id => "select_item_#{call_history.id}", :class => 'select_item') + %td.thumbnail + - image = call_history.display_image(:small, phone_book_entry) + - if image + = image_tag(image, :itemprop => 'image') + %td.time + - if voicemail_message + .voicemail-message + %a{:href => sip_account_voicemail_messages_path(@sip_account, :anchor => "message_#{voicemail_message.id}")} + = image_tag('icons/gs_envelope_16x.png', :class => 'display') + = call_history.display_call_date(t("call_histories.index.date_format"), t("call_histories.index.date_today_format")) + - elsif call_history.entry_type == 'forwarded' + .call-forwarded= call_history.display_call_date(t("call_histories.index.date_format"), t("call_histories.index.date_today_format")) + - if call_history.callee_account_type.to_s.downcase == 'voicemail' + = t("call_histories.index.voicemail") + - else + = call_history.destination_number + - elsif call_history.entry_type == 'dialed' + .call-placed= call_history.display_call_date(t("call_histories.index.date_format"), t("call_histories.index.date_today_format")) + - elsif call_history.entry_type == 'received' + .call-received= call_history.display_call_date(t("call_histories.index.date_format"), t("call_histories.index.date_today_format")) + - elsif call_history.entry_type == 'missed' + .call-missed= call_history.display_call_date(t("call_histories.index.date_format"), t("call_histories.index.date_today_format")) + - else + .call-unknown + = t("call_histories.index.#{call_history.entry_type}") + = call_history.display_call_date(t("call_histories.index.date_format"), t("call_histories.index.date_today_format")) + - if call_history.forwarding_service && call_history.entry_type != 'forwarded' + = t("call_histories.index.forwarded_by") + = call_history.display_auth_account_name + %td.user + - display_name = call_history.display_name + - if display_name.blank? + - display_name = phone_book_entry.to_s + - if phone_book_entry + %a.name{:href => phone_book_phone_book_entry_path(phone_book_entry.phone_book, phone_book_entry), :itemprop => "name"}= display_name + - else + .name= display_name + .phone= phone_number + %td.status + - if call_history.display_duration + .duration= call_history.display_duration + - else + .disposition= t("call_histories.call_results.#{call_history.result}") + %td.actions + - if @sip_account.registration && can?(:call, call_history) + = link_to t('call_histories.index.actions.call'), call_sip_account_call_history_path(@sip_account, call_history), :method => :put + %td.actions + - if can? :destroy, call_history + = link_to t('call_histories.index.actions.destroy'), sip_account_call_history_path(@sip_account, call_history), :method => :delete + + %footer.entries-nav= render :partial => "call_histories/navigation" + = image_submit_tag('icons/cross-16x.png', :confirm => t("call_histories.index.actions.confirm_selected")) + = t("call_histories.index.actions.destroy_multiple") diff --git a/app/views/call_histories/_navigation.html.haml b/app/views/call_histories/_navigation.html.haml new file mode 100644 index 0000000..a1999d9 --- /dev/null +++ b/app/views/call_histories/_navigation.html.haml @@ -0,0 +1,11 @@ +%nav + %ol.abc + %li + %a{ :href => "?type=" }= t("call_histories.index.navigation.all", :calls => @calls_count) + %a{ :href => "?type=missed" }= t("call_histories.index.navigation.missed", :calls => @calls_missed_count) + %a{ :href => "?type=received" }= t("call_histories.index.navigation.received", :calls => @calls_received_count) + %a{ :href => "?type=dialed" }= t("call_histories.index.navigation.dialed", :calls => @calls_dialed_count) + %a{ :href => "?type=forwarded" }= t("call_histories.index.navigation.forwarded", :calls => @calls_forwarded_count) + +.pagination + = will_paginate @call_histories diff --git a/app/views/call_histories/index.html.haml b/app/views/call_histories/index.html.haml new file mode 100644 index 0000000..adf6838 --- /dev/null +++ b/app/views/call_histories/index.html.haml @@ -0,0 +1,6 @@ +- if @type + - title t("call_histories.index.page_title_#{@type}") +- else + - title t("call_histories.index.page_title") + += render "index_core", :call_histories => @call_histories diff --git a/app/views/calls/_index_core.html.haml b/app/views/calls/_index_core.html.haml new file mode 100644 index 0000000..ddd0650 --- /dev/null +++ b/app/views/calls/_index_core.html.haml @@ -0,0 +1,9 @@ +%table + %tr + %th= t('calls.index.uuid') + + - reset_cycle + - for call in @calls + %tr{:class => cycle('odd', 'even')} + %td + = call.uuid diff --git a/app/views/calls/index.html.haml b/app/views/calls/index.html.haml new file mode 100644 index 0000000..4ea60a6 --- /dev/null +++ b/app/views/calls/index.html.haml @@ -0,0 +1,6 @@ +- title t("calls.index.page_title") + +- if @calls.count > 0 + = render "index_core", :calls => @calls + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => Call} \ No newline at end of file diff --git a/app/views/callthroughs/_form.html.haml b/app/views/callthroughs/_form.html.haml new file mode 100644 index 0000000..99f92d0 --- /dev/null +++ b/app/views/callthroughs/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@tenant, @callthrough]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('callthroughs.form.submit') diff --git a/app/views/callthroughs/_form_core.html.haml b/app/views/callthroughs/_form_core.html.haml new file mode 100644 index 0000000..1f137d9 --- /dev/null +++ b/app/views/callthroughs/_form_core.html.haml @@ -0,0 +1,24 @@ +.inputs + = f.input :name, :label => t('callthroughs.form.name.label'), :hint => conditional_hint('callthroughs.form.name.hint') + + %h2= t('callthroughs.form.phone_numbers.label') + - if !t('callthroughs.form.phone_numbers.hint').blank? + %p= t('callthroughs.form.phone_numbers.hint') + = f.simple_fields_for :phone_numbers do |phone_number| + = render "phone_numbers/form_core", :f => phone_number + %p + + - if @callthrough && @callthrough.access_authorizations.size > 0 + %h2= t('callthroughs.form.access_authorizations.label') + - if !t('callthroughs.form.access_authorizations.hint').blank? + %p= t('callthroughs.form.access_authorizations.hint') + = f.simple_fields_for :access_authorizations do |access_authorization| + = render "access_authorizations/form_core", :f => access_authorization + + - if CALLTHROUGH_HAS_WHITELISTS == true + - if @callthrough && @callthrough.whitelists.size > 0 + %h2= t('callthroughs.form.whitelists.label') + - if !t('callthroughs.form.whitelists.hint').blank? + %p= t('callthroughs.form.whitelists.hint') + = f.simple_fields_for :whitelists do |whitelist| + = render "whitelists/form_core", :f => whitelist diff --git a/app/views/callthroughs/_index_core.html.haml b/app/views/callthroughs/_index_core.html.haml new file mode 100644 index 0000000..f1802d4 --- /dev/null +++ b/app/views/callthroughs/_index_core.html.haml @@ -0,0 +1,17 @@ +%table + %tr + %th= t('callthroughs.index.name') + %th= t('callthroughs.index.phone_numbers') + %th= t('callthroughs.index.access_authorized_phone_numbers') + - if CALLTHROUGH_HAS_WHITELISTS == true + %th= t('callthroughs.index.whitelist_phone_numbers') + + - reset_cycle + - for callthrough in callthroughs + %tr{:class => cycle('odd', 'even')} + %td= callthrough.name + %td=render 'phone_numbers/listing', :phone_numbers => callthrough.phone_numbers + %td=render 'phone_numbers/listing', :phone_numbers => callthrough.access_authorization_phone_numbers + - if CALLTHROUGH_HAS_WHITELISTS == true + %td=render 'phone_numbers/listing', :phone_numbers => callthrough.whitelisted_phone_numbers + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => callthrough.tenant, :child => callthrough} \ No newline at end of file diff --git a/app/views/callthroughs/edit.html.haml b/app/views/callthroughs/edit.html.haml new file mode 100644 index 0000000..44fe17e --- /dev/null +++ b/app/views/callthroughs/edit.html.haml @@ -0,0 +1,3 @@ +- title t("callthroughs.edit.page_title") + += render "form" \ No newline at end of file diff --git a/app/views/callthroughs/index.html.haml b/app/views/callthroughs/index.html.haml new file mode 100644 index 0000000..c595351 --- /dev/null +++ b/app/views/callthroughs/index.html.haml @@ -0,0 +1,6 @@ +- title t("callthroughs.index.page_title") + +- if @callthroughs.count > 0 + = render "index_core", :callthroughs => @callthroughs + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => Callthrough} \ No newline at end of file diff --git a/app/views/callthroughs/new.html.haml b/app/views/callthroughs/new.html.haml new file mode 100644 index 0000000..ff47c1c --- /dev/null +++ b/app/views/callthroughs/new.html.haml @@ -0,0 +1,3 @@ +- title t("callthroughs.new.page_title") + += render "form" diff --git a/app/views/callthroughs/show.html.haml b/app/views/callthroughs/show.html.haml new file mode 100644 index 0000000..55bd6eb --- /dev/null +++ b/app/views/callthroughs/show.html.haml @@ -0,0 +1,27 @@ +- title t("callthroughs.show.page_title") + +%p + %strong= t('callthroughs.show.name') + ":" + = @callthrough.name + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @callthrough.tenant, :child => @callthrough } + + +%h2= t('callthroughs.form.phone_numbers.label') +- if @callthrough.phone_numbers.count > 0 + = render 'phone_numbers/index_core', :phone_numbers => @callthrough.phone_numbers + %br += render :partial => 'shared/create_link', :locals => {:parent => @callthrough, :child_class => PhoneNumber} + +%h2= t('callthroughs.form.access_authorizations.label') +- if @callthrough.access_authorizations.count > 0 + = render 'access_authorizations/index_core', :access_authorizations => @callthrough.access_authorizations + %br += render :partial => 'shared/create_link', :locals => {:parent => @callthrough, :child_class => AccessAuthorization} + +- if CALLTHROUGH_HAS_WHITELISTS == true + %h2= t('callthroughs.form.whitelists.label') + - if @callthrough.whitelisted_phone_numbers.count > 0 + = render 'whitelists/index_core', :whitelists => @callthrough.whitelists + %br + = render :partial => 'shared/create_link', :locals => {:parent => @callthrough, :child_class => Whitelist} \ No newline at end of file diff --git a/app/views/conference_invitees/_form.html.haml b/app/views/conference_invitees/_form.html.haml new file mode 100644 index 0000000..400580d --- /dev/null +++ b/app/views/conference_invitees/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@conference, @conference_invitee]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('conference_invitees.form.submit') \ No newline at end of file diff --git a/app/views/conference_invitees/_form_core.html.haml b/app/views/conference_invitees/_form_core.html.haml new file mode 100644 index 0000000..3cac18d --- /dev/null +++ b/app/views/conference_invitees/_form_core.html.haml @@ -0,0 +1,7 @@ +.inputs + = f.simple_fields_for :phone_number, @phone_number do |p| + = p.input :number, :label => t('phone_numbers.form.number.label'), :hint => conditional_hint('phone_numbers.form.number.hint') + + = f.input :pin, :label => t('conference_invitees.form.pin.label'), :hint => conditional_hint('conference_invitees.form.pin.hint') + = f.input :speaker, :label => t('conference_invitees.form.speaker.label'), :hint => conditional_hint('conference_invitees.form.speaker.hint') + = f.input :moderator, :label => t('conference_invitees.form.moderator.label'), :hint => conditional_hint('conference_invitees.form.moderator.hint') diff --git a/app/views/conference_invitees/_index_core.html.haml b/app/views/conference_invitees/_index_core.html.haml new file mode 100644 index 0000000..f84af7d --- /dev/null +++ b/app/views/conference_invitees/_index_core.html.haml @@ -0,0 +1,17 @@ +%table + %tr + %th= t('conference_invitees.index.phone_book_entry_id') + %th= t('conference_invitees.index.phone_number') + %th= t('conference_invitees.index.pin') + %th= t('conference_invitees.index.speaker') + %th= t('conference_invitees.index.moderator') + + - reset_cycle + - for conference_invitee in conference_invitees + %tr{:class => cycle('odd', 'even')} + %td= conference_invitee.phone_book_entry || '-' + %td= conference_invitee.phone_number + %td= conference_invitee.pin + %td= conference_invitee.speaker + %td= conference_invitee.moderator + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => conference_invitee.conference, :child => conference_invitee} \ No newline at end of file diff --git a/app/views/conference_invitees/edit.html.haml b/app/views/conference_invitees/edit.html.haml new file mode 100644 index 0000000..ce90bbe --- /dev/null +++ b/app/views/conference_invitees/edit.html.haml @@ -0,0 +1,3 @@ +- title t("conference_invitees.edit.page_title") + += render "form" \ No newline at end of file diff --git a/app/views/conference_invitees/index.html.haml b/app/views/conference_invitees/index.html.haml new file mode 100644 index 0000000..2a0c26c --- /dev/null +++ b/app/views/conference_invitees/index.html.haml @@ -0,0 +1,6 @@ +- title t("conference_invitees.index.page_title") + +- if @conference_invitees.count > 0 + = render "index_core", :conference_invitees => @conference_invitees + += render :partial => 'shared/create_link', :locals => {:parent => @conference, :child_class => ConferenceInvitee} \ No newline at end of file diff --git a/app/views/conference_invitees/new.html.haml b/app/views/conference_invitees/new.html.haml new file mode 100644 index 0000000..780494e --- /dev/null +++ b/app/views/conference_invitees/new.html.haml @@ -0,0 +1,3 @@ +- title t("conference_invitees.new.page_title") + += render "form" diff --git a/app/views/conference_invitees/show.html.haml b/app/views/conference_invitees/show.html.haml new file mode 100644 index 0000000..57c5627 --- /dev/null +++ b/app/views/conference_invitees/show.html.haml @@ -0,0 +1,20 @@ +- title t("conference_invitees.show.page_title") + +%p + %strong= t('conference_invitees.show.conference_id') + ":" + = @conference_invitee.conference +- if @conference_invitee.phone_book_entry_id + %p + %strong= t('conference_invitees.show.phone_book_entry_id') + ":" + = @conference_invitee.phone_book_entry +%p + %strong= t('conference_invitees.show.pin') + ":" + = @conference_invitee.pin +%p + %strong= t('conference_invitees.show.speaker') + ":" + = @conference_invitee.speaker +%p + %strong= t('conference_invitees.show.moderator') + ":" + = @conference_invitee.moderator + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @conference_invitee.conference, :child => @conference_invitee } \ No newline at end of file diff --git a/app/views/conferences/_form.html.haml b/app/views/conferences/_form.html.haml new file mode 100644 index 0000000..4bee1a4 --- /dev/null +++ b/app/views/conferences/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([ @parent, @conference ]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('conferences.form.submit') \ No newline at end of file diff --git a/app/views/conferences/_form_core.html.haml b/app/views/conferences/_form_core.html.haml new file mode 100644 index 0000000..04754de --- /dev/null +++ b/app/views/conferences/_form_core.html.haml @@ -0,0 +1,11 @@ +.inputs + = f.input :name, :label => t('conferences.form.name.label'), :hint => conditional_hint('conferences.form.name.hint'), :autofocus => true + - if !f.object.start.nil? + = f.input :start, :label => t('conferences.form.start.label'), :hint => conditional_hint('conferences.form.start.hint'), :include_blank => true, :start_year => Time.now.year, :end_year => Time.now.year + 2 + = f.input :end, :label => t('conferences.form.end.label'), :hint => conditional_hint('conferences.form.end.hint'), :include_blank => true, :start_year => Time.now.year, :end_year => Time.now.year + 2 + = f.input :description, :label => t('conferences.form.description.label'), :hint => conditional_hint('conferences.form.description.hint') + = f.input :pin, :label => t('conferences.form.pin.label'), :hint => conditional_hint('conferences.form.pin.hint') + = f.input :max_members, :collection => 1..MAXIMUM_NUMBER_OF_PEOPLE_IN_A_CONFERENCE, :include_blank => false, :label => t('conferences.form.max_members.label'), :hint => conditional_hint('conferences.form.max_members.hint') + = f.input :open_for_anybody, :label => t('conferences.form.open_for_anybody.label'), :hint => conditional_hint('conferences.form.open_for_anybody.hint') + = f.input :announce_new_member_by_name, :label => t('conferences.form.announce_new_member_by_name.label'), :hint => conditional_hint('conferences.form.announce_new_member_by_name.hint') + = f.input :announce_left_member_by_name, :label => t('conferences.form.announce_left_member_by_name.label'), :hint => conditional_hint('conferences.form.announce_left_member_by_name.hint') \ No newline at end of file diff --git a/app/views/conferences/_index_core.html.haml b/app/views/conferences/_index_core.html.haml new file mode 100644 index 0000000..4073e83 --- /dev/null +++ b/app/views/conferences/_index_core.html.haml @@ -0,0 +1,53 @@ +%table + %tr + %th= t('conferences.index.name') + - if !conferences.respond_to?('where') || conferences.where(:start => nil).where(:end => nil).count != conferences.count + %th= t('conferences.index.start') + %th= t('conferences.index.end') + %th= t('conferences.index.phone_numbers') + - if !conferences.respond_to?('where') || conferences.where(:pin => '').count != conferences.count + %th= t('conferences.index.pin') + %th= t('conferences.index.max_members') + %th= t('conferences.index.number_of_invitees') + %th= t('conferences.index.flags') + + - reset_cycle + - for conference in conferences + - parent = conference.conferenceable + %tr{:class => cycle('odd', 'even')} + %td= conference.name + - if !conferences.respond_to?('where') || conferences.where(:start => nil).where(:end => nil).count != conferences.count + %td + - if conference.start + = l conference.start, :format => :long + - else + = '-' + %td + - if conference.end + = l conference.end, :format => :long + - else + = '-' + %td + - if conference.phone_numbers.count > 0 + = render 'phone_numbers/listing', :phone_numbers => conference.phone_numbers.order(:number) + %br + = render :partial => 'shared/create_link', :locals => {:parent => conference, :child_class => PhoneNumber, :short_link => true} + + - if !conferences.respond_to?('where') || conferences.where(:pin => '').count != conferences.count + %td + - if !conference.pin.blank? + = conference.pin + - else + = '-' + %td= conference.max_members + %td= conference.conference_invitees.count + %td + %ul + - if conference.open_for_anybody + %li= t('conferences.index.open_for_anybody') + - if conference.announce_new_member_by_name + %li= t('conferences.index.announce_new_member_by_name') + - if conference.announce_left_member_by_name + %li= t('conferences.index.announce_left_member_by_name') + + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => parent, :child => conference} \ No newline at end of file diff --git a/app/views/conferences/edit.html.haml b/app/views/conferences/edit.html.haml new file mode 100644 index 0000000..bc190e7 --- /dev/null +++ b/app/views/conferences/edit.html.haml @@ -0,0 +1,3 @@ +- title t("conferences.edit.page_title") + += render "form" diff --git a/app/views/conferences/index.html.haml b/app/views/conferences/index.html.haml new file mode 100644 index 0000000..0324acd --- /dev/null +++ b/app/views/conferences/index.html.haml @@ -0,0 +1,6 @@ +- title t("conferences.index.page_title") + +- if @conferences.count > 0 + = render "index_core", :conferences => @conferences + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => Conference} \ No newline at end of file diff --git a/app/views/conferences/new.html.haml b/app/views/conferences/new.html.haml new file mode 100644 index 0000000..102f6a9 --- /dev/null +++ b/app/views/conferences/new.html.haml @@ -0,0 +1,3 @@ +- title t("conferences.new.page_title") + += render "form" diff --git a/app/views/conferences/show.html.haml b/app/views/conferences/show.html.haml new file mode 100644 index 0000000..10ebaed --- /dev/null +++ b/app/views/conferences/show.html.haml @@ -0,0 +1,43 @@ +- title t("conferences.show.page_title") + +%p + %strong= t('conferences.show.name') + ":" + = @conference.name +- if @conference.start + %p + %strong= t('conferences.show.start') + ":" + = @conference.start + %p + %strong= t('conferences.show.end') + ":" + = @conference.end +- if !@conference.description.blank? + %p + %strong= t('conferences.show.description') + ":" + = @conference.description +- if !@conference.pin.blank? + %p + %strong= t('conferences.show.pin') + ":" + = @conference.pin +%p + %strong= t('conferences.show.open_for_anybody') + ":" + = @conference.open_for_anybody +%p + %strong= t('conferences.show.announce_new_member_by_name') + ":" + = @conference.announce_new_member_by_name +%p + %strong= t('conferences.show.announce_left_member_by_name') + ":" + = @conference.announce_left_member_by_name + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @parent, :child => @conference } + +%h2= t('phone_numbers.index.page_title') +- if @phone_numbers.count > 0 + = render "phone_numbers/index_core", :phone_numbers => @phone_numbers + += render :partial => 'shared/create_link', :locals => {:parent => @conference, :child_class => PhoneNumber} + +%h2= t("conference_invitees.index.page_title") +- if @conference.conference_invitees.count > 0 + = render "conference_invitees/index_core", :conference_invitees => @conference.conference_invitees + += render :partial => 'shared/create_link', :locals => {:parent => @conference, :child_class => ConferenceInvitee} \ No newline at end of file diff --git a/app/views/config_polycom/_call_history.xml.haml b/app/views/config_polycom/_call_history.xml.haml new file mode 100644 index 0000000..7d23edd --- /dev/null +++ b/app/views/config_polycom/_call_history.xml.haml @@ -0,0 +1,18 @@ +!!! XML +%html + %head + %title= @phone_xml_object[:title] + %body + %table{ :border => 0 } + %tbody + - @phone_xml_object[:entries].each do |entry| + %tr + %td= entry[:date] + %td= entry[:text] + %td + %a{:href => entry[:url]}= entry[:number] + + %softkey{ :index => 1, :label => 'Home', :action => 'SoftKey:Home' } + %softkey{ :index => 2, :label => 'Refresh', :action => 'SoftKey:Refresh' } + %softkey{ :index => 4, :label => 'Exit', :action => 'SoftKey:Exit' } + %softkey{ :index => 3, :label => 'Back', :action => 'SoftKey:Back' } diff --git a/app/views/config_polycom/_call_history_menu.xml.haml b/app/views/config_polycom/_call_history_menu.xml.haml new file mode 100644 index 0000000..6e56f37 --- /dev/null +++ b/app/views/config_polycom/_call_history_menu.xml.haml @@ -0,0 +1,13 @@ +!!! XML +%html + %head + %title= @phone_xml_object[:title] + %body + - @phone_xml_object[:entries].each do |entry| + %br + %a{ :href => entry[:url]}= entry[:text] + + %softkey{ :index => 1, :label => 'Home', :action => 'SoftKey:Home' } + %softkey{ :index => 2, :label => 'Refresh', :action => 'SoftKey:Refresh' } + %softkey{ :index => 4, :label => 'Exit', :action => 'SoftKey:Exit' } + %softkey{ :index => 3, :label => 'Back', :action => 'SoftKey:Back' } diff --git a/app/views/config_polycom/_phone_book.xml.haml b/app/views/config_polycom/_phone_book.xml.haml new file mode 100644 index 0000000..1066695 --- /dev/null +++ b/app/views/config_polycom/_phone_book.xml.haml @@ -0,0 +1,18 @@ +!!! XML +%html + %head + %title= @phone_xml_object[:title] + %body + %table{ :border => 0 } + %tbody + - @phone_xml_object[:entries].each do |entry| + %tr + %td= entry[:text] + %td + = "#{entry[:type][0]}: " + %a{:href => entry[:url]}= entry[:number] + + %softkey{ :index => 1, :label => 'Home', :action => 'SoftKey:Home' } + %softkey{ :index => 2, :label => 'Refresh', :action => 'SoftKey:Refresh' } + %softkey{ :index => 4, :label => 'Exit', :action => 'SoftKey:Exit' } + %softkey{ :index => 3, :label => 'Back', :action => 'SoftKey:Back' } diff --git a/app/views/config_polycom/config_files.xml.builder b/app/views/config_polycom/config_files.xml.builder new file mode 100644 index 0000000..50819ff --- /dev/null +++ b/app/views/config_polycom/config_files.xml.builder @@ -0,0 +1,12 @@ +xml.instruct! + +xml.tag!('APPLICATION', + 'APP_FILE_PATH' => 'sip.ld', + 'CONFIG_FILES' => "settings-#{@mac_address}.cfg", + 'MISC_FILES' => '', + 'LOG_FILE_DIRECTORY' => '', + 'OVERRIDES_DIRECTORY' => '', + 'CONTACTS_DIRECTORY' => '', + 'LICENSE_DIRECTORY' => '', + 'USER_PROFILES_DIRECTORY' => '', + 'CALL_LISTS_DIRECTORY' => '') diff --git a/app/views/config_polycom/idle_screen.xml.haml b/app/views/config_polycom/idle_screen.xml.haml new file mode 100644 index 0000000..fa52c4f --- /dev/null +++ b/app/views/config_polycom/idle_screen.xml.haml @@ -0,0 +1,7 @@ +!!! XML +%html + %head + %title= @sip_account.caller_name + %body + - @sip_account.phone_numbers.each do |number| + %br= number.number diff --git a/app/views/config_polycom/settings.xml.erb b/app/views/config_polycom/settings.xml.erb new file mode 100644 index 0000000..ea9d325 --- /dev/null +++ b/app/views/config_polycom/settings.xml.erb @@ -0,0 +1,8 @@ + + + + <%= key %>="<%= value %>" + <% end %> + /> + diff --git a/app/views/config_polycom/settings_directory.xml.haml b/app/views/config_polycom/settings_directory.xml.haml new file mode 100644 index 0000000..107f991 --- /dev/null +++ b/app/views/config_polycom/settings_directory.xml.haml @@ -0,0 +1,16 @@ +!!! XML +%directory + %item_list + %item + %ln= 'Directory' + %ct= '!directory' + %sd= 38 + %item + %ln= 'Call History' + %ct= '!callhistory' + %sd= 39 + %item + %ln= 'Applications' + %ct= '!applications' + %sd= 40 + diff --git a/app/views/config_siemens/_menu_list.xml.haml b/app/views/config_siemens/_menu_list.xml.haml new file mode 100644 index 0000000..70bfc43 --- /dev/null +++ b/app/views/config_siemens/_menu_list.xml.haml @@ -0,0 +1,33 @@ +!!! XML +%IppPhone + %IppDisplay + %IppScreen{:ID => '1', :HiddenCount => (@phone_xml_object[:hidden] ? @phone_xml_object[:hidden].length : '0'), :CommandCount => (@phone_xml_object[:commands] ? @phone_xml_object[:commands].length : '0')} + - if @phone_xml_object[:make_call] + %IppAction{:Type => 'MAKECALL'} + %Number= @phone_xml_object[:make_call] + - if @phone_xml_object[:led] != nil + %IppAction{:Type => (@phone_xml_object[:led] ? 'TURNLEDON' : 'TURNLEDOFF')} + %IppKey{:Keypad => 'YES', :SendKeys => 'YES', :BufferKeys => 'NO', :BufferLength => '0', :TermKey => '', :UrlKey => 'key'} + - if @phone_xml_object[:entries] + %IppList{:Type => 'IMPLICIT', :Count => @phone_xml_object[:entries].length, :Columns => @phone_xml_object[:columns]} + - if @phone_xml_object[:title] + %Title= @phone_xml_object[:title] + %Url= @phone_xml_object[:url] + - @phone_xml_object[:entries].each_with_index do |entry, index| + %Option{:ID => index+1, :Selected => (entry[:selected] ? 'TRUE' : 'FALSE'), :Key => (entry[:key] ? entry[:key] : 'item'), :Value => entry[:value]} + - if entry[:image] + %Image= entry[:image] + %OptionText= entry[:text].to_s + - if entry[:text_center] + %OptionText= entry[:text_center].to_s + - if entry[:text_right] + %OptionText= entry[:text_right].to_s + - if @phone_xml_object[:hidden] + - @phone_xml_object[:hidden].each do |key, value| + %IppHidden{:Type => 'VALUE', :Key => key} + %Value= value + - if @phone_xml_object[:commands] + - @phone_xml_object[:commands].each_with_index do |command, index| + %IppCommand{:Type => command[:type], :DisplayOn => command[:display], :Priority => index, :Key => command[:key], :Value => command[:value], :DisplayOn => command[:display_on], :Select => command[:select], :Default => command[:default], :Auto => command[:auto]} + %Label= command[:label] + %Screen= "1" diff --git a/app/views/config_siemens/clean-up.xml.erb b/app/views/config_siemens/clean-up.xml.erb new file mode 100644 index 0000000..e1cbf93 --- /dev/null +++ b/app/views/config_siemens/clean-up.xml.erb @@ -0,0 +1,5 @@ + + + CleanUp + + \ No newline at end of file diff --git a/app/views/config_siemens/index.xml.erb b/app/views/config_siemens/index.xml.erb new file mode 100644 index 0000000..0c60b1f --- /dev/null +++ b/app/views/config_siemens/index.xml.erb @@ -0,0 +1,5 @@ + + + ReadAllItems + + \ No newline at end of file diff --git a/app/views/config_siemens/write.xml.erb b/app/views/config_siemens/write.xml.erb new file mode 100644 index 0000000..ee9e32e --- /dev/null +++ b/app/views/config_siemens/write.xml.erb @@ -0,0 +1,10 @@ + + + WriteItems + + <% @new_settings.each do |setting| %> + index="<%=setting[1]%>"<%end%>><%=setting[2]%> + <% end %> + + + \ No newline at end of file diff --git a/app/views/config_snom/_snom_phone_directory.xml.haml b/app/views/config_snom/_snom_phone_directory.xml.haml new file mode 100644 index 0000000..698f2e5 --- /dev/null +++ b/app/views/config_snom/_snom_phone_directory.xml.haml @@ -0,0 +1,19 @@ +!!! XML +%SnomIPPhoneDirectory{:speedselect => 'off'} + %Title= @phone_xml_object[:title] + %Prompt= @phone_xml_object[:prompt] + - @phone_xml_object[:entries].each do |entry| + %DirectoryEntry{:sel => entry[:selected].to_s} + %Name= entry[:text] + %Telephone= entry[:number] + + - if @phone_xml_object[:softkeys] + - @phone_xml_object[:softkeys].each do |softkey| + %SoftKeyItem + %Name= softkey[:name] + - if ! softkey[:label].blank? + %Label= softkey[:label] + - if ! softkey[:url].blank? + %URL= softkey[:url] + - if ! softkey[:softkey].blank? + %URL= softkey[:softkey] diff --git a/app/views/config_snom/_snom_phone_input.xml.haml b/app/views/config_snom/_snom_phone_input.xml.haml new file mode 100644 index 0000000..6038282 --- /dev/null +++ b/app/views/config_snom/_snom_phone_input.xml.haml @@ -0,0 +1,19 @@ +!!! XML +%SnomIPPhoneInput + %Title= @phone_xml_object[:title] + %Prompt= @phone_xml_object[:prompt] + %URL= @phone_xml_object[:url] + %InputItem + %DisplayName= @phone_xml_object[:display_name] + %QueryStringParam= @phone_xml_object[:query_string_param] + %DefaultValue= @phone_xml_object[:default_value] + %InputFlags= @phone_xml_object[:input_flags] + - if @phone_xml_object[:softkeys] + - @phone_xml_object[:softkeys].each do |softkey| + %SoftKeyItem + %Name= softkey[:name] + %Label= softkey[:label] + - if ! softkey[:url].blank? + %URL= softkey[:url] + - if ! softkey[:softkey].blank? + %URL= softkey[:softkey] diff --git a/app/views/config_snom/_snom_phone_menu.xml.haml b/app/views/config_snom/_snom_phone_menu.xml.haml new file mode 100644 index 0000000..de016c0 --- /dev/null +++ b/app/views/config_snom/_snom_phone_menu.xml.haml @@ -0,0 +1,17 @@ +!!! XML +%SnomIPPhoneMenu{:speedselect => 'off'} + %Title= @phone_xml_object[:title] + - @phone_xml_object[:entries].each do |entry| + %MenuItem{:sel => entry[:selected].to_s} + %Name= entry[:text] + %URL= entry[:url] + + - if @phone_xml_object[:softkeys] + - @phone_xml_object[:softkeys].each do |softkey| + %SoftKeyItem + %Name= softkey[:name] + %Label= softkey[:label] + - if ! softkey[:url].blank? + %URL= softkey[:url] + - if ! softkey[:softkey].blank? + %URL= softkey[:softkey] diff --git a/app/views/config_snom/_snom_phone_text.xml.haml b/app/views/config_snom/_snom_phone_text.xml.haml new file mode 100644 index 0000000..6c3773c --- /dev/null +++ b/app/views/config_snom/_snom_phone_text.xml.haml @@ -0,0 +1,16 @@ +!!! XML +%SnomIPPhoneText + %Title= @phone_xml_object[:title] + %Prompt= @phone_xml_object[:prompt] + %Text= @phone_xml_object[:text] + - if @phone_xml_object[:softkeys] + - @phone_xml_object[:softkeys].each do |softkey| + %SoftKeyItem + %Name= softkey[:name] + %Label= softkey[:label] + - if ! softkey[:url].blank? + %URL= softkey[:url] + - if ! softkey[:softkey].blank? + %URL= softkey[:softkey] + -if @phone_xml_object[:fetch_url] + %fetch{:mil => @phone_xml_object[:fetch_mil]}= @phone_xml_object[:fetch_url] diff --git a/app/views/config_snom/call_history.xml.haml b/app/views/config_snom/call_history.xml.haml new file mode 100644 index 0000000..00f9990 --- /dev/null +++ b/app/views/config_snom/call_history.xml.haml @@ -0,0 +1,2 @@ +!!! XML += render @phone_xml_object[:name] diff --git a/app/views/config_snom/idle_screen.xml.haml b/app/views/config_snom/idle_screen.xml.haml new file mode 100644 index 0000000..9476c44 --- /dev/null +++ b/app/views/config_snom/idle_screen.xml.haml @@ -0,0 +1,33 @@ +!!! XML +%screen_description + - if @phone_xml_object[:image] + %Image + %Data{:encoding => 'base64'}= @phone_xml_object[:image][:data] + %LocationX= @phone_xml_object[:image][:location_x] + %LocationY= @phone_xml_object[:image][:location_y] + %Invert= @phone_xml_object[:image][:invert] + - if @phone_xml_object[:clock] + %Clock + %LocationX= @phone_xml_object[:clock][:location_x] + %LocationY= @phone_xml_object[:clock][:location_y] + - if @phone_xml_object[:digital_clock] + %DigitalClock + %LocationX= @phone_xml_object[:digital_clock][:location_x] + %LocationY= @phone_xml_object[:digital_clock][:location_y] + - if @phone_xml_object[:date] + %Date + %LocationX= @phone_xml_object[:date][:location_x] + %LocationY= @phone_xml_object[:date][:location_y] + - if @phone_xml_object[:line] + %Line + %Account + %LocationX= @phone_xml_object[:line][:location_x] + %LocationY= @phone_xml_object[:line][:location_y] + - if @phone_xml_object[:status] + %Status + %LocationX= @phone_xml_object[:status][:location_x] + %LocationY= @phone_xml_object[:status][:location_y] + - if @phone_xml_object[:softkeys] + %Softkeys + %LocationX= @phone_xml_object[:softkeys][:location_x] + %LocationY= @phone_xml_object[:softkeys][:location_y] diff --git a/app/views/config_snom/log_in.xml.haml b/app/views/config_snom/log_in.xml.haml new file mode 100644 index 0000000..1f45d93 --- /dev/null +++ b/app/views/config_snom/log_in.xml.haml @@ -0,0 +1,3 @@ +!!! XML += render @phone_xml_object[:name] + diff --git a/app/views/config_snom/show.xml.haml b/app/views/config_snom/show.xml.haml new file mode 100644 index 0000000..d9953c5 --- /dev/null +++ b/app/views/config_snom/show.xml.haml @@ -0,0 +1,151 @@ +!!! XML +%settings + %phone-settings + %auto_reboot_on_setting_change{:perm => 'RW'}= 'off' + - if !@phone_settings[:setting_server].blank? + %setting_server{:perm => 'RW'}= @phone_settings[:setting_server] + %web_language{:perm => 'RW'}= 'English' + %language{:perm => 'RW'}= @phone_settings[:language] + %timezone{:perm => 'RW'}= 'GER+1' + %date_us_format{:perm => 'RW'}= 'off' + %time_24_format{:perm => 'RW'}= 'on' + %reset_settings{:perm => 'RW'}= '' + %update_policy{:perm => 'RW'}= 'settings_only' + %settings_refresh_timer{:perm => 'RW'}= '0' + %firmware_status{:perm => 'RW'}= '' + %webserver_type{:perm => 'R'}= 'http_https' + %http_scheme{:perm => 'RW'}= 'off' + %http_port{:perm => 'R'}= '80' + %https_port{:perm => 'R'}= '443' + %http_user{:perm => 'R'}= @phone_settings[:http_user] + %http_pass{:perm => 'R'}= @phone_settings[:http_pass] + %admin_mode_password{:perm => 'R'}= @phone_settings[:admin_mode_password] + %tone_scheme{:perm => 'RW'}= @phone_settings[:tone_scheme] + %keytones{:perm => 'RW'}= 'off' + %dtmf_speaker_phone{:perm => 'RW'}= 'off' + %disable_redirection_menu{:perm => 'R'}= 'on' + %retry_after_failed_register{:perm => 'RW'}= '70' + %encode_display_name{:perm => 'R'}= 'on' + %dtmf_payload_type{:perm => 'RW'}= '101' + %ignore_security_warning{:perm => 'R'}= 'on' + %call_completion{:perm => 'RW'}= 'on' + %block_url_dialing{:perm => 'RW'}= 'on' + %redirect_ringing{:perm => 'RW'}= 'on' + %goto_virtual_keys_state_on_activity{:perm => 'RW'}= 'off' + %goto_monitor_state_on_line_activity{:perm => 'RW'}= 'on' + %ringer_animation{:perm => 'RW'}= 'on' + %display_method{:perm => 'RW'}= 'display_name_number' + %callpickup_dialoginfo{:perm => 'RW'}= 'on' + %show_local_line{:perm => 'RW'}= 'off' + %mwi_notification{:perm => 'RW'}= 'silent' + %mwi_dialtone{:perm => 'RW'}= 'normal' + %prefer_saved_over_received_photo{:perm => 'RW'}= 'off' + %no_dnd{:perm => 'RW'}= 'on' + %logon_wizard{:perm => 'RW'}= 'off' + %disable_deflection{:perm => 'RW'}= 'off' + %csta_control{:perm => 'RW'}= 'on' + %save_latest_callrecords_to_flash{:perm => 'RW'}= 'off' + %use_proxy_number_guessing{:perm => 'RW'}= 'off' + %guess_number{:perm => 'RW'}= 'off' + %guess_start_length{:perm => 'RW'}= '3' + %ieee8021x_eap_md5_username{:perm => 'RW'}= PROVISIONING_IEEE8021X_EAP_USERNAME + %ieee8021x_eap_md5_password{:perm => 'RW'}= PROVISIONING_IEEE8021X_EAP_PASSWORD + + - 0.upto(9) do |ringer_idx| + %internal_ringer_text{:idx => ringer_idx, :perm => 'RW'}= "Ringer#{(ringer_idx+1)}" + %internal_ringer_file{:idx => ringer_idx, :perm => 'RW'}= "Ringer#{(ringer_idx+1)}" + + %internal_ringer_text{:idx => 10, :perm => 'RW'}= "Ringer0" + %internal_ringer_file{:idx => 10, :perm => 'RW'}= "Silent" + + %gui_fkey1{:perm => 'R'}= 'none' + %gui_fkey2{:perm => 'R'}= 'none' + %gui_fkey3{:perm => 'R'}= 'none' + %gui_fkey4{:perm => 'R'}= 'none' + + %dkey_menu{:perm => 'RW'}= @dkeys[:menu] + %dkey_retrieve{:perm => 'RW'}= @dkeys[:retrieve] + %dkey_conf{:perm => 'RW'}= @dkeys[:conf] + %dkey_redial{:perm => 'RW'}= @dkeys[:redial] + %dkey_directory{:perm => 'RW'}= @dkeys[:directory] + + %idle_ok_key_action{:perm => 'RW'}= @dkeys[:idle_ok] + %idle_cancel_key_action{:perm => 'RW'}= @dkeys[:idle_cancel] + %idle_up_key_action{:perm => 'RW'}= @dkeys[:idle_up] + %idle_down_key_action{:perm => 'RW'}= @dkeys[:idle_down] + %idle_left_key_action{:perm => 'RW'}= @dkeys[:idle_left] + %idle_right_key_action{:perm => 'RW'}= @dkeys[:idle_right] + + != "\ + + + +%html.no-js{ :lang => "en" } + ~#OPTIMIZE Make html lang attribute reflect the actual language. + + %header + %meta{ :charset => "utf-8" }/ + ~#OPTIMIZE "/" seems to be supposed to make an empty element tag, but it doesn't work. HAML bug? + %title + = content_for?(:title) ? yield(:title) : "Untitled" + %meta{ :name => "viewport", :content => "width=device-width, initial-scale=1.0" }/ + = stylesheet_link_tag "application" + = javascript_include_tag "application" + = csrf_meta_tag + = yield(:head) + + %body + #container + = render :partial => "shared/header" + = render :partial => "shared/system_message" + = render :partial => "shared/flash", :locals => { :flash => flash} + + #content{:role => 'main'} + .light + %header.main + .breadcrumbs= render_breadcrumbs :separator => ' » ' + - if show_title? + %h1= yield(:title) + = yield + + %footer#main + %ul + - if GuiFunction.display?('amooma_commercial_support_link_in_footer', current_user) + %li + %a{:href => "http://www.amooma.de"} Kommerzieller Support und Consulting + - if GuiFunction.display?('gemeinschaft_mailinglist_link_in_footer', current_user) + %li + %a{:href => "https://groups.google.com/group/gs5-users/"} Kostenlose Mailingliste + + .amooma-logo + %span brought to you by + %a{ :target => '_blank', :href => "http://www.amooma.de/" } Amooma + diff --git a/app/views/manufacturers/_form.html.haml b/app/views/manufacturers/_form.html.haml new file mode 100644 index 0000000..d89c603 --- /dev/null +++ b/app/views/manufacturers/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for(@manufacturer) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('manufacturers.form.submit') \ No newline at end of file diff --git a/app/views/manufacturers/_form_core.html.haml b/app/views/manufacturers/_form_core.html.haml new file mode 100644 index 0000000..84b85c9 --- /dev/null +++ b/app/views/manufacturers/_form_core.html.haml @@ -0,0 +1,4 @@ +.inputs + = f.input :name, :label => t('manufacturers.form.name.label'), :hint => conditional_hint('manufacturers.form.name.hint') + = f.input :ieee_name, :label => t('manufacturers.form.ieee_name.label'), :hint => conditional_hint('manufacturers.form.ieee_name.hint') + = f.input :homepage_url, :label => t('manufacturers.form.homepage_url.label'), :hint => conditional_hint('manufacturers.form.homepage_url.hint') diff --git a/app/views/manufacturers/_index_core.html.haml b/app/views/manufacturers/_index_core.html.haml new file mode 100644 index 0000000..8937909 --- /dev/null +++ b/app/views/manufacturers/_index_core.html.haml @@ -0,0 +1,18 @@ +%table + %tr + %th= t('manufacturers.index.name') + %th= t('manufacturers.index.ieee_name') + %th= t('manufacturers.index.homepage_url') + %th= t('manufacturers.index.phone_models') + + - reset_cycle + - for manufacturer in manufacturers + %tr{:class => cycle('odd', 'even')} + %td= manufacturer.name + %td= manufacturer.ieee_name + %td + - if manufacturer.homepage_url + =link_to manufacturer.homepage_url, manufacturer.homepage_url + %td + = manufacturer.phone_models.map{|x| x.to_s }.join(', ') + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => manufacturer} \ No newline at end of file diff --git a/app/views/manufacturers/edit.html.haml b/app/views/manufacturers/edit.html.haml new file mode 100644 index 0000000..61bcba0 --- /dev/null +++ b/app/views/manufacturers/edit.html.haml @@ -0,0 +1,3 @@ +- title t("manufacturers.edit.page_title") + += render "form" diff --git a/app/views/manufacturers/index.html.haml b/app/views/manufacturers/index.html.haml new file mode 100644 index 0000000..43fecc6 --- /dev/null +++ b/app/views/manufacturers/index.html.haml @@ -0,0 +1,5 @@ +- title t("manufacturers.index.page_title") + += render "index_core", :manufacturers => @manufacturers + += render :partial => 'shared/create_link', :locals => {:child_class => Manufacturer} \ No newline at end of file diff --git a/app/views/manufacturers/new.html.haml b/app/views/manufacturers/new.html.haml new file mode 100644 index 0000000..4fb9dbf --- /dev/null +++ b/app/views/manufacturers/new.html.haml @@ -0,0 +1,3 @@ +- title t("manufacturers.new.page_title") + += render "form" diff --git a/app/views/manufacturers/show.html.haml b/app/views/manufacturers/show.html.haml new file mode 100644 index 0000000..1b8383b --- /dev/null +++ b/app/views/manufacturers/show.html.haml @@ -0,0 +1,18 @@ +- title t("manufacturers.show.page_title") + +%p + %strong= t('manufacturers.show.name') + ":" + = @manufacturer.name +%p + %strong= t('manufacturers.show.ieee_name') + ":" + = @manufacturer.ieee_name +%p + %strong= t('manufacturers.show.homepage_url') + ":" + - if @manufacturer.homepage_url + =link_to @manufacturer.homepage_url, @manufacturer.homepage_url + += render :partial => 'shared/show_edit_destroy_part', :locals => { :child => @manufacturer } + +%h2=t("phone_models.index.page_title") + +=render 'phone_models/index_core', :phone_models => @manufacturer.phone_models \ No newline at end of file diff --git a/app/views/notifications/new_fax.text.erb b/app/views/notifications/new_fax.text.erb new file mode 100644 index 0000000..579cc18 --- /dev/null +++ b/app/views/notifications/new_fax.text.erb @@ -0,0 +1,8 @@ +Hello <%= @fax[:greeting] %>, + +You've just received a fax document on your Gemeinschaft account <%= @fax[:account_name] %>. + + Caller: <%= @fax[:from] %> +Remote Station: <%= @fax[:remote_station_id] %> + Local Station: <%= @fax[:local_station_id] %> +Receiving Time: <%= @fax[:date] %> diff --git a/app/views/notifications/new_password.text.erb b/app/views/notifications/new_password.text.erb new file mode 100644 index 0000000..a865960 --- /dev/null +++ b/app/views/notifications/new_password.text.erb @@ -0,0 +1,3 @@ +Hello <%= @message[:greeting] %>, + +Your password has been reset to: <%= @password %> diff --git a/app/views/notifications/new_pin.text.erb b/app/views/notifications/new_pin.text.erb new file mode 100644 index 0000000..11cf17b --- /dev/null +++ b/app/views/notifications/new_pin.text.erb @@ -0,0 +1,7 @@ +Hello <%= @pin[:greeting] %>, + +the PIN of one of your conference rooms has been changed. + +Conference: <%= @pin[:conference] %> + Numbers: <%= @pin[:phone_numbers] %> + New PIN: <%= @pin[:pin] %> diff --git a/app/views/notifications/new_voicemail.text.erb b/app/views/notifications/new_voicemail.text.erb new file mode 100644 index 0000000..adeabda --- /dev/null +++ b/app/views/notifications/new_voicemail.text.erb @@ -0,0 +1,9 @@ +Hello <%= @voicemail[:greeting] %>, + +You've just received a voicemail on your Gemeinschaft account <%= @voicemail[:destination] %>. + + From: <%= @voicemail[:from] %> + To: <%= @voicemail[:to] %> +Receiving Time: <%= @voicemail[:date] %> +Message length: <%= @voicemail[:duration] %> + File: <%= @voicemail[:file_name] %> diff --git a/app/views/page/conference.html.haml b/app/views/page/conference.html.haml new file mode 100644 index 0000000..061dfd1 --- /dev/null +++ b/app/views/page/conference.html.haml @@ -0,0 +1,80 @@ +- conf_call_topic = "The next big thing" +- title "Conference Call \u2013 Topic: #{conf_call_topic}" + + +%section.conference + + %section.panel.speakers.first + %header + %h3 Speakers + ~# Naming this class message, since when you add or chat you're sending + ~# a message to the server. + %form.message + %input{:placeholder => '# '} + .actors + - 2.times do + .actor + .info + - user = current_user #FIXME + - avatar_url = user.image_url(:mini) || 'stubs/user-36x.jpg' + = image_tag avatar_url.to_s, :class => 'display', :alt => "[ ]" + %span.name Fake Stefan + %span.status Joined at 03:00 + .voice-actions + %a.make.listener{ :href => '#', :title => "Make listener" } Make listener + %a.voice.unmuted{ :href => '#', :title => "Mute" } Mute + %a.remove{ :href => '#', :title => "Remove from conference" } Remove + + + %section.panel.listeners + %header + %h3 Listeners + ~# Naming this class message, since when you add or chat you're sending + ~# a message to the server. + %form.message + %input{:placeholder => '# '} + .actors + - 5.times do + .actor + .info + - user = current_user #FIXME + - avatar_url = user.image_url(:mini) || 'stubs/user-36x.jpg' + = image_tag avatar_url.to_s, :class => 'display', :alt => "[ ]" + %span.name Fake Stefan + %span.status Joined at 03:00 + .voice-actions + %a.make.speaker{ :href => '#', :title => "Make speaker" } Make speaker + %a.voice.unmuted{ :href => '#', :title => "Mute" } Mute + %a.remove{ :href => '#', :title => "Remove from conference" } Remove + + + %section.panel.log.last + %header + %h3 Log + ~# Naming this class message, since when you add or chat you're sending + ~# a message to the server. + %form.message + %input{:placeholder => 'Write a Message...'} + .messages + %div + %span.name Mario: + %span.content Sorry for the Delay! + %div.status + %span.name 03:11: + %span.content Fake Stefan is now a Speaker. + %div.status + %span.name 03:10: + %span.content Stefan Wintermeyer Left. + %div + %span.name Stefan: + %span.content Hello World. + %div.status + %span.name 03:00: + %span.content Stefan Wintermeyer Joined. + %div + %span.name Herpiti Derp: + %span.content Cool Conference Room! + %div + %span.name Pamela: + %span.content I'm here to sing along. + diff --git a/app/views/page/index.de.html.haml b/app/views/page/index.de.html.haml new file mode 100644 index 0000000..2928319 --- /dev/null +++ b/app/views/page/index.de.html.haml @@ -0,0 +1,16 @@ +- title "Gemeinschaft #{GEMEINSCHAFT_VERSION}" + +%div + %h3 Aktueller Mandant + - if current_user && current_user.current_tenant + %strong= current_user.current_tenant + - else + %strong= "(none)" + +%div + %h3 Aktuelle Gruppenzugehörigkeiten + %ul + - (current_user.try(:user_groups) || []).each do |group| + %li + %strong= "#{group.name}" + = "(aus Mandant: %s)" % group.tenant diff --git a/app/views/page/index.html.haml b/app/views/page/index.html.haml new file mode 100644 index 0000000..9621395 --- /dev/null +++ b/app/views/page/index.html.haml @@ -0,0 +1,16 @@ +- title "Gemeinschaft #{GEMEINSCHAFT_VERSION}" + +%div + %h3 Current tenant + - if current_user && current_user.current_tenant + %strong= current_user.current_tenant + - else + %strong= "(none)" + +%div + %h3 Current user groups + %ul + - (current_user.try(:user_groups) || []).each do |group| + %li + %strong= "#{group.name}" + = "(from tenant: %s)" % group.tenant diff --git a/app/views/phone_book_entries/_form.html.haml b/app/views/phone_book_entries/_form.html.haml new file mode 100644 index 0000000..c73d10a --- /dev/null +++ b/app/views/phone_book_entries/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@phone_book, @phone_book_entry]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('phone_book_entries.form.submit') \ No newline at end of file diff --git a/app/views/phone_book_entries/_form_core.html.haml b/app/views/phone_book_entries/_form_core.html.haml new file mode 100644 index 0000000..c05139e --- /dev/null +++ b/app/views/phone_book_entries/_form_core.html.haml @@ -0,0 +1,27 @@ +.inputs + = f.input :is_male, :collection => [[true, t('phone_book_entries.form.gender.male')], [false, t('phone_book_entries.form.gender.female')]], :label_method => :last, :value_method => :first, :label => t('phone_book_entries.form.male.label'), :hint => conditional_hint('phone_book_entries.form.gender.hint'), :label => t('phone_book_entries.form.gender.label'), :as => :radio + = f.input :first_name, :label => t('phone_book_entries.form.first_name.label'), :hint => conditional_hint('phone_book_entries.form.first_name.hint') + = f.input :middle_name, :label => t('phone_book_entries.form.middle_name.label'), :hint => conditional_hint('phone_book_entries.form.middle_name.hint') + = f.input :last_name, :label => t('phone_book_entries.form.last_name.label'), :hint => conditional_hint('phone_book_entries.form.last_name.hint') + = f.input :birth_name, :label => t('phone_book_entries.form.birth_name.label'), :hint => conditional_hint('phone_book_entries.form.birth_name.hint') + = f.input :title, :label => t('phone_book_entries.form.title.label'), :hint => conditional_hint('phone_book_entries.form.title.hint') + = f.input :nickname, :label => t('phone_book_entries.form.nickname.label'), :hint => conditional_hint('phone_book_entries.form.nickname.hint') + = f.input :organization, :label => t('phone_book_entries.form.organization.label'), :hint => conditional_hint('phone_book_entries.form.organization.hint') + / = f.input :is_organization, :label => t('phone_book_entries.form.is_organization.label'), :hint => conditional_hint('phone_book_entries.form.is_organization.hint') + = f.input :department, :label => t('phone_book_entries.form.department.label'), :hint => conditional_hint('phone_book_entries.form.department.hint') + = f.input :job_title, :label => t('phone_book_entries.form.job_title.label'), :hint => conditional_hint('phone_book_entries.form.job_title.hint') + + = f.input :birthday, :label => t('phone_book_entries.form.birthday.label'), :hint => conditional_hint('phone_book_entries.form.birthday.hint'), :start_year => Date.today.year - 100, :end_year => Date.today.year - 0, :order => [:day, :month, :year], :include_blank => true + + = f.input :description, :label => t('phone_book_entries.form.description.label'), :hint => conditional_hint('phone_book_entries.form.description.hint') + + = f.input :image, { :as => :file, :label => t('phone_book_entries.form.image.label'), :hint => conditional_hint('phone_book_entries.form.image.hint') } + + = f.input :homepage_organization, :label => t('phone_book_entries.form.homepage_organization.label'), :hint => conditional_hint('phone_book_entries.form.homepage_organization.hint') + = f.input :homepage_personal, :label => t('phone_book_entries.form.homepage_personal.label'), :hint => conditional_hint('phone_book_entries.form.homepage_personal.hint') + / = f.input :twitter_account, :label => t('phone_book_entries.form.twitter_account.label'), :hint => conditional_hint('phone_book_entries.form.twitter_account.hint') + / = f.input :facebook_account, :label => t('phone_book_entries.form.facebook_account.label'), :hint => conditional_hint('phone_book_entries.form.facebook_account.hint') + / = f.input :google_plus_account, :label => t('phone_book_entries.form.google_plus_account.label'), :hint => conditional_hint('phone_book_entries.form.google_plus_account.hint') + / = f.input :xing_account, :label => t('phone_book_entries.form.xing_account.label'), :hint => conditional_hint('phone_book_entries.form.xing_account.hint') + / = f.input :linkedin_account, :label => t('phone_book_entries.form.linkedin_account.label'), :hint => conditional_hint('phone_book_entries.form.linkedin_account.hint') + diff --git a/app/views/phone_book_entries/_index_core.de.html.haml b/app/views/phone_book_entries/_index_core.de.html.haml new file mode 100644 index 0000000..01be65f --- /dev/null +++ b/app/views/phone_book_entries/_index_core.de.html.haml @@ -0,0 +1,36 @@ +~# To Look for the other fields, please look into Git History. +%section.phone-book-entries + %header.entries-nav= render :partial => "phone_book_entries/navigation" + .content + - reset_cycle + %table + - for entry in phone_book_entries + ~# Dear IE7, + ~# Because of you we have to do this with a table. + ~# With Love, + ~# Mario. + %tr.phone-book-entry{:class => cycle('odd', 'even'), :"itemscope itemtype" => "http://schema.org/Person"} + %td.thumbnail + = image_tag(entry.image_url(:small).to_s, :itemprop => 'image') + %td.user + - if entry.is_organization == true + %a.name{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => "name"}= entry + - else + %a.name{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => "name"}= entry + %a.company{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => 'memberOf'}= entry.organization + %td.contact + - if @found_phone_numbers and @found_phone_numbers.where(:phone_numberable_id => entry.id) + %a.phone{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => 'telephone'}= @found_phone_numbers.where(:phone_numberable_id => entry.id).first + - elsif entry.phone_numbers.first + %a.phone{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => 'telephone'}= entry.phone_numbers.first + - if entry.phone_numbers.count > 1 + %a.more{:href => phone_book_phone_book_entry_path(entry.phone_book, entry)}= t('phone_book_entries.index.more_numbers', :numbers => (entry.phone_numbers.count-1)) + %td.extra + - if !entry.description.blank? + %strong Beschreibung: + %div= entry.description + - if can? :edit, entry + %td= link_to t('phone_book_entries.index.actions.edit'), edit_phone_book_phone_book_entry_path( entry.phone_book, entry ) + - if can? :destroy, entry + %td= link_to t('phone_book_entries.index.actions.destroy'), [entry.phone_book, entry], :confirm => t('phone_book_entries.index.actions.confirm'), :method => :delete + %footer.entries-nav= render :partial => "phone_book_entries/navigation" diff --git a/app/views/phone_book_entries/_index_core.html.haml b/app/views/phone_book_entries/_index_core.html.haml new file mode 100644 index 0000000..d9cfe10 --- /dev/null +++ b/app/views/phone_book_entries/_index_core.html.haml @@ -0,0 +1,36 @@ +~# To Look for the other fields, please look into Git History. +%section.phone-book-entries + %header.entries-nav= render :partial => "phone_book_entries/navigation" + .content + - reset_cycle + %table + - for entry in phone_book_entries + ~# Dear IE7, + ~# Because of you we have to do this with a table. + ~# With Love, + ~# Mario. + %tr.phone-book-entry{:class => cycle('odd', 'even'), :"itemscope itemtype" => "http://schema.org/Person"} + %td.thumbnail + = image_tag(entry.image_url(:small).to_s, :itemprop => 'image') + %td.user + - if entry.is_organization == true + %a.name{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => "name"}= entry + - else + %a.name{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => "name"}= entry + %a.company{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => 'memberOf'}= entry.organization + %td.contact + - if @found_phone_numbers and @found_phone_numbers.where(:phone_numberable_id => entry.id) + %a.phone{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => 'telephone'}= @found_phone_numbers.where(:phone_numberable_id => entry.id).first + - elsif entry.phone_numbers.first + %a.phone{:href=> phone_book_phone_book_entry_path(entry.phone_book, entry), :itemprop => 'telephone'}= entry.phone_numbers.first + - if entry.phone_numbers.count > 1 + %a.more{:href => phone_book_phone_book_entry_path(entry.phone_book, entry)}= t('phone_book_entries.index.more_numbers', :numbers => (entry.phone_numbers.count-1)) + %td.extra + - if !entry.description.blank? + %strong Description: + %div= entry.description + - if can? :edit, entry + %td= link_to t('phone_book_entries.index.actions.edit'), edit_phone_book_phone_book_entry_path( entry.phone_book, entry ) + - if can? :destroy, entry + %td= link_to t('phone_book_entries.index.actions.destroy'), [entry.phone_book, entry], :confirm => t('phone_book_entries.index.actions.confirm'), :method => :delete + %footer.entries-nav= render :partial => "phone_book_entries/navigation" diff --git a/app/views/phone_book_entries/_navigation.html.haml b/app/views/phone_book_entries/_navigation.html.haml new file mode 100644 index 0000000..dd1e8a7 --- /dev/null +++ b/app/views/phone_book_entries/_navigation.html.haml @@ -0,0 +1,8 @@ +%nav + %ol.abc + - %w{# A B C D E F G H I J K L M N O P Q R S T U V W X Y Z}.each do |char| + %li + %a{ :href => "?name=#{char}" }= char + +.pagination + = will_paginate @phone_book_entries diff --git a/app/views/phone_book_entries/edit.html.haml b/app/views/phone_book_entries/edit.html.haml new file mode 100644 index 0000000..d4fad4d --- /dev/null +++ b/app/views/phone_book_entries/edit.html.haml @@ -0,0 +1,9 @@ +- title t("phone_book_entries.edit.page_title") + += render "form" + +%p + - if can? :edit, @phone_book_entry + = link_to t('phone_book_entries.edit.actions.edit'), @phone_book_entry + | + = link_to t('phone_book_entries.edit.actions.view_all'), phone_book_entries_path diff --git a/app/views/phone_book_entries/index.html.haml b/app/views/phone_book_entries/index.html.haml new file mode 100644 index 0000000..6a17eb9 --- /dev/null +++ b/app/views/phone_book_entries/index.html.haml @@ -0,0 +1,19 @@ +- title t("phone_book_entries.index.page_title") + +- if @phone_books + %p + = t('phone_book_entries.index.available_phone_books') + - @phone_books.each do |phone_book| + = link_to phone_book, phone_book + - if can?(:create, PhoneBookEntry, :phone_book_id => phone_book.id) + ( + = link_to "#{t('phone_book_entries.index.create_new_phone_book_entry')}", new_phone_book_phone_book_entry_path(phone_book) + ) + - if phone_book != @phone_books.last + \, + +- if @phone_book_entries.count > 0 + = render "index_core", :phone_book_entries => @phone_book_entries + +- if @phone_book + = render :partial => 'shared/create_link', :locals => {:parent => @phone_book, :child_class => PhoneBookEntry} \ No newline at end of file diff --git a/app/views/phone_book_entries/new.html.haml b/app/views/phone_book_entries/new.html.haml new file mode 100644 index 0000000..d72d1a4 --- /dev/null +++ b/app/views/phone_book_entries/new.html.haml @@ -0,0 +1,3 @@ +- title t("phone_book_entries.new.page_title") + += render "form" diff --git a/app/views/phone_book_entries/show.html.haml b/app/views/phone_book_entries/show.html.haml new file mode 100644 index 0000000..b6e8c6e --- /dev/null +++ b/app/views/phone_book_entries/show.html.haml @@ -0,0 +1,146 @@ +- title nil + +%section.phone-book-entry + .content + %header + %h1.username + %a= @phone_book_entry + - if !@phone_book_entry.organization.blank? + .work + %a= @phone_book_entry.organization + - if !@phone_book_entry.department.blank? + \/ + = @phone_book_entry.department + - if !@phone_book_entry.job_title.blank? + \/ + = @phone_book_entry.job_title + .personal + - if !@phone_book_entry.nickname.blank? + %span.nickname + a.k.a + %strong= @phone_book_entry.nickname + - if @phone_book_entry.birthday + %span.birthday + = l(@phone_book_entry.birthday) + .tags + %a= l @phone_book_entry.created_at.utc.getlocal, :format => :short + , + %a= l @phone_book_entry.updated_at.utc.getlocal, :format => :short + %section.activity + - if @user_log + %h2 User Log + - @user_log.each do |log_entry| + - if log_entry[:type] == 'voicemail' + .entry.voice-message + %span.motive + = log_entry[:text] + %span.timestamp + = log_entry[:timestamp] + - if log_entry[:type] == 'fax_document' + .entry.fax + %span.motive + = log_entry[:text] + %span.timestamp + = log_entry[:timestamp] + - if log_entry[:type] == 'call_placed' + .entry.phone + %span.motive + log_entry[:text] + %span.timestamp + = log_entry[:timestamp] + - elsif log_entry[:type] == 'call_received' + .entry.phone + %span.motive + = log_entry[:text] + %span.timestamp + = log_entry[:timestamp] + - elsif log_entry[:type] == 'call_missed' + .entry.phone-down + %span.motive + = log_entry[:text] + %span.timestamp + = log_entry[:timestamp] + + .sidebar + = image_tag @phone_book_entry.image_url(:profile).to_s, :class => 'display' + %p.description + = @phone_book_entry.description + .widget.phones + - @phone_book_entry.phone_numbers.each do |phone_number| + - case phone_number.name + - when /fax/ + .fax + %a= phone_number + %span= phone_number.name + - when /home/ + .home + %a= phone_number + %span= phone_number.name + - when /mobile/ + .cellphone + %a= phone_number + %span= phone_number.name + - when /office/ + .office + %a= phone_number + %span= phone_number.name + - else + .phone + %a= phone_number + %span= phone_number.name + = link_to t('phone_book_entries.show.manage_phone_numbers'), phone_book_entry_phone_numbers_path(@phone_book_entry) + + .widget.adresses + - @phone_book_entry.addresses.each do |address| + .home + %strong + - if !address.line1.blank? + = address.line1 + %br + - if !address.line2.blank? + = address.line1 + %br + - if !address.street.blank? + = address.street + %br + - if !address.city.blank? + = "#{address.city} #{address.zip_code}" + %br + - if !address.country.blank? + = address.country.to_s + %br + / %span Home + / .office + .widget.social + - if !@phone_book_entry.homepage_organization.blank? + .home + %a= @phone_book_entry.homepage_organization + %span www + - if !@phone_book_entry.homepage_personal.blank? + .home + %a= @phone_book_entry.homepage_personal + %span www + - if !@phone_book_entry.twitter_account.blank? + .twitter + %a= @phone_book_entry.twitter_account + %span Twitter + - if !@phone_book_entry.google_plus_account.blank? + .google_plus + %a= @phone_book_entry.google_plus_account + %span Google+ + - if !@phone_book_entry.facebook_account.blank? + .facebook + %a= @phone_book_entry.facebook_account + %span Facebook + - if !@phone_book_entry.xing_account.blank? + .xing + %a= @phone_book_entry.xing_account + %span Xing + - if !@phone_book_entry.linkedin_account.blank? + .linkedin + %a= @phone_book_entry.linkedin_account + %span LinkedIn + - if !@phone_book_entry.mobileme_account.blank? + .mobileme + %a= @phone_book_entry.mobileme_account + %span MobileMe diff --git a/app/views/phone_book_entries/show.html.haml.examlple b/app/views/phone_book_entries/show.html.haml.examlple new file mode 100644 index 0000000..176ad04 --- /dev/null +++ b/app/views/phone_book_entries/show.html.haml.examlple @@ -0,0 +1,194 @@ +- title nil + +%section.phone-book-entry + .content + %header + %h1.username + %a= @phone_book_entry + - if !@phone_book_entry.organization.blank? + .work + %a= @phone_book_entry.organization + - if !@phone_book_entry.department.blank? + \/ + = @phone_book_entry.department + - if !@phone_book_entry.job_title.blank? + \/ + = @phone_book_entry.job_title + .personal + - if !@phone_book_entry.nickname.blank? + %span.nickname + a.k.a + %strong= @phone_book_entry.nickname + - if @phone_book_entry.birthday + %span.birthday + = l(@phone_book_entry.birthday) + .tags + %a Developer + , + %a Worked With + , + %a Friend + %section.activity + %h2 User Log + = form_tag '/entry-whatever' do + %textarea{ :placeholder => "Leave a comment ..." } + .comment + = image_tag @phone_book_entry.image_url(:mini).to_s, :class => 'display' + .info + %span.commenter Random User + %span.time at 10:00 of Today + .body + I'm really tired and I still have to pack part of my baggage... + - 3.times do + .entry.voice-message + %span.motive + Stefan Wintermeyer left you a voice message 4 minutes ago. + %span.timestamp + 10:30 03/11/2011 + .entry.phone + %span.motive + Called Stefan Wintermeyer at + %a Work + with a duration of 5 minutes. + %span.timestamp + 10:30 03/11/2011 + .comment + = image_tag @phone_book_entry.image_url(:mini).to_s, :class => 'display' + .info + %span.commenter Random User + %span.time at 10:00 of Today + .body + I'm really tired and I still have to pack part of my baggage... + .entry.fax + %span.motive + Stefan Wintermeyer sent you a fax with 3 pages. + %span.timestamp + 10:30 03/11/2011 + + .entry.phone-down + %span.motive + Missed call from Stephan Wintermeyer. + %span.timestamp + 10:30 03/11/2011 + + .sidebar + = image_tag @phone_book_entry.image_url(:profile).to_s, :class => 'display' + %p.description + = @phone_book_entry.description + .widget.phones + - @phone_book_entry.phone_numbers.each do |phone_number| + - case phone_number.name + - when /fax/ + .fax + %a= phone_number + %span= phone_number.name + - when /home/ + .home + %a= phone_number + %span= phone_number.name + - when /mobile/ + .cellphone + %a= phone_number + %span= phone_number.name + - when /office/ + .office + %a= phone_number + %span= phone_number.name + - else + .phone + %a= phone_number + %span= phone_number.name + = link_to t('phone_book_entries.show.manage_phone_numbers'), phone_book_entry_phone_numbers_path(@phone_book_entry) + + .widget.adresses + - @phone_book_entry.addresses.each do |address| + .home + %strong + - if !address.line1.blank? + = address.line1 + %br + - if !address.line2.blank? + = address.line1 + %br + - if !address.street.blank? + = address.street + %br + - if !address.city.blank? + = "#{address.city} #{address.zip_code}" + %br + - if !address.country.blank? + = address.country.to_s + %br + / %span Home + / .office + .widget.social + - if !@phone_book_entry.homepage_organization.blank? + .home + %a= @phone_book_entry.homepage_organization + %span www + - if !@phone_book_entry.homepage_personal.blank? + .home + %a= @phone_book_entry.homepage_personal + %span www + - if !@phone_book_entry.twitter_account.blank? + .twitter + %a= @phone_book_entry.twitter_account + %span Twitter + - if !@phone_book_entry.google_plus_account.blank? + .google_plus + %a= @phone_book_entry.google_plus_account + %span Google+ + - if !@phone_book_entry.facebook_account.blank? + .facebook + %a= @phone_book_entry.facebook_account + %span Facebook + - if !@phone_book_entry.xing_account.blank? + .xing + %a= @phone_book_entry.xing_account + %span Xing + - if !@phone_book_entry.linkedin_account.blank? + .linkedin + %a= @phone_book_entry.linkedin_account + %span LinkedIn + - if !@phone_book_entry.mobileme_account.blank? + .mobileme + %a= @phone_book_entry.mobileme_account + %span MobileMe + +/ = debug @phone_book_entry.attributes + + +/ %p +/ %strong= t('phone_book_entries.show.first_name') + ":" +/ = @phone_book_entry.first_name +/ %p +/ %strong= t('phone_book_entries.show.middle_name') + ":" +/ = @phone_book_entry.middle_name +/ %p +/ %strong= t('phone_book_entries.show.last_name') + ":" +/ = @phone_book_entry.last_name +/ %p +/ %strong= t('phone_book_entries.show.title') + ":" +/ = @phone_book_entry.title +/ %p +/ %strong= t('phone_book_entries.show.nickname') + ":" +/ = @phone_book_entry.nickname +/ %p +/ %strong= t('phone_book_entries.show.organization') + ":" +/ = @phone_book_entry.organization +/ %p +/ %strong= t('phone_book_entries.show.department') + ":" +/ = @phone_book_entry.department +/ %p +/ %strong= t('phone_book_entries.show.job_title') + ":" +/ = @phone_book_entry.job_title +/ +/ %p +/ = link_to t('phone_book_entries.show.actions.edit'), edit_phone_book_phone_book_entry_path( @phone_book_entry.phone_book, @phone_book_entry ) +/ | +/ - if can? :destroy, @phone_book_entry +/ = link_to t('phone_book_entries.show.actions.destroy'), phone_book_phone_book_entry_path( @phone_book_entry.phone_book, @phone_book_entry ), :confirm => t('phone_book_entries.show.actions.confirm'), :method => :delete +/ +/ - if @phone_book_entry.phone_numbers.count > 0 +/ = render "phone_numbers/index_core", :phone_numbers => @phone_book_entry.phone_numbers +/ diff --git a/app/views/phone_books/_form.html.haml b/app/views/phone_books/_form.html.haml new file mode 100644 index 0000000..245426b --- /dev/null +++ b/app/views/phone_books/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([ @parent, @phone_book ]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('phone_books.form.submit') \ No newline at end of file diff --git a/app/views/phone_books/_form_core.html.haml b/app/views/phone_books/_form_core.html.haml new file mode 100644 index 0000000..0e36c4d --- /dev/null +++ b/app/views/phone_books/_form_core.html.haml @@ -0,0 +1,3 @@ +.inputs + = f.input :name, :label => t('phone_books.form.name.label'), :hint => conditional_hint('phone_books.form.name.hint'), :autofocus => true + = f.input :description, :label => t('phone_books.form.description.label'), :hint => conditional_hint('phone_books.form.description.hint') diff --git a/app/views/phone_books/_index_core.html.haml b/app/views/phone_books/_index_core.html.haml new file mode 100644 index 0000000..5f50675 --- /dev/null +++ b/app/views/phone_books/_index_core.html.haml @@ -0,0 +1,16 @@ +%table + %tr + %th= t('phone_books.index.name') + %th= t('phone_books.index.description') + %th= t('phone_books.index.count') + + - reset_cycle + - for phone_book in phone_books + %tr{:class => cycle('odd', 'even')} + %td= phone_book.name + %td= phone_book.description + %td + = number_with_delimiter( phone_book.phone_book_entries.count ) + = render :partial => 'shared/create_link', :locals => {:parent => phone_book, :child_class => PhoneBookEntry, :short_link => true} + + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => phone_book.phone_bookable, :child => phone_book} \ No newline at end of file diff --git a/app/views/phone_books/edit.html.haml b/app/views/phone_books/edit.html.haml new file mode 100644 index 0000000..36c945b --- /dev/null +++ b/app/views/phone_books/edit.html.haml @@ -0,0 +1,3 @@ +- title t("phone_books.edit.page_title", :resource => @phone_book) + += render "form" diff --git a/app/views/phone_books/index.html.haml b/app/views/phone_books/index.html.haml new file mode 100644 index 0000000..52b4e9b --- /dev/null +++ b/app/views/phone_books/index.html.haml @@ -0,0 +1,6 @@ +- title t("phone_books.index.page_title") + +- if @phone_books.count > 0 + = render "index_core", :phone_books => @phone_books + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => PhoneBook} \ No newline at end of file diff --git a/app/views/phone_books/new.html.haml b/app/views/phone_books/new.html.haml new file mode 100644 index 0000000..e96ce1e --- /dev/null +++ b/app/views/phone_books/new.html.haml @@ -0,0 +1,3 @@ +- title t("phone_books.new.page_title") + += render "form" \ No newline at end of file diff --git a/app/views/phone_books/show.html.haml b/app/views/phone_books/show.html.haml new file mode 100644 index 0000000..047b15e --- /dev/null +++ b/app/views/phone_books/show.html.haml @@ -0,0 +1,13 @@ +- title @phone_book +- if ! @phone_book.description.blank? + %p + %strong= t('phone_books.show.description') + ":" + = @phone_book.description + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @phone_book.phone_bookable, :child => @phone_book } + +%h2= t("phone_book_entries.index.page_title") +- if @phone_book_entries.count > 0 + = render "phone_book_entries/index_core", :phone_book_entries => @phone_book_entries + += render :partial => 'shared/create_link', :locals => {:parent => @phone_book, :child_class => PhoneBookEntry} \ No newline at end of file diff --git a/app/views/phone_models/_form.html.haml b/app/views/phone_models/_form.html.haml new file mode 100644 index 0000000..45c176f --- /dev/null +++ b/app/views/phone_models/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([ @manufacturer, @phone_model ]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('phone_models.form.submit') \ No newline at end of file diff --git a/app/views/phone_models/_form_core.html.haml b/app/views/phone_models/_form_core.html.haml new file mode 100644 index 0000000..b406d6e --- /dev/null +++ b/app/views/phone_models/_form_core.html.haml @@ -0,0 +1,4 @@ +.inputs + = f.input :name, :label => t('phone_models.form.name.label'), :hint => conditional_hint('phone_models.form.name.hint') + = f.input :product_manual_homepage_url, :label => t('phone_models.form.product_manual_homepage_url.label'), :hint => conditional_hint('phone_models.form.product_manual_homepage_url.hint') + = f.input :product_homepage_url, :label => t('phone_models.form.product_homepage_url.label'), :hint => conditional_hint('phone_models.form.product_homepage_url.hint') diff --git a/app/views/phone_models/_index_core.html.haml b/app/views/phone_models/_index_core.html.haml new file mode 100644 index 0000000..b07eb68 --- /dev/null +++ b/app/views/phone_models/_index_core.html.haml @@ -0,0 +1,19 @@ +%table + %tr + %th= t('phone_models.index.name') + %th= t('phone_models.index.product_manual_homepage_url') + %th= t('phone_models.index.product_homepage_url') + %th= t('phone_models.index.number_of_phones') + + - reset_cycle + - for phone_model in phone_models + %tr{:class => cycle('odd', 'even')} + %td= phone_model.name + %td + - if phone_model.product_manual_homepage_url + =link_to truncate(phone_model.product_manual_homepage_url, :length => 40), phone_model.product_manual_homepage_url + %td + - if phone_model.product_homepage_url + =link_to truncate(phone_model.product_homepage_url, :length => 40), phone_model.product_homepage_url + %td= phone_model.phones.count + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => phone_model, :parent => phone_model.manufacturer} \ No newline at end of file diff --git a/app/views/phone_models/edit.html.haml b/app/views/phone_models/edit.html.haml new file mode 100644 index 0000000..bf31ffc --- /dev/null +++ b/app/views/phone_models/edit.html.haml @@ -0,0 +1,3 @@ +- title t("phone_models.edit.page_title") + += render "form" diff --git a/app/views/phone_models/index.html.haml b/app/views/phone_models/index.html.haml new file mode 100644 index 0000000..90aa4ce --- /dev/null +++ b/app/views/phone_models/index.html.haml @@ -0,0 +1,6 @@ +- title t("phone_models.index.page_title") + +- if @phone_models.count > 0 + = render "index_core", :phone_models => @phone_models + += render :partial => 'shared/create_link', :locals => {:parent => @manufacturer, :child_class => PhoneModel} \ No newline at end of file diff --git a/app/views/phone_models/new.html.haml b/app/views/phone_models/new.html.haml new file mode 100644 index 0000000..9e900d4 --- /dev/null +++ b/app/views/phone_models/new.html.haml @@ -0,0 +1,3 @@ +- title t("phone_models.new.page_title") + += render "form" diff --git a/app/views/phone_models/show.html.haml b/app/views/phone_models/show.html.haml new file mode 100644 index 0000000..06fae4b --- /dev/null +++ b/app/views/phone_models/show.html.haml @@ -0,0 +1,18 @@ +- title t("phone_models.show.page_title") + +%p + %strong= t('phone_models.show.name') + ":" + = @phone_model.name +%p + %strong= t('phone_models.show.manufacturer_id') + ":" + = @phone_model.manufacturer +%p + %strong= t('phone_models.show.product_manual_homepage_url') + ":" + - if @phone_model.product_manual_homepage_url + =link_to @phone_model.product_manual_homepage_url, @phone_model.product_manual_homepage_url +%p + %strong= t('phone_models.show.product_homepage_url') + ":" + - if @phone_model.product_homepage_url + =link_to @phone_model.product_homepage_url, @phone_model.product_homepage_url + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @manufacturer, :child => @phone_model } \ No newline at end of file diff --git a/app/views/phone_number_ranges/_form.html.haml b/app/views/phone_number_ranges/_form.html.haml new file mode 100644 index 0000000..a86d45b --- /dev/null +++ b/app/views/phone_number_ranges/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([ @phone_number_range.phone_number_rangeable, @phone_number_range ]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('phone_number_ranges.form.submit') \ No newline at end of file diff --git a/app/views/phone_number_ranges/_form_core.html.haml b/app/views/phone_number_ranges/_form_core.html.haml new file mode 100644 index 0000000..f553f08 --- /dev/null +++ b/app/views/phone_number_ranges/_form_core.html.haml @@ -0,0 +1,3 @@ +.inputs + = f.input :name, :label => t('phone_number_ranges.form.name.label'), :hint => conditional_hint('phone_number_ranges.form.name.hint') + = f.input :description, :label => t('phone_number_ranges.form.description.label'), :hint => conditional_hint('phone_number_ranges.form.description.hint') diff --git a/app/views/phone_number_ranges/_index_core.html.haml b/app/views/phone_number_ranges/_index_core.html.haml new file mode 100644 index 0000000..24ea96d --- /dev/null +++ b/app/views/phone_number_ranges/_index_core.html.haml @@ -0,0 +1,21 @@ +%table + %tr + %th= t('phone_number_ranges.index.name') + %th= t('phone_number_ranges.index.description') + %th= t('phone_number_ranges.index.numbers') + %th= t('phone_number_ranges.index.amount') + + - reset_cycle + - for phone_number_range in phone_number_ranges + %tr{:class => cycle('odd', 'even')} + %td= t("phone_number_ranges.ranges.#{phone_number_range}.label") + %td= t("phone_number_ranges.ranges.#{phone_number_range}.description") + %td + - if phone_number_range.phone_numbers.count > 0 + = render 'phone_numbers/listing', :phone_numbers => phone_number_range.phone_numbers.order(:number) + %br + = render :partial => 'shared/create_link', :locals => {:parent => phone_number_range, :child_class => PhoneNumber, :short_link => true} + + %td= phone_number_range.phone_numbers.count + - if phone_number_range.phone_number_rangeable.class != Country + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => phone_number_range.phone_number_rangeable, :child => phone_number_range} \ No newline at end of file diff --git a/app/views/phone_number_ranges/edit.html.haml b/app/views/phone_number_ranges/edit.html.haml new file mode 100644 index 0000000..fbf6d12 --- /dev/null +++ b/app/views/phone_number_ranges/edit.html.haml @@ -0,0 +1,3 @@ +- title t("phone_number_ranges.edit.page_title", :resource => @phone_number_range) + += render "form" diff --git a/app/views/phone_number_ranges/index.html.haml b/app/views/phone_number_ranges/index.html.haml new file mode 100644 index 0000000..56cf137 --- /dev/null +++ b/app/views/phone_number_ranges/index.html.haml @@ -0,0 +1,6 @@ +- title t("phone_number_ranges.index.page_title") + +- if @phone_number_ranges.count > 0 + = render "index_core", :phone_number_ranges => @phone_number_ranges + += render :partial => 'shared/create_link', :locals => {:child_class => PhoneNumberRange} \ No newline at end of file diff --git a/app/views/phone_number_ranges/new.html.haml b/app/views/phone_number_ranges/new.html.haml new file mode 100644 index 0000000..d26b34d --- /dev/null +++ b/app/views/phone_number_ranges/new.html.haml @@ -0,0 +1,3 @@ +- title t("phone_number_ranges.new.page_title") + += render "form" diff --git a/app/views/phone_number_ranges/show.html.haml b/app/views/phone_number_ranges/show.html.haml new file mode 100644 index 0000000..64df556 --- /dev/null +++ b/app/views/phone_number_ranges/show.html.haml @@ -0,0 +1,18 @@ +- title t("phone_number_ranges.show.page_title") + +%p + %strong= t('phone_number_ranges.show.name') + ":" + = t("phone_number_ranges.ranges.#{@phone_number_range}.label") +%p + %strong= t('phone_number_ranges.show.description') + ":" + = @phone_number_range.description + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @phone_number_range.phone_number_rangeable, :child => @phone_number_range } + +%h2= t("phone_number_ranges.show.phone_numbers") + +- if @phone_number_range.try(:phone_numbers).try(:count).to_i > 0 + = render "phone_numbers/index_core", :phone_numbers => @phone_number_range.phone_numbers.order(:number) + +%p + = render :partial => 'shared/create_link', :locals => {:parent => @phone_number_range, :child_class => PhoneNumber} \ No newline at end of file diff --git a/app/views/phone_numbers/_form.html.haml b/app/views/phone_numbers/_form.html.haml new file mode 100644 index 0000000..2812e21 --- /dev/null +++ b/app/views/phone_numbers/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([ @parent, @phone_number ]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('phone_numbers.form.submit') diff --git a/app/views/phone_numbers/_form_core.html.haml b/app/views/phone_numbers/_form_core.html.haml new file mode 100644 index 0000000..add3039 --- /dev/null +++ b/app/views/phone_numbers/_form_core.html.haml @@ -0,0 +1,10 @@ +.inputs + + - if @phone_book_entry + = f.input :name, :collection => ['Office', 'Home', 'Mobile', 'Fax'], :include_blank => false, :label => t('phone_numbers.form.name.label'), :hint => conditional_hint('phone_numbers.form.name.hint') + = f.input :number, :label => t('phone_numbers.form.number.label'), :hint => conditional_hint('phone_numbers.form.number.hint') + - else + - if @callthrough || @hunt_group_member || @access_authorization || @current_user.current_tenant.array_of_available_internal_extensions_and_dids.count == 0 || @current_user.current_tenant.array_of_available_internal_extensions_and_dids.count > 250 + = f.input :number, :label => t('phone_numbers.form.number.label'), :hint => conditional_hint('phone_numbers.form.number.hint') + - else + = f.input :number, :collection => @current_user.current_tenant.array_of_available_internal_extensions_and_dids, :label => t('phone_numbers.form.number.label'), :hint => conditional_hint('phone_numbers.form.number.hint'), :include_blank => false diff --git a/app/views/phone_numbers/_index_core.html.haml b/app/views/phone_numbers/_index_core.html.haml new file mode 100644 index 0000000..06b27c8 --- /dev/null +++ b/app/views/phone_numbers/_index_core.html.haml @@ -0,0 +1,13 @@ +%table + %tr + - if phone_numbers.count > 1 && phone_numbers.first.phone_numberable_type == 'PhoneBookEntry' + %th= t('phone_numbers.index.name') + %th= t('phone_numbers.index.number') + + - reset_cycle + - for phone_number in phone_numbers.order(:position) + %tr{:class => cycle('odd', 'even')} + - if phone_number.phone_numberable_type == 'PhoneBookEntry' + %td= phone_number.name + %td= phone_number + = render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => phone_number.phone_numberable, :child => phone_number} diff --git a/app/views/phone_numbers/_listing.html.haml b/app/views/phone_numbers/_listing.html.haml new file mode 100644 index 0000000..ca002c5 --- /dev/null +++ b/app/views/phone_numbers/_listing.html.haml @@ -0,0 +1,8 @@ +- amount_of_phone_numbers = phone_numbers.count +- if amount_of_phone_numbers > 0 + - if amount_of_phone_numbers < 110 + = phone_numbers.map{|number| number}.join(', ') + - else + = phone_numbers.limit(30).map{|number| number}.join(', ') + ', ' + = '[...]' + = phone_numbers.offset(amount_of_phone_numbers - 30).map{|number| number}.join(', ') \ No newline at end of file diff --git a/app/views/phone_numbers/edit.html.haml b/app/views/phone_numbers/edit.html.haml new file mode 100644 index 0000000..d238d3d --- /dev/null +++ b/app/views/phone_numbers/edit.html.haml @@ -0,0 +1,3 @@ +- title t("phone_numbers.edit.page_title", :resource => "" ) + += render "form" \ No newline at end of file diff --git a/app/views/phone_numbers/index.html.haml b/app/views/phone_numbers/index.html.haml new file mode 100644 index 0000000..2161739 --- /dev/null +++ b/app/views/phone_numbers/index.html.haml @@ -0,0 +1,6 @@ +- title @parent + +- if @phone_numbers.count > 0 + = render "index_core", :phone_numbers => @phone_numbers + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => PhoneNumber} \ No newline at end of file diff --git a/app/views/phone_numbers/new.html.haml b/app/views/phone_numbers/new.html.haml new file mode 100644 index 0000000..e91f4f4 --- /dev/null +++ b/app/views/phone_numbers/new.html.haml @@ -0,0 +1,3 @@ +- title t("phone_numbers.new.page_title") + += render "form" diff --git a/app/views/phone_numbers/show.html.haml b/app/views/phone_numbers/show.html.haml new file mode 100644 index 0000000..30c48bc --- /dev/null +++ b/app/views/phone_numbers/show.html.haml @@ -0,0 +1,27 @@ +- title t("phone_numbers.show.page_title") + +- if @phone_number.phone_numberable.class == PhoneBookEntry + %p + %strong= t('phone_numbers.show.name') + ":" + = @phone_number.name + +%p + %strong= t('phone_numbers.show.number') + ":" + = @phone_number.to_s + +- if @ringtoneable_classes.has_key?(@phone_number.phone_numberable.class.to_s) + %p + %strong= t('ringtones.name') + ':' + - if @phone_number.ringtones.count > 0 + = link_to @phone_number.ringtones.first, phone_number_ringtone_path(@phone_number, @phone_number.ringtones.first) + - else + = link_to t('ringtones.set_a_ringtone'), new_phone_number_ringtone_path(@phone_number) + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @phone_number.phone_numberable, :child => @phone_number } + +- if @forwardable_classes.has_key?(@phone_number.phone_numberable.class.to_s) + %h3= t("call_forwards.index.page_title") + - if @phone_number.call_forwards.length > 0 + = render "call_forwards/index_core", :call_forwards => @phone_number.call_forwards + + = render :partial => 'shared/create_link', :locals => {:parent => @phone_number, :child_class => CallForward} \ No newline at end of file diff --git a/app/views/phone_sip_accounts/_form.html.haml b/app/views/phone_sip_accounts/_form.html.haml new file mode 100644 index 0000000..c2558b8 --- /dev/null +++ b/app/views/phone_sip_accounts/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@phone, @phone_sip_account]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('phone_sip_accounts.form.submit') \ No newline at end of file diff --git a/app/views/phone_sip_accounts/_form_core.html.haml b/app/views/phone_sip_accounts/_form_core.html.haml new file mode 100644 index 0000000..81f1121 --- /dev/null +++ b/app/views/phone_sip_accounts/_form_core.html.haml @@ -0,0 +1,2 @@ +.inputs + = f.association :sip_account, :collection => @available_sip_accounts, :label => t('phone_sip_accounts.form.sip_account_id.label'), :hint => conditional_hint('phone_sip_accounts.form.sip_account_id.hint'), :include_blank => false diff --git a/app/views/phone_sip_accounts/_index_core.html.haml b/app/views/phone_sip_accounts/_index_core.html.haml new file mode 100644 index 0000000..89afb2b --- /dev/null +++ b/app/views/phone_sip_accounts/_index_core.html.haml @@ -0,0 +1,13 @@ +%table + %tr + %th= t('phone_sip_accounts.index.phone_id') + %th= t('phone_sip_accounts.index.sip_account_id') + %th= t('phone_sip_accounts.index.position') + + - reset_cycle + - for phone_sip_account in phone_sip_accounts + %tr{:class => cycle('odd', 'even')} + %td= phone_sip_account.phone + %td= phone_sip_account.sip_account + %td= phone_sip_account.position + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => phone_sip_account.phone, :child => phone_sip_account} \ No newline at end of file diff --git a/app/views/phone_sip_accounts/index.html.haml b/app/views/phone_sip_accounts/index.html.haml new file mode 100644 index 0000000..a9e3f85 --- /dev/null +++ b/app/views/phone_sip_accounts/index.html.haml @@ -0,0 +1,6 @@ +- title t("phone_sip_accounts.index.page_title") + +- if @phone_sip_accounts.count > 0 + =render "index_core", :phone_sip_accounts => @phone_sip_accounts + += render :partial => 'shared/create_link', :locals => {:parent => @phone, :child_class => PhoneSipAccount} \ No newline at end of file diff --git a/app/views/phone_sip_accounts/new.html.haml b/app/views/phone_sip_accounts/new.html.haml new file mode 100644 index 0000000..bfe40b8 --- /dev/null +++ b/app/views/phone_sip_accounts/new.html.haml @@ -0,0 +1,3 @@ +- title t("phone_sip_accounts.new.page_title") + += render "form" diff --git a/app/views/phone_sip_accounts/show.html.haml b/app/views/phone_sip_accounts/show.html.haml new file mode 100644 index 0000000..0dd5a9b --- /dev/null +++ b/app/views/phone_sip_accounts/show.html.haml @@ -0,0 +1,13 @@ +- title t("phone_sip_accounts.show.page_title") + +%p + %strong= t('phone_sip_accounts.show.phone_id') + ":" + = @phone_sip_account.phone +%p + %strong= t('phone_sip_accounts.show.sip_account_id') + ":" + = @phone_sip_account.sip_account +%p + %strong= t('phone_sip_accounts.show.position') + ":" + = @phone_sip_account.position + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @phone, :child => @phone_sip_account } \ No newline at end of file diff --git a/app/views/phones/_form.html.haml b/app/views/phones/_form.html.haml new file mode 100644 index 0000000..9bfa226 --- /dev/null +++ b/app/views/phones/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([ @phoneable, @phone ]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('phones.form.submit') \ No newline at end of file diff --git a/app/views/phones/_form_core.html.haml b/app/views/phones/_form_core.html.haml new file mode 100644 index 0000000..51ceff5 --- /dev/null +++ b/app/views/phones/_form_core.html.haml @@ -0,0 +1,8 @@ +.inputs + = f.input :mac_address, :label => t('phones.form.mac_address.label'), :hint => conditional_hint('phones.form.mac_address.hint') + = f.association :phone_model, :label => t('phones.form.phone_model_id.label'), :hint => conditional_hint('phones.form.phone_model_id.hint') + = f.input :hot_deskable, :label => t('phones.form.hot_deskable.label'), :hint => conditional_hint('phones.form.hot_deskable.hint') + - if defined? NIGHTLY_REBOOT_OF_PHONES && NIGHTLY_REBOOT_OF_PHONES == true + = f.input :nightly_reboot, :label => t('phones.form.nightly_reboot.label'), :hint => conditional_hint('phones.form.nightly_reboot.hint') + - if defined? PROVISIONING_KEY_LENGTH && PROVISIONING_KEY_LENGTH > 0 + = f.input :provisioning_key_active, :label => t('phones.form.provisioning_key_active.label'), :hint => conditional_hint('phones.form.provisioning_key_active.hint') diff --git a/app/views/phones/_index_core.html.haml b/app/views/phones/_index_core.html.haml new file mode 100644 index 0000000..c442d7f --- /dev/null +++ b/app/views/phones/_index_core.html.haml @@ -0,0 +1,15 @@ +%table + %tr + %th= t('phones.index.mac_address') + %th= t('phones.index.phone_model_id') + %th= t('phones.index.hot_deskable') + %th= t('phones.index.ip_address') + + - reset_cycle + - for phone in phones + %tr{:class => cycle('odd', 'even')} + %td= phone.pretty_mac_address + %td= phone.phone_model + %td= phone.hot_deskable + %td= phone.ip_address + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => phone.phoneable, :child => phone} \ No newline at end of file diff --git a/app/views/phones/edit.html.haml b/app/views/phones/edit.html.haml new file mode 100644 index 0000000..cdbacac --- /dev/null +++ b/app/views/phones/edit.html.haml @@ -0,0 +1,3 @@ +- title t("phones.edit.page_title", :resource => @phone.mac_address) + += render "form" diff --git a/app/views/phones/index.html.haml b/app/views/phones/index.html.haml new file mode 100644 index 0000000..785adf9 --- /dev/null +++ b/app/views/phones/index.html.haml @@ -0,0 +1,6 @@ +- title t("phones.index.page_title") + +- if @phones.count > 0 + = render "index_core", :phones => @phones + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => Phone} \ No newline at end of file diff --git a/app/views/phones/new.html.haml b/app/views/phones/new.html.haml new file mode 100644 index 0000000..61923cc --- /dev/null +++ b/app/views/phones/new.html.haml @@ -0,0 +1,3 @@ +- title t("phones.new.page_title") + += render "form" diff --git a/app/views/phones/show.html.haml b/app/views/phones/show.html.haml new file mode 100644 index 0000000..2664ffa --- /dev/null +++ b/app/views/phones/show.html.haml @@ -0,0 +1,31 @@ +- title t("phones.show.page_title") + +%p + %strong= t('phones.show.mac_address') + ":" + = @phone.pretty_mac_address +%p + %strong= t('phones.show.phone_model_id') + ":" + = @phone.phone_model +%p + %strong= t('phones.show.hot_deskable') + ":" + = @phone.hot_deskable +- if defined? NIGHTLY_REBOOT_OF_PHONES && NIGHTLY_REBOOT_OF_PHONES == true + %p + %strong= t('phones.show.nightly_reboot') + ":" + = @phone.nightly_reboot + +- if defined? PROVISIONING_KEY_LENGTH && PROVISIONING_KEY_LENGTH > 0 + %p + %strong= t('phones.show.provisioning_key_active') + ":" + = @phone.provisioning_key_active +%p + %strong= t('phones.show.ip_address') + ":" + = @phone.ip_address + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @phone.phoneable, :child => @phone } + +%h2= t("phones.sip_accounts.title") +- if @phone.phone_sip_accounts.count > 0 + = render "phone_sip_accounts/index_core", :phone_sip_accounts => @phone.phone_sip_accounts + += render :partial => 'shared/create_link', :locals => {:parent => @phone, :child_class => PhoneSipAccount} diff --git a/app/views/ringtones/_form.html.haml b/app/views/ringtones/_form.html.haml new file mode 100644 index 0000000..7dbfcb0 --- /dev/null +++ b/app/views/ringtones/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@parent,@ringtone]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('ringtones.form.submit') \ No newline at end of file diff --git a/app/views/ringtones/_form_core.html.haml b/app/views/ringtones/_form_core.html.haml new file mode 100644 index 0000000..e44c950 --- /dev/null +++ b/app/views/ringtones/_form_core.html.haml @@ -0,0 +1,3 @@ +.inputs + / = f.input :audio, :label => t('ringtones.form.audio.label'), :hint => conditional_hint('ringtones.form.audio.hint') + = f.input :bellcore_id, :collection => 0..10, :label => t('ringtones.form.bellcore_id.label'), :hint => conditional_hint('ringtones.form.bellcore_id.hint'), :include_blank => true diff --git a/app/views/ringtones/_index_core.html.haml b/app/views/ringtones/_index_core.html.haml new file mode 100644 index 0000000..c39357a --- /dev/null +++ b/app/views/ringtones/_index_core.html.haml @@ -0,0 +1,11 @@ +%table + %tr + %th= t('ringtones.index.audio') + %th= t('ringtones.index.bellcore_id') + + - reset_cycle + - for ringtone in ringtones + %tr{:class => cycle('odd', 'even')} + %td= ringtone.audio + %td= ringtone.bellcore_id + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => ringtone.ringtoneable, :child => ringtone} \ No newline at end of file diff --git a/app/views/ringtones/edit.html.haml b/app/views/ringtones/edit.html.haml new file mode 100644 index 0000000..6779190 --- /dev/null +++ b/app/views/ringtones/edit.html.haml @@ -0,0 +1,3 @@ +- title t("ringtones.edit.page_title") + += render "form" diff --git a/app/views/ringtones/index.html.haml b/app/views/ringtones/index.html.haml new file mode 100644 index 0000000..4da75fa --- /dev/null +++ b/app/views/ringtones/index.html.haml @@ -0,0 +1,6 @@ +- title t("ringtones.index.page_title") + +- if @ringtones.count > 0 + = render "index_core", :ringtones => @ringtones + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => Ringtone} \ No newline at end of file diff --git a/app/views/ringtones/new.html.haml b/app/views/ringtones/new.html.haml new file mode 100644 index 0000000..025f440 --- /dev/null +++ b/app/views/ringtones/new.html.haml @@ -0,0 +1,3 @@ +- title t("ringtones.new.page_title") + += render "form" diff --git a/app/views/ringtones/show.html.haml b/app/views/ringtones/show.html.haml new file mode 100644 index 0000000..408b808 --- /dev/null +++ b/app/views/ringtones/show.html.haml @@ -0,0 +1,12 @@ +- title t("ringtones.show.page_title") + +- if 1 == 2 + %p + %strong= t('ringtones.show.audio') + ":" + = @ringtone.audio + +%p + %strong= t('ringtones.show.bellcore_id') + ":" + = @ringtone.bellcore_id + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @ringtone.ringtoneable, :child => @ringtone } \ No newline at end of file diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml new file mode 100644 index 0000000..8ad77b1 --- /dev/null +++ b/app/views/sessions/new.html.haml @@ -0,0 +1,8 @@ +- title t("sessions.new.page_title") + += simple_form_for :sessions, :url => sessions_path do |t| + = t.input :login_data, :label => t('sessions.form.email'), :autofocus => true + = t.input :password, :label => t('sessions.form.password'), :required => false + = t.input :reset_password, :label => t('sessions.form.reset_password'), :as => :boolean + .actions + = t.button :submit, :value => 'Login' diff --git a/app/views/shared/_create_link.html.haml b/app/views/shared/_create_link.html.haml new file mode 100644 index 0000000..103c82b --- /dev/null +++ b/app/views/shared/_create_link.html.haml @@ -0,0 +1,11 @@ +- if !(defined? parent).nil? && !(defined? child_class).nil? + - if can? :create, parent.send(child_class.name.underscore.pluralize).build + %p + - if t("#{child_class.name.underscore.pluralize}.index.actions.create_for").include?('translation missing') || (!(defined? short_link).nil? && short_link == true) + = link_to t("#{child_class.name.underscore.pluralize}.index.actions.create"), method( :"new_#{parent.class.name.underscore}_#{child_class.name.underscore}_path" ).(parent) + - else + = link_to t("#{child_class.name.underscore.pluralize}.index.actions.create_for", :resource => parent.to_s), method( :"new_#{parent.class.name.underscore}_#{child_class.name.underscore}_path" ).(parent) +- elsif !(defined? child_class).nil? + - if can? :create, child_class + %p + = link_to t("#{child_class.name.underscore.pluralize}.index.actions.create"), method( :"new_#{child_class.name.underscore}_path" ).() \ No newline at end of file diff --git a/app/views/shared/_flash.html.haml b/app/views/shared/_flash.html.haml new file mode 100644 index 0000000..320fd15 --- /dev/null +++ b/app/views/shared/_flash.html.haml @@ -0,0 +1,19 @@ +- flash.each do |type, msg| + .flash{:class => type} + .light + .sign= resolve_flash_sign(type) + .message= msg + + +-# These are the available types: +-# +-# .flash.notice +-# .light +-# .sign i +-# .message Lorem ipsum dolor sit amet, consectetur adipisicing eli.w +-# +-# .flash.warning +-# .light +-# .sign ! +-# .message Lorem ipsum dolor sit amet, consectetur adipisicing eli.w + diff --git a/app/views/shared/_header.de.html.haml b/app/views/shared/_header.de.html.haml new file mode 100644 index 0000000..c6205ae --- /dev/null +++ b/app/views/shared/_header.de.html.haml @@ -0,0 +1,41 @@ +%header#main + .light + %h1.gemeinschaft-logo + - if @current_user && @current_user.current_tenant + = link_to "Gemeinschaft", tenant_path(@current_user.current_tenant) + - else + = link_to "Gemeinschaft", root_url + + - if current_user + = form_tag '/search' do + %div.search-box + - if GuiFunction.display?('search_field_in_top_navigation_bar', current_user) + %input.text{:value => 'Suchen ...', :name => 'q'} + %input{:type => 'submit', :value => ''} + + / Adjustable Navigation. + - if current_user + - if navigation_items.size > 0 + - navigation_items.each do |item| + - if GuiFunction.display?('navigation_items_in_top_navigation_bar', current_user) + %span + = link_to item[:title], item[:url] + + - if current_user + .user-context + %a.user{:href => tenant_user_path(current_user.current_tenant.id, current_user.id)} + - if GuiFunction.display?('user_avatar_in_top_navigation_bar', current_user) + - if current_user.image? && current_user.image_url(:mini) + = image_tag current_user.image_url(:mini).to_s, :class => 'display' + - else + - if current_user.male? + = image_tag 'icons/user-male-16x.png', :class => 'display logged-out' + - else + = image_tag 'icons/user-female-16x.png', :class => 'display logged-out' + = current_user + = link_to( "[x]", log_out_path, :class => 'logout', :title => "Abmelden" ) # Temporary way of logging out. + - else + .user-context + = link_to "Registrieren", sign_up_path + oder + = link_to "Anmelden", log_in_path diff --git a/app/views/shared/_header.html.haml b/app/views/shared/_header.html.haml new file mode 100644 index 0000000..377d8e0 --- /dev/null +++ b/app/views/shared/_header.html.haml @@ -0,0 +1,41 @@ +%header#main + .light + %h1.gemeinschaft-logo + - if @current_user && @current_user.current_tenant + = link_to "Gemeinschaft", tenant_path(@current_user.current_tenant) + - else + = link_to "Gemeinschaft", root_url + + - if current_user + = form_tag '/search' do + %div.search-box + - if GuiFunction.display?('search_field_in_top_navigation_bar', current_user) + %input.text{:value => 'Search ...', :name => 'q'} + %input{:type => 'submit', :value => ''} + + / Adjustable Navigation. + - if current_user + - if navigation_items.size > 0 + - navigation_items.each do |item| + - if GuiFunction.display?('navigation_items_in_top_navigation_bar', current_user) + %span + = link_to item[:title], item[:url] + + - if current_user + .user-context + %a.user{:href => tenant_user_path(current_user.current_tenant.id, current_user.id)} + - if GuiFunction.display?('user_avatar_in_top_navigation_bar', current_user) + - if current_user.image? && current_user.image_url(:mini) + = image_tag current_user.image_url(:mini).to_s, :class => 'display' + - else + - if current_user.male? + = image_tag 'icons/user-male-16x.png', :class => 'display logged-out' + - else + = image_tag 'icons/user-female-16x.png', :class => 'display logged-out' + = current_user + = link_to( "[x]", log_out_path, :class => 'logout', :title => "Log out" ) # Temporary way of logging out. + - else + .user-context + = link_to "Sign up", sign_up_path + or + = link_to "Log in", log_in_path diff --git a/app/views/shared/_index_view_edit_destroy_part.html.haml b/app/views/shared/_index_view_edit_destroy_part.html.haml new file mode 100644 index 0000000..06ec904 --- /dev/null +++ b/app/views/shared/_index_view_edit_destroy_part.html.haml @@ -0,0 +1,29 @@ +- style = 'width:35px' + +- if !(defined? parent).nil? && !(defined? child).nil? + %td{ :style => style } + - if can? :show, child + = link_to t("#{child.class.name.underscore.pluralize}.index.actions.show"), method( :"#{parent.class.name.underscore}_#{child.class.name.underscore}_path" ).(parent, child) + %td{ :style => style } + - if can? :edit, child + = link_to t("#{child.class.name.underscore.pluralize}.index.actions.edit"), method( :"edit_#{parent.class.name.underscore}_#{child.class.name.underscore}_path" ).(parent, child) + %td{ :style => style } + - if can? :destroy, child + = link_to t("#{child.class.name.underscore.pluralize}.index.actions.destroy"), method( :"#{parent.class.name.underscore}_#{child.class.name.underscore}_path" ).(parent, child), :method => :delete + - if child.respond_to?(:move_up?) or child and child.respond_to?(:move_down?) + %td{ :style => style } + - if can? :move_down, child and child.respond_to?(:move_down?) and child.move_down? + = link_to '⇩'.html_safe, method( :"move_lower_#{parent.class.name.underscore}_#{child.class.name.underscore}_path" ).(parent, child), :method => :put + - if can? :move_up, child and child.respond_to?(:move_up?) and child.move_up? + = link_to '⇧'.html_safe, method( :"move_higher_#{parent.class.name.underscore}_#{child.class.name.underscore}_path" ).(parent, child), :method => :put + +- elsif !(defined? child).nil? + %td{ :style => style } + - if can? :show, child + = link_to t("#{child.class.name.underscore.pluralize}.index.actions.show"), method( :"#{child.class.name.underscore}_path" ).(child) + %td{ :style => style } + - if can? :edit, child + = link_to t("#{child.class.name.underscore.pluralize}.index.actions.edit"), method( :"edit_#{child.class.name.underscore}_path" ).(child) + %td{ :style => style } + - if can? :destroy, child + = link_to t("#{child.class.name.underscore.pluralize}.index.actions.destroy"), method( :"#{child.class.name.underscore}_path" ).(child), :method => :delete \ No newline at end of file diff --git a/app/views/shared/_show_edit_destroy_part.html.haml b/app/views/shared/_show_edit_destroy_part.html.haml new file mode 100644 index 0000000..aff18d1 --- /dev/null +++ b/app/views/shared/_show_edit_destroy_part.html.haml @@ -0,0 +1,16 @@ +%p + - if !(defined? parent).nil? && !(defined? child).nil? + - if can? :edit, child + = link_to t("#{child.class.name.underscore.pluralize}.show.actions.edit"), method( :"edit_#{parent.class.name.underscore}_#{child.class.name.underscore}_path" ).(parent, child) + - if can? :destroy, child + - if can? :edit, child + | + = link_to t("#{child.class.name.underscore.pluralize}.show.actions.destroy"), method( :"#{parent.class.name.underscore}_#{child.class.name.underscore}_path" ).(parent, child), :method => :delete + + - elsif !(defined? child).nil? + - if can? :edit, child + = link_to t("#{child.class.name.underscore.pluralize}.show.actions.edit"), method( :"edit_#{child.class.name.underscore}_path" ).(child) + - if can? :destroy, child + - if can? :edit, child + | + = link_to t("#{child.class.name.underscore.pluralize}.show.actions.destroy"), method( :"#{child.class.name.underscore}_path" ).(child), :method => :delete \ No newline at end of file diff --git a/app/views/shared/_system_message.html.haml b/app/views/shared/_system_message.html.haml new file mode 100644 index 0000000..4aabb9c --- /dev/null +++ b/app/views/shared/_system_message.html.haml @@ -0,0 +1,10 @@ +- if current_user + .flash.notice#system_message_display + .light + .sign i + .message#system_message This is the place to display incoming calls and other stuff. + + = subscribe_to "/users/#{current_user.id}/system_messages" + + :javascript + $('#system_message_display').hide() \ No newline at end of file diff --git a/app/views/sip_accounts/_form.html.haml b/app/views/sip_accounts/_form.html.haml new file mode 100644 index 0000000..f209bf4 --- /dev/null +++ b/app/views/sip_accounts/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([ @parent, @sip_account ]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('sip_accounts.form.submit') \ No newline at end of file diff --git a/app/views/sip_accounts/_form_core.html.haml b/app/views/sip_accounts/_form_core.html.haml new file mode 100644 index 0000000..dbd27fe --- /dev/null +++ b/app/views/sip_accounts/_form_core.html.haml @@ -0,0 +1,12 @@ +.inputs + = f.input :auth_name, :as => :string, :label => t('sip_accounts.form.auth_name.label'), :hint => conditional_hint('sip_accounts.form.auth_name.hint') + = f.input :password, :as => :string, :label => t('sip_accounts.form.password.label'), :hint => conditional_hint('sip_accounts.form.password.hint') + = f.input :caller_name, :as => :string, :label => t('sip_accounts.form.caller_name.label'), :hint => conditional_hint('sip_accounts.form.caller_name.hint') + = f.input :voicemail_pin, :as => :string, :label => t('sip_accounts.form.voicemail_pin.label'), :hint => conditional_hint('sip_accounts.form.voicemail_pin.hint') + = f.input :call_waiting, :label => t('sip_accounts.form.call_waiting.label'), :hint => conditional_hint('sip_accounts.form.call_waiting.hint') + = f.input :clir, :label => t('sip_accounts.form.clir.label'), :hint => conditional_hint('sip_accounts.form.clir.hint') + = f.input :clip, :label => t('sip_accounts.form.clip.label'), :hint => conditional_hint('sip_accounts.form.clip.hint') + = f.input :hotdeskable, :label => t('sip_accounts.form.hotdeskable.label'), :hint => conditional_hint('sip_accounts.form.hotdeskable.hint') + = f.input :clip_no_screening, :label => t('sip_accounts.form.clip_no_screening.label'), :hint => conditional_hint('sip_accounts.form.clip_no_screening.hint') + - if CallForward.where(:phone_number_id => @sip_account.phone_number_ids).count == 0 || @sip_account.callforward_rules_act_per_sip_account == true + = f.input :callforward_rules_act_per_sip_account, :label => t('sip_accounts.form.callforward_rules_act_per_sip_account.label'), :hint => conditional_hint('sip_accounts.form.callforward_rules_act_per_sip_account.hint') diff --git a/app/views/sip_accounts/_index_core.html.haml b/app/views/sip_accounts/_index_core.html.haml new file mode 100644 index 0000000..7f8dcd2 --- /dev/null +++ b/app/views/sip_accounts/_index_core.html.haml @@ -0,0 +1,28 @@ +%table + %tr + %th= t('sip_accounts.index.online') + %th= t('sip_accounts.index.caller_name') + %th= t('sip_accounts.index.phone_numbers') + %th= t('phones.name') + + - reset_cycle + - for sip_account in sip_accounts + %tr{:class => cycle('odd', 'even')} + %td + - if sip_account.registration + %img{:src => '/assets/icons/phone-down-green-32x.png'} + - else + %img{:src => '/assets/icons/phone-down-grey-32x.png'} + %td + = sip_account.caller_name + - phone_numbers = sip_account.phone_numbers + %td + - if sip_account.phone_numbers.count > 0 + = render 'phone_numbers/listing', :phone_numbers => sip_account.phone_numbers.order(:number) + %br + = render :partial => 'shared/create_link', :locals => {:parent => sip_account, :child_class => PhoneNumber, :short_link => true} + + %td + - sip_account.phones.each do |phone| + = link_to phone.to_s, method( :"#{phone.phoneable_type.underscore}_phone_path" ).( phone.phoneable_id, phone ) + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => sip_account.sip_accountable, :child => sip_account} \ No newline at end of file diff --git a/app/views/sip_accounts/edit.html.haml b/app/views/sip_accounts/edit.html.haml new file mode 100644 index 0000000..c070ff7 --- /dev/null +++ b/app/views/sip_accounts/edit.html.haml @@ -0,0 +1,3 @@ +- title t("sip_accounts.edit.page_title") + += render "form" diff --git a/app/views/sip_accounts/index.html.haml b/app/views/sip_accounts/index.html.haml new file mode 100644 index 0000000..1131770 --- /dev/null +++ b/app/views/sip_accounts/index.html.haml @@ -0,0 +1,6 @@ +- title t("sip_accounts.index.page_title") + +- if @sip_accounts.count > 0 + = render "index_core", :sip_accounts => @sip_accounts + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => SipAccount} \ No newline at end of file diff --git a/app/views/sip_accounts/new.html.haml b/app/views/sip_accounts/new.html.haml new file mode 100644 index 0000000..9d44680 --- /dev/null +++ b/app/views/sip_accounts/new.html.haml @@ -0,0 +1,3 @@ +- title t("sip_accounts.new.page_title") + += render "form" diff --git a/app/views/sip_accounts/show.html.haml b/app/views/sip_accounts/show.html.haml new file mode 100644 index 0000000..c6344cd --- /dev/null +++ b/app/views/sip_accounts/show.html.haml @@ -0,0 +1,50 @@ +- title t("sip_accounts.show.page_title") + +%p + %strong= t('sip_accounts.show.auth_name') + ":" + = @sip_account.auth_name +%p + %strong= t('sip_accounts.show.caller_name') + ":" + = @sip_account.caller_name +%p + %strong= t('sip_accounts.show.password') + ":" + = @sip_account.password +%p + %strong= t('sip_accounts.show.call_waiting') + ":" + = @sip_account.call_waiting +%p + %strong= t('sip_accounts.show.clir') + ":" + = @sip_account.clir +%p + %strong= t('sip_accounts.show.clip_no_screening') + ":" + = @sip_account.clip_no_screening +%p + %strong= t('sip_accounts.show.hotdeskable') + ":" + = @sip_account.hotdeskable +%p + %strong= t('sip_accounts.show.callforward_rules_act_per_sip_account') + ":" + = @sip_account.callforward_rules_act_per_sip_account +- if @sip_account.registration.try(:network_ip) && @sip_account.registration.try(:network_port) + %p + %strong= t('sip_accounts.show.registration') + ":" + = "#{@sip_account.registration.network_ip}:#{@sip_account.registration.network_port}" +- if @sip_account.registration.try(:expires) + %p + %strong= t('sip_accounts.show.expires') + ":" + = "#{@sip_account.registration.try(:expires) - Time.now.to_i} s" + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @sip_account.sip_accountable, :child => @sip_account } + +- if @sip_account.phone_numbers.count > 0 || can?(:create, @sip_account.phone_numbers.build) + %h2= t('phone_numbers.index.page_title') + - if @sip_account.phone_numbers.count > 0 + = render "phone_numbers/index_core", :phone_numbers => @sip_account.phone_numbers + %br + = render :partial => 'shared/create_link', :locals => { :parent => @sip_account, :child_class => PhoneNumber } + +- if @sip_account.softkeys.count > 0 || can?(:create, @sip_account.softkeys.build) + %h2= t("softkeys.index.page_title") + - if @sip_account.softkeys.count > 0 + = render "softkeys/index_core", :softkeys => @sip_account.softkeys + %br + = render :partial => 'shared/create_link', :locals => { :parent => @sip_account, :child_class => Softkey } \ No newline at end of file diff --git a/app/views/sip_domains/_form.html.haml b/app/views/sip_domains/_form.html.haml new file mode 100644 index 0000000..2d662af --- /dev/null +++ b/app/views/sip_domains/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for(@sip_domain) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('sip_domains.form.submit') \ No newline at end of file diff --git a/app/views/sip_domains/_form_core.html.haml b/app/views/sip_domains/_form_core.html.haml new file mode 100644 index 0000000..a7f024f --- /dev/null +++ b/app/views/sip_domains/_form_core.html.haml @@ -0,0 +1,3 @@ +.inputs + = f.input :host, :label => t('sip_domains.form.host.label'), :hint => conditional_hint('sip_domains.form.host.hint') + = f.input :realm, :label => t('sip_domains.form.realm.label'), :hint => conditional_hint('sip_domains.form.realm.hint') diff --git a/app/views/sip_domains/_index_core.html.haml b/app/views/sip_domains/_index_core.html.haml new file mode 100644 index 0000000..37374f2 --- /dev/null +++ b/app/views/sip_domains/_index_core.html.haml @@ -0,0 +1,11 @@ +%table + %tr + %th= t('sip_domains.index.host') + %th= t('sip_domains.index.realm') + + - reset_cycle + - for sip_domain in sip_domains + %tr{:class => cycle('odd', 'even')} + %td= sip_domain.host + %td= sip_domain.realm + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => sip_domain} \ No newline at end of file diff --git a/app/views/sip_domains/edit.html.haml b/app/views/sip_domains/edit.html.haml new file mode 100644 index 0000000..dcf8d6b --- /dev/null +++ b/app/views/sip_domains/edit.html.haml @@ -0,0 +1,3 @@ +- title t("sip_domains.edit.page_title") + += render "form" diff --git a/app/views/sip_domains/index.html.haml b/app/views/sip_domains/index.html.haml new file mode 100644 index 0000000..6de7f42 --- /dev/null +++ b/app/views/sip_domains/index.html.haml @@ -0,0 +1,6 @@ +- title t("sip_domains.index.page_title") + +- if @sip_domains.count > 0 + = render "index_core", :sip_domains => @sip_domains + += render :partial => 'shared/create_link', :locals => {:child_class => SipDomain} \ No newline at end of file diff --git a/app/views/sip_domains/new.html.haml b/app/views/sip_domains/new.html.haml new file mode 100644 index 0000000..12ff340 --- /dev/null +++ b/app/views/sip_domains/new.html.haml @@ -0,0 +1,3 @@ +- title t("sip_domains.new.page_title") + += render "form" diff --git a/app/views/sip_domains/show.html.haml b/app/views/sip_domains/show.html.haml new file mode 100644 index 0000000..e136eaf --- /dev/null +++ b/app/views/sip_domains/show.html.haml @@ -0,0 +1,10 @@ +- title t("sip_domains.show.page_title") + +%p + %strong= t('sip_domains.show.host') + ":" + = @sip_domain.host +%p + %strong= t('sip_domains.show.realm') + ":" + = @sip_domain.realm + += render :partial => 'shared/show_edit_destroy_part', :locals => { :child => @sip_domain } \ No newline at end of file diff --git a/app/views/softkeys/_form.html.haml b/app/views/softkeys/_form.html.haml new file mode 100644 index 0000000..5b799b6 --- /dev/null +++ b/app/views/softkeys/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@sip_account, @softkey]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('softkeys.form.submit') \ No newline at end of file diff --git a/app/views/softkeys/_form_core.html.haml b/app/views/softkeys/_form_core.html.haml new file mode 100644 index 0000000..b833aad --- /dev/null +++ b/app/views/softkeys/_form_core.html.haml @@ -0,0 +1,12 @@ +%script{:type => "text/javascript"} + :plain + call_forwarding_function_name = "#{I18n.t('softkeys.functions.call_forwarding')}" + hold_function_name = "#{I18n.t('softkeys.functions.hold')}" + deactivated_function_name = "#{I18n.t('softkeys.functions.deactivated')}" + +.inputs + = f.input :softkey_function_id, :as => :select, :collection => @softkey_functions.map {|x| [I18n.t("softkeys.functions.#{x}"), x.id] }, :label => t('softkeys.form.function.label'), :hint => conditional_hint('softkeys.form.function.hint'), :include_blank => false + - if @available_call_forwards && @available_call_forwards.count > 0 + = f.association :call_forward, :collection => @available_call_forwards, :label => t('softkeys.form.call_forward.label'), :hint => conditional_hint('softkeys.form.call_forward.hint'), :include_blank => false + = f.input :number, :label => t('softkeys.form.number.label'), :hint => conditional_hint('softkeys.form.number.hint') + = f.input :label, :label => t('softkeys.form.label.label'), :hint => conditional_hint('softkeys.form.label.hint') diff --git a/app/views/softkeys/_index_core.html.haml b/app/views/softkeys/_index_core.html.haml new file mode 100644 index 0000000..fd3dca8 --- /dev/null +++ b/app/views/softkeys/_index_core.html.haml @@ -0,0 +1,14 @@ +%table + %tr + %th= t('softkeys.index.function') + %th= t('softkeys.index.number') + %th= t('softkeys.index.label') + + - reset_cycle + - for softkey in softkeys.order(:position) + %tr{:class => cycle('odd', 'even')} + %td + =softkey.to_s + %td= softkey.number + %td= softkey.label + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => softkey.sip_account, :child => softkey} \ No newline at end of file diff --git a/app/views/softkeys/edit.html.haml b/app/views/softkeys/edit.html.haml new file mode 100644 index 0000000..54d53fc --- /dev/null +++ b/app/views/softkeys/edit.html.haml @@ -0,0 +1,3 @@ +- title t("softkeys.edit.page_title") + += render "form" \ No newline at end of file diff --git a/app/views/softkeys/index.html.haml b/app/views/softkeys/index.html.haml new file mode 100644 index 0000000..8bdc00e --- /dev/null +++ b/app/views/softkeys/index.html.haml @@ -0,0 +1,6 @@ +- title t("softkeys.index.page_title") + +- if @softkeys.count > 0 + = render "index_core", :softkeys => @softkeys + += render :partial => 'shared/create_link', :locals => {:parent => @sip_account, :child_class => Softkey} \ No newline at end of file diff --git a/app/views/softkeys/new.html.haml b/app/views/softkeys/new.html.haml new file mode 100644 index 0000000..593add6 --- /dev/null +++ b/app/views/softkeys/new.html.haml @@ -0,0 +1,3 @@ +- title t("softkeys.new.page_title") + += render "form" diff --git a/app/views/softkeys/show.html.haml b/app/views/softkeys/show.html.haml new file mode 100644 index 0000000..9ab4333 --- /dev/null +++ b/app/views/softkeys/show.html.haml @@ -0,0 +1,7 @@ +- title t("softkeys.show.page_title") + +%p + %strong= t('softkeys.show.function') + ":" + =@softkey.to_s + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @softkey.sip_account, :child => @softkey } \ No newline at end of file diff --git a/app/views/system_messages/_form.html.haml b/app/views/system_messages/_form.html.haml new file mode 100644 index 0000000..036ee00 --- /dev/null +++ b/app/views/system_messages/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@user, @system_message]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('system_messages.form.submit') \ No newline at end of file diff --git a/app/views/system_messages/_form_core.html.haml b/app/views/system_messages/_form_core.html.haml new file mode 100644 index 0000000..a85db28 --- /dev/null +++ b/app/views/system_messages/_form_core.html.haml @@ -0,0 +1,2 @@ +.inputs + = f.input :content, :label => t('system_messages.form.content.label'), :hint => conditional_hint('system_messages.form.content.hint') diff --git a/app/views/system_messages/_index_core.html.haml b/app/views/system_messages/_index_core.html.haml new file mode 100644 index 0000000..157d964 --- /dev/null +++ b/app/views/system_messages/_index_core.html.haml @@ -0,0 +1,11 @@ +%table + %tr + %th= t('system_messages.index.created_at') + %th= t('system_messages.index.content') + + - reset_cycle + - for system_message in system_messages + %tr{:class => cycle('odd', 'even')} + %td= system_message.created_at + %td= system_message.content + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => system_message} \ No newline at end of file diff --git a/app/views/system_messages/index.html.haml b/app/views/system_messages/index.html.haml new file mode 100644 index 0000000..da77e18 --- /dev/null +++ b/app/views/system_messages/index.html.haml @@ -0,0 +1,3 @@ +- title t("system_messages.index.page_title") + += render "index_core", :system_messages => @system_messages \ No newline at end of file diff --git a/app/views/system_messages/new.html.haml b/app/views/system_messages/new.html.haml new file mode 100644 index 0000000..3afdb24 --- /dev/null +++ b/app/views/system_messages/new.html.haml @@ -0,0 +1,3 @@ +- title t("system_messages.new.page_title") + += render "form" diff --git a/app/views/system_messages/show.html.haml b/app/views/system_messages/show.html.haml new file mode 100644 index 0000000..694e4c1 --- /dev/null +++ b/app/views/system_messages/show.html.haml @@ -0,0 +1,8 @@ +- title t("system_messages.show.page_title") + +%p + %strong= t('system_messages.show.created_at') + ":" + = @system_message.created_at +%p + %strong= t('system_messages.show.content') + ":" + = @system_message.content diff --git a/app/views/tenants/_admin_area.de.html.haml b/app/views/tenants/_admin_area.de.html.haml new file mode 100644 index 0000000..b9b47d5 --- /dev/null +++ b/app/views/tenants/_admin_area.de.html.haml @@ -0,0 +1,118 @@ +%p + Sie sind Mitglied der + = link_to 'Admin Gruppe', tenant_user_group_path(@tenant, @tenant.user_groups.find_by_name('Admins')) + und haben deshalb besondere Rechte. Aber wie Peter Parker schon sagte: "With great power comes great responsibility." + +%p + Dieser Mandant hat + = link_to pluralize(@tenant.user_groups.count, 'user group'), tenant_user_groups_path(@tenant) + - if @tenant.user_groups.count < 5 + = "(#{@tenant.user_groups.order(:name).map{|group| group.to_s }.join(', ')})" + die in Summe + = link_to pluralize(@tenant.users.count, 'user'), tenant_users_path(@tenant) + verwalten. + Das System kann + = PhoneModel.count + verschiedene Telefonmodelle von den folgenden Herstellern verwalten: + - Manufacturer.all.each do |manufacturer| + - if manufacturer != Manufacturer.last && manufacturer != Manufacturer.limit(Manufacturer.count - 1).last + = succeed ', ' do + =link_to manufacturer, manufacturer_path(manufacturer) + - elsif manufacturer == Manufacturer.limit(Manufacturer.count - 1).last + = succeed ' und ' do + =link_to manufacturer, manufacturer_path(manufacturer) + - else + = succeed '.' do + =link_to manufacturer, manufacturer_path(manufacturer) + +%h3 SIP-Konten und Telefone + +%table + %tr{:class => 'even'} + %th + %th + = @tenant + %th + Alle Benutzer von + = "\"#{@tenant}\"" + %tr{:class => 'odd'} + %td + SIP-Konten + %td + = link_to @tenant.sip_accounts.count.to_s, tenant_sip_accounts_path(@tenant) + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => SipAccount} + %td= @tenant.users_sip_accounts.count.to_s + %tr{:class => 'even'} + %td + Telefone + %td + = link_to @tenant.phones.count.to_s, tenant_phones_path(@tenant) + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => Phone} + %td= @tenant.users_phones.count.to_s + +%h3 Allgemein + +%table + %tr{:class => 'even'} + %th + Funktion + %th + Anzahl + %th + %tr{:class => 'odd'} + %td + Callthrough + %td + = link_to @tenant.callthroughs.count.to_s, tenant_callthroughs_path(@tenant) + %td + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => Callthrough} + %tr{:class => 'even'} + %td + Konferenzen + %td + = link_to @tenant.conferences.count.to_s, tenant_conferences_path(@tenant) + %td + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => Conference} + %tr{:class => 'odd'} + %td + Rufgruppen + %td + = link_to @tenant.hunt_groups.count.to_s, tenant_hunt_groups_path(@tenant) + %td + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => HuntGroup} + %tr{:class => 'even'} + %td + Warteschleifen + %td + = link_to @tenant.automatic_call_distributors.count.to_s, tenant_automatic_call_distributors_path(@tenant) + %td + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => AutomaticCallDistributor} + %tr{:class => 'odd'} + %td + Oberflächen-Funktionen + %td + = link_to GuiFunction.count.to_s, gui_functions_path + %td + +-# Phone books +-# +- if GuiFunction.display?('show_phone_books_in_user_show_view', current_user) + - if can?( :index, PhoneBook ) + %h2=t("phone_books.index.page_title") + = render "phone_books/index_core", :phone_books => @tenant.phone_books + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => PhoneBook} + +- if STRICT_INTERNAL_EXTENSION_HANDLING == true + %h3= t('phone_number_ranges.index.page_title') + + - if @tenant.created_at > (Time.now - 15.minutes) && Delayed::Job.count > 0 && @tenant.phone_number_ranges.find_by_name(INTERNAL_EXTENSIONS).try(:phone_numbers).try(:count).to_i == 0 + Der Mandant + = "\"#{@tenant}\"" + wurde erst vor + = distance_of_time_in_words_to_now(@tenant.created_at) + erstellt. Es gibt immer noch nicht abgeschlossene + = pluralize(Delayed::Job.count, 'Hintergrundprozesse') + \. Bitte warten Sie noch ein paar Minuten und laden anschließend diese Seite erneut. + - else + =render 'phone_number_ranges/index_core', :phone_number_ranges => (@tenant.phone_number_ranges + @tenant.country.phone_number_ranges.where(:name => SERVICE_NUMBERS)) + =render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => PhoneNumberRange} diff --git a/app/views/tenants/_admin_area.html.haml b/app/views/tenants/_admin_area.html.haml new file mode 100644 index 0000000..d648143 --- /dev/null +++ b/app/views/tenants/_admin_area.html.haml @@ -0,0 +1,116 @@ +%p + You belong to the + = link_to 'admin group', tenant_user_group_path(@tenant, @tenant.user_groups.find_by_name('Admins')) + and therefore have super powers. But always remember Peter Parker's: "With great power comes great responsibility." + +%p + = succeed '.' do + This tenant has + = link_to pluralize(@tenant.user_groups.count, 'user group'), tenant_user_groups_path(@tenant) + - if @tenant.user_groups.count < 5 + = "(#{@tenant.user_groups.order(:name).map{|group| group.to_s }.join(', ')})" + which handle a total of + = link_to pluralize(@tenant.users.count, 'user'), tenant_users_path(@tenant) + This system can setup + = PhoneModel.count + different phone models from the manufacturers + - Manufacturer.all.each do |manufacturer| + - if manufacturer != Manufacturer.last && manufacturer != Manufacturer.limit(Manufacturer.count - 1).last + = succeed ', ' do + =link_to manufacturer, manufacturer_path(manufacturer) + - elsif manufacturer == Manufacturer.limit(Manufacturer.count - 1).last + = succeed ' and ' do + =link_to manufacturer, manufacturer_path(manufacturer) + - else + = succeed '.' do + =link_to manufacturer, manufacturer_path(manufacturer) + +%h3 SIP-Accounts and Phones + +%table + %tr{:class => 'even'} + %th + %th + = @tenant + %th + All users of + = "\"#{@tenant}\"" + %tr{:class => 'odd'} + %td + SIP accounts + %td + = link_to @tenant.sip_accounts.count.to_s, tenant_sip_accounts_path(@tenant) + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => SipAccount} + %td= @tenant.users_sip_accounts.count.to_s + %tr{:class => 'even'} + %td + Phones + %td + = link_to @tenant.phones.count.to_s, tenant_phones_path(@tenant) + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => Phone} + %td= @tenant.users_phones.count.to_s + +%h3 Misc + +%table + %tr{:class => 'even'} + %th + Feature + %th + Counter + %th + %tr{:class => 'odd'} + %td + Callthroughs + %td + = link_to @tenant.callthroughs.count.to_s, tenant_callthroughs_path(@tenant) + %td + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => Callthrough} + %tr{:class => 'even'} + %td + Conferences + %td + = link_to @tenant.conferences.count.to_s, tenant_conferences_path(@tenant) + %td + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => Conference} + %tr{:class => 'odd'} + %td + Hunt groups + %td + = link_to @tenant.hunt_groups.count.to_s, tenant_hunt_groups_path(@tenant) + %td + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => HuntGroup} + %tr{:class => 'even'} + %td + ACDs + %td + = link_to @tenant.automatic_call_distributors.count.to_s, tenant_automatic_call_distributors_path(@tenant) + %td + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => AutomaticCallDistributor} + %tr{:class => 'odd'} + %td + GUI functions + %td + = link_to GuiFunction.count.to_s, gui_functions_path + %td + +-# Phone books +-# +- if GuiFunction.display?('show_phone_books_in_user_show_view', current_user) + - if can?( :index, PhoneBook ) + %h2=t("phone_books.index.page_title") + = render "phone_books/index_core", :phone_books => @tenant.phone_books + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => PhoneBook} + +- if STRICT_INTERNAL_EXTENSION_HANDLING == true + %h3= t('phone_number_ranges.index.page_title') + + - if @tenant.created_at > (Time.now - 15.minutes) && Delayed::Job.count > 0 && @tenant.phone_number_ranges.find_by_name(INTERNAL_EXTENSIONS).try(:phone_numbers).try(:count).to_i == 0 + This tenant was created + = distance_of_time_in_words_to_now(@tenant.created_at) + ago. There are still + = pluralize(Delayed::Job.count, 'background job') + not finished. This can take a couple of minutes. Please reload this page later. + - else + =render 'phone_number_ranges/index_core', :phone_number_ranges => (@tenant.phone_number_ranges + @tenant.country.phone_number_ranges.where(:name => SERVICE_NUMBERS)) + =render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => PhoneNumberRange} diff --git a/app/views/tenants/_form.html.haml b/app/views/tenants/_form.html.haml new file mode 100644 index 0000000..2ca8a69 --- /dev/null +++ b/app/views/tenants/_form.html.haml @@ -0,0 +1,24 @@ += simple_form_for(@tenant) do |f| + = f.error_notification + + = render "form_core", :f => f + + = f.association :country, :label => t('tenants.form.country_id.label'), :hint => conditional_hint('tenants.form.country_id.hint'), :include_blank => false + = f.association :language, :label => t('tenants.form.language_id.label'), :hint => conditional_hint('tenants.form.language_id.hint'), :include_blank => false + + = f.association :sip_domain, :label => t('tenants.form.sip_domain.label'), :hint => conditional_hint('tenants.form.sip_domain.hint'), :include_blank => false + + = f.input :from_field_voicemail_email, :label => t('tenants.form.from_field_voicemail_email.label'), :hint => conditional_hint('tenants.form.from_field_voicemail_email.hint') + = f.input :from_field_pin_change_email, :label => t('tenants.form.from_field_pin_change_email.label'), :hint => conditional_hint('tenants.form.from_field_pin_change_email.hint') + + - if STRICT_INTERNAL_EXTENSION_HANDLING == true || STRICT_DID_HANDLING == true + %h2= t('tenants.form.phone_numbers') + %p= t('tenants.form.intro') + + - if STRICT_INTERNAL_EXTENSION_HANDLING == true + = f.input :internal_extension_ranges, :label => t('tenants.form.internal_extension_ranges.label'), :hint => conditional_hint('tenants.form.internal_extension_ranges.hint') + - if STRICT_DID_HANDLING == true + = f.input :did_list, :label => t('tenants.form.did_list.label'), :hint => conditional_hint('tenants.form.did_list.hint') + + .actions + = f.button :submit, conditional_t('tenants.form.submit') \ No newline at end of file diff --git a/app/views/tenants/_form_core.html.haml b/app/views/tenants/_form_core.html.haml new file mode 100644 index 0000000..4eb2ccc --- /dev/null +++ b/app/views/tenants/_form_core.html.haml @@ -0,0 +1,3 @@ +.inputs + = f.input :name, :label => t('tenants.form.name.label'), :hint => conditional_hint('tenants.form.name.hint') + = f.input :description, :label => t('tenants.form.description.label'), :hint => conditional_hint('tenants.form.description.hint') diff --git a/app/views/tenants/_index_core.html.haml b/app/views/tenants/_index_core.html.haml new file mode 100644 index 0000000..60afeee --- /dev/null +++ b/app/views/tenants/_index_core.html.haml @@ -0,0 +1,17 @@ +%table + %tr + %th= t('tenants.index.name') + %th= t('tenants.index.description') + + - reset_cycle + - for tenant in tenants + %tr{:class => cycle('odd', 'even')} + %td= tenant.name + %td= tenant.description + %td + - if current_user && current_user.current_tenant != tenant && current_user.tenants.include?(tenant) + = simple_form_for([current_user.current_tenant, current_user]) do |f| + = f.hidden_field :current_tenant_id, :value => tenant.id + .actions + = f.button :submit, conditional_t('tenants.switch_to_tenant') + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => tenant} \ No newline at end of file diff --git a/app/views/tenants/edit.html.haml b/app/views/tenants/edit.html.haml new file mode 100644 index 0000000..159f4fd --- /dev/null +++ b/app/views/tenants/edit.html.haml @@ -0,0 +1,3 @@ +- title t("tenants.edit.page_title", :resource => @tenant ) + += render "form" diff --git a/app/views/tenants/index.html.haml b/app/views/tenants/index.html.haml new file mode 100644 index 0000000..1783825 --- /dev/null +++ b/app/views/tenants/index.html.haml @@ -0,0 +1,6 @@ +- title t("tenants.index.page_title") + +- if @tenants.count > 0 + = render "index_core", :tenants => @tenants + += render :partial => 'shared/create_link', :locals => {:child_class => Tenant} \ No newline at end of file diff --git a/app/views/tenants/new.html.haml b/app/views/tenants/new.html.haml new file mode 100644 index 0000000..dca3809 --- /dev/null +++ b/app/views/tenants/new.html.haml @@ -0,0 +1,3 @@ +- title t("tenants.new.page_title") + += render "form" diff --git a/app/views/tenants/show.html.haml b/app/views/tenants/show.html.haml new file mode 100644 index 0000000..cb2b895 --- /dev/null +++ b/app/views/tenants/show.html.haml @@ -0,0 +1,14 @@ +- title t("tenants.show.page_title") + +%p + %strong= t('tenants.show.name') + ":" + = @tenant.name +- if !@tenant.description.blank? + %p + %strong= t('tenants.show.description') + ":" + = @tenant.description + += render :partial => 'shared/show_edit_destroy_part', :locals => { :child => @tenant } + +- if @tenant.user_groups.where(:name => 'Admins').count > 0 && @tenant.user_groups.where(:name => 'Admins').first.users.include?(current_user) + = render 'admin_area' \ No newline at end of file diff --git a/app/views/user_group_memberships/_form.html.haml b/app/views/user_group_memberships/_form.html.haml new file mode 100644 index 0000000..3c0fee1 --- /dev/null +++ b/app/views/user_group_memberships/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@user_group, @user_group_membership]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('user_group_memberships.form.submit') \ No newline at end of file diff --git a/app/views/user_group_memberships/_form_core.html.haml b/app/views/user_group_memberships/_form_core.html.haml new file mode 100644 index 0000000..e77427f --- /dev/null +++ b/app/views/user_group_memberships/_form_core.html.haml @@ -0,0 +1,2 @@ +.inputs + = f.input :user_id, :label => t('hunt_groups.form.user.label'), :hint => conditional_hint('hunt_groups.form.user.hint'), :collection => @potential_users, :include_blank => false \ No newline at end of file diff --git a/app/views/user_group_memberships/_index_core.html.haml b/app/views/user_group_memberships/_index_core.html.haml new file mode 100644 index 0000000..3c3cebe --- /dev/null +++ b/app/views/user_group_memberships/_index_core.html.haml @@ -0,0 +1,13 @@ +%table + %tr + %th= t('user_group_memberships.index.tenant') + %th= t('user_group_memberships.index.user_group') + %th= t('user_group_memberships.index.user') + + - reset_cycle + - for user_group_membership in user_group_memberships + %tr{:class => cycle('odd', 'even')} + %td= user_group_membership.user_group.tenant + %td= user_group_membership.user_group + %td= user_group_membership.user + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => user_group_membership.user_group, :child => user_group_membership} \ No newline at end of file diff --git a/app/views/user_group_memberships/edit.html.haml b/app/views/user_group_memberships/edit.html.haml new file mode 100644 index 0000000..2080c87 --- /dev/null +++ b/app/views/user_group_memberships/edit.html.haml @@ -0,0 +1,3 @@ +- title t("user_group_memberships.edit.page_title", :resource => @user_group_membership) + += render "form" diff --git a/app/views/user_group_memberships/index.html.haml b/app/views/user_group_memberships/index.html.haml new file mode 100644 index 0000000..1d7927b --- /dev/null +++ b/app/views/user_group_memberships/index.html.haml @@ -0,0 +1,7 @@ +- title t("user_group_memberships.index.page_title") + +- if @user_group_memberships.count > 0 + = render "index_core", :user_group_memberships => @user_group_memberships + +- if @potential_users_count > 0 + = render :partial => 'shared/create_link', :locals => {:parent => @user_group, :child_class => UserGroupMembership} \ No newline at end of file diff --git a/app/views/user_group_memberships/new.html.haml b/app/views/user_group_memberships/new.html.haml new file mode 100644 index 0000000..9d59fdd --- /dev/null +++ b/app/views/user_group_memberships/new.html.haml @@ -0,0 +1,3 @@ +- title t("user_group_memberships.new.page_title") + += render "form" diff --git a/app/views/user_group_memberships/show.html.haml b/app/views/user_group_memberships/show.html.haml new file mode 100644 index 0000000..0e5cfab --- /dev/null +++ b/app/views/user_group_memberships/show.html.haml @@ -0,0 +1,7 @@ +- title t("user_group_memberships.show.page_title") + +%p + %strong= t('user_group_memberships.show.user') + ":" + = @user_group_membership.user + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @user_group, :child => @user_group_membership } \ No newline at end of file diff --git a/app/views/user_groups/_form.html.haml b/app/views/user_groups/_form.html.haml new file mode 100644 index 0000000..cbe3cc4 --- /dev/null +++ b/app/views/user_groups/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for(@user_group) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('user_groups.form.submit') \ No newline at end of file diff --git a/app/views/user_groups/_form_core.html.haml b/app/views/user_groups/_form_core.html.haml new file mode 100644 index 0000000..a45ba6f --- /dev/null +++ b/app/views/user_groups/_form_core.html.haml @@ -0,0 +1,3 @@ +.inputs + = f.input :name, :label => t('user_groups.form.name.label'), :hint => conditional_hint('user_groups.form.name.hint') + = f.input :description, :label => t('user_groups.form.description.label'), :hint => conditional_hint('user_groups.form.description.hint') diff --git a/app/views/user_groups/_index_core.html.haml b/app/views/user_groups/_index_core.html.haml new file mode 100644 index 0000000..d2b6e88 --- /dev/null +++ b/app/views/user_groups/_index_core.html.haml @@ -0,0 +1,24 @@ +%table + %tr + %th= t('user_groups.index.name') + %th= t('user_groups.index.description') + - if @user + %th= t('user_groups.index.tenant_id') + - else + %th= t('user_groups.index.members') + + - reset_cycle + - for user_group in user_groups + %tr{:class => cycle('odd', 'even')} + %td= user_group.name + %td= user_group.description + - if @user + %td= user_group.tenant + - else + %td + =render 'users/listing', :users => user_group.users + - if user_group.users.count > 1 + %br + = render :partial => 'shared/create_link', :locals => {:parent => user_group, :child_class => UserGroupMembership} + + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => user_group.tenant, :child => user_group} diff --git a/app/views/user_groups/edit.html.haml b/app/views/user_groups/edit.html.haml new file mode 100644 index 0000000..35514e0 --- /dev/null +++ b/app/views/user_groups/edit.html.haml @@ -0,0 +1,3 @@ +- title t("user_groups.edit.page_title", :resource => @user_group) + += render "form" diff --git a/app/views/user_groups/index.html.haml b/app/views/user_groups/index.html.haml new file mode 100644 index 0000000..545b838 --- /dev/null +++ b/app/views/user_groups/index.html.haml @@ -0,0 +1,6 @@ +- title t("user_groups.index.page_title") + +- if @user_groups.count > 0 + = render "index_core", :user_groups => @user_groups + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => UserGroup} \ No newline at end of file diff --git a/app/views/user_groups/new.html.haml b/app/views/user_groups/new.html.haml new file mode 100644 index 0000000..dfef18e --- /dev/null +++ b/app/views/user_groups/new.html.haml @@ -0,0 +1,3 @@ +- title t("user_groups.new.page_title") + += render "form" diff --git a/app/views/user_groups/show.html.haml b/app/views/user_groups/show.html.haml new file mode 100644 index 0000000..00bdeb7 --- /dev/null +++ b/app/views/user_groups/show.html.haml @@ -0,0 +1,20 @@ +- title t("user_groups.show.page_title") + +%p + %strong= t('user_groups.show.name') + ":" + = @user_group.name +%p + %strong= t('user_groups.show.description') + ":" + = @user_group.description +%p + %strong= t('user_groups.show.tenant_id') + ":" + = @user_group.tenant + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @user_group.tenant, :child => @user_group } + +%h2=t("user_group_memberships.index.page_title") + +- if @user_group.user_group_memberships.count > 0 + = render "user_group_memberships/index_core", :user_group_memberships => @user_group.user_group_memberships + += render :partial => 'shared/create_link', :locals => {:parent => @user_group, :child_class => UserGroupMembership} \ No newline at end of file diff --git a/app/views/users/_form.html.haml b/app/views/users/_form.html.haml new file mode 100644 index 0000000..9a75677 --- /dev/null +++ b/app/views/users/_form.html.haml @@ -0,0 +1,16 @@ +- if @parent && @parent.class == Tenant + = simple_form_for([@parent, @user]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('users.form.submit') +- else + = simple_form_for(@user) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('users.form.submit') diff --git a/app/views/users/_form_core.html.haml b/app/views/users/_form_core.html.haml new file mode 100644 index 0000000..8e18d12 --- /dev/null +++ b/app/views/users/_form_core.html.haml @@ -0,0 +1,27 @@ +.inputs + - if GuiFunction.display?('name_data_fields_in_user_edit_form', current_user) + = f.input :male, :collection => [[true, t('users.form.gender.male')], [false, t('users.form.gender.female')]], :label_method => :last, :value_method => :first, :label => t('users.form.male.label'), :hint => conditional_hint('users.form.gender.hint'), :label => t('users.form.gender.label'), :as => :radio + = f.input :first_name, :label => t('users.form.first_name.label'), :hint => conditional_hint('users.form.first_name.hint'), :autofocus => true + = f.input :middle_name, :label => t('users.form.middle_name.label'), :hint => conditional_hint('users.form.middle_name.hint') + = f.input :last_name, :label => t('users.form.last_name.label'), :hint => conditional_hint('users.form.last_name.hint') + - if GuiFunction.display?('user_name_field_in_user_edit_form', current_user) + = f.input :user_name, :label => t('users.form.user_name.label'), :hint => conditional_hint('users.form.user_name.hint') + - if GuiFunction.display?('email_field_in_user_edit_form', current_user) + = f.input :email, :label => t('users.form.email.label'), :hint => conditional_hint('users.form.email.hint') + + - if GuiFunction.display?('password_fields_in_user_edit_form', current_user) + = f.input :password, :label => t('users.form.password.label'), :hint => conditional_hint('users.form.password.hint'), :as => :password + = f.input :password_confirmation, :label => t('users.form.password_confirmation.label'), :hint => conditional_hint('users.form.password_confirmation.hint'), :as => :password + + - if GuiFunction.display?('pin_fields_in_user_edit_form', current_user) + = f.input :new_pin, :label => t('users.form.new_pin.label'), :hint => conditional_hint('users.form.new_pin.hint'), :as => :password + = f.input :new_pin_confirmation, :label => t('users.form.new_pin_confirmation.label'), :hint => conditional_hint('users.form.new_pin_confirmation.hint'), :as => :password + + = f.input :image, { :as => :file, :label => t('users.form.image.label'), :hint => conditional_hint('users.form.image.hint') } + - if @user && @user.image? + %p + =link_to 'Destroy avatar', tenant_user_destroy_avatar_path(@tenant, @user) + + = f.input :language_id, :collection => Language.all, :label => t('users.form.language_id.label'), :hint => conditional_hint('users.form.language_id.hint'), :include_blank => false + + /= f.input :send_voicemail_as_email_attachment, :label => t('users.form.send_voicemail_as_email_attachment.label'), :hint => conditional_hint('users.form.send_voicemail_as_email_attachment.hint') diff --git a/app/views/users/_index_core.html.haml b/app/views/users/_index_core.html.haml new file mode 100644 index 0000000..51c15de --- /dev/null +++ b/app/views/users/_index_core.html.haml @@ -0,0 +1,18 @@ +%table + %tr + %th + %th= t('users.index.user_name') + %th= t('users.index.email') + %th= t('users.index.first_name') + %th= t('users.index.last_name') + + - reset_cycle + - for user in users + %tr{:class => cycle('odd', 'even')} + %td + = image_tag user.image_url(:mini).to_s if user.image_url(:mini) + %td= user.user_name + %td= user.email + %td= user.first_name + %td= user.last_name + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => @tenant, :child => user} \ No newline at end of file diff --git a/app/views/users/_listing.html.haml b/app/views/users/_listing.html.haml new file mode 100644 index 0000000..0a97ad1 --- /dev/null +++ b/app/views/users/_listing.html.haml @@ -0,0 +1,8 @@ +- amount_of_users = users.count +- if amount_of_users > 0 + - if amount_of_users < 30 + = users.map{|user| user}.join(', ') + - else + = users.limit(15).map{|user| user}.join(', ') + ', ' + = '[...]' + = users.offset(amount_of_users - 15).map{|user| user}.join(', ') \ No newline at end of file diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml new file mode 100644 index 0000000..96272f5 --- /dev/null +++ b/app/views/users/edit.html.haml @@ -0,0 +1,3 @@ +- title t("users.edit.page_title", :resource => @user) + += render "form" diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml new file mode 100644 index 0000000..892e035 --- /dev/null +++ b/app/views/users/index.html.haml @@ -0,0 +1,6 @@ +- title t("users.index.page_title") + +- if @users.count > 0 + = render "index_core", :users => @users + += render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => User} \ No newline at end of file diff --git a/app/views/users/new.html.haml b/app/views/users/new.html.haml new file mode 100644 index 0000000..a014611 --- /dev/null +++ b/app/views/users/new.html.haml @@ -0,0 +1,3 @@ +- title t("users.new.page_title") + += render "form" diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml new file mode 100644 index 0000000..7730447 --- /dev/null +++ b/app/views/users/show.html.haml @@ -0,0 +1,96 @@ +- title "User: #{@user}" + +#user-show + %aside + = image_tag @user.image_url(:small).to_s, class: 'display' if @user.image? && @user.image_url(:small) + %p + %strong= t('users.show.user_name') + ":" + = @user.user_name + %p + %strong= t('users.show.email') + ":" + = @user.email + + %p.controls + = render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @tenant, :child => @user } + + - @user.sip_accounts.each do |sip_account| + - phone_number = sip_account.phone_numbers.order(:number).last + - if phone_number && !phone_number.number.blank? && phone_number.number[0] != '+' + %p + %strong= sip_account.phone_numbers.order(:number).last.number + %p + =link_to t("call_histories.index.page_title"), sip_account_call_histories_path(sip_account) + %br + =link_to t("voicemail_messages.index.page_title"), sip_account_voicemail_messages_path(sip_account) + %br + =link_to t("call_forwards.index.page_title"), phone_number_call_forwards_path(phone_number) + %br + =link_to t("voicemail_settings.index.page_title"), sip_account_voicemail_settings_path(sip_account) + %br + =link_to t("softkeys.index.page_title"), sip_account_softkeys_path(sip_account) + %br + =link_to t("ringtones.show.page_title"), phone_number_ringtones_path(phone_number) + + - if @user.conferences.any? + %p + %strong= t("conferences.index.page_title") + - @user.conferences.each do |conference| + %p + =link_to conference, edit_user_conference_path(@user, conference) + + + %section + -# Phone books + -# + - if GuiFunction.display?('show_phone_books_in_user_show_view', current_user) + - if can?( :index, PhoneBook ) + %h2=t("phone_books.index.page_title") + = render "phone_books/index_core", :phone_books => @phone_books + = render :partial => 'shared/create_link', :locals => {:parent => @user, :child_class => PhoneBook} + + -# User groups (only if the current user can edit or destroy them) + -# + - if @user.user_groups.map{ |x| can?( :edit, x ) || can?( :destroy, x ) }.include?(true) + - if can?( :index, UserGroup ) + %h2=t("user_groups.index.page_title") + - if @user.user_groups.count > 0 + = render "user_groups/index_core", :user_groups => @user.user_groups.where(:tenant_id => @tenant.id).order(:name) + = render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => UserGroup} + + -# SIP accounts + -# + - if (can?( :index, SipAccount ) && @user.sip_accounts.count > 0 ) || can?( :create, SipAccount ) + %h2= t('sip_accounts.index.page_title') + - if can?( :index, SipAccount ) && @user.sip_accounts.count > 0 + = render "sip_accounts/index_core", :sip_accounts => @user.sip_accounts + = render :partial => 'shared/create_link', :locals => {:parent => @user, :child_class => SipAccount} + + -# Phones + -# + - if (can?( :index, Phone, :phoneable => @user ) && @user.phones.count > 0 ) || can?( :create, Phone, :phoneable => @user ) + %h2= t('phones.index.page_title') + - if can?( :index, Phone, :phoneable => @user ) && @user.phones.count > 0 + = render "phones/index_core", :phones => @user.phones + = render :partial => 'shared/create_link', :locals => {:parent => @user, :child_class => Phone} + + -# FaxAccount + -# + - if (can?( :index, FaxAccount ) && @user.fax_accounts.count > 0 ) || can?( :create, FaxAccount ) + %h2= t('fax_accounts.index.page_title') + - if can?( :index, FaxAccount ) && @user.fax_accounts.count > 0 + = render "fax_accounts/index_core", {:fax_accounts => @user.fax_accounts, :fax_accountable => @user} + = render :partial => 'shared/create_link', :locals => {:parent => @user, :child_class => FaxAccount} + + -# Conferences + -# + - if (can?( :index, Conference ) && @user.conferences.count > 0 ) || can?( :create, Conference ) + %h2= t('conferences.index.page_title') + - if can?( :index, Conference ) && @user.conferences.count > 0 + = render "conferences/index_core", :conferences => @user.conferences + = render :partial => 'shared/create_link', :locals => {:parent => @user, :child_class => Conference} + + -# Tenants + -# + - if can?( :index, Tenant ) && @user.tenants.count > 1 + %h2=t("tenants.index.page_title") + = render "tenants/index_core", :tenants => @user.tenants \ No newline at end of file diff --git a/app/views/voicemail_messages/_index_core.html.haml b/app/views/voicemail_messages/_index_core.html.haml new file mode 100644 index 0000000..b8e47af --- /dev/null +++ b/app/views/voicemail_messages/_index_core.html.haml @@ -0,0 +1,44 @@ += form_tag(destroy_multiple_sip_account_voicemail_messages_path(@sip_account), :method => :delete, :id => 'voicemail_message_form') do + %header.entries-nav= render :partial => "voicemail_messages/navigation" + .content + %table + - reset_cycle + - for voicemail_message in voicemail_messages + %tr.voicemail-messages-entry{:class => cycle('odd', 'even'), :id => "message_#{voicemail_message.uuid}"} + %td.select_box= check_box_tag("selected_uuids[]", voicemail_message.uuid, false, :uuid => "select_item_#{voicemail_message.uuid}", :class => 'select_item') + %td.time + .voicemail-received + = voicemail_message.format_date(voicemail_message.created_epoch, t("voicemail_messages.index.date_format"), t("voicemail_messages.index.date_today_format")) + + - read_date = voicemail_message.format_date(voicemail_message.read_epoch, t("voicemail_messages.index.date_format"), t("voicemail_messages.index.date_today_format")) + - if read_date + .voicemail-read + = read_date + %td.folder + = t("voicemail_messages.index.mailbox.#{voicemail_message.in_folder}") + %td.user + .name= voicemail_message.cid_name + .phone= voicemail_message.cid_number + %td.status + .duration= voicemail_message.display_duration + %td + - if ! voicemail_message.flags.blank? + = t("voicemail_messages.index.flags.#{voicemail_message.flags}") + %td.actions + - if can?(:show, voicemail_message) && File.readable?(voicemail_message.file_path) + = link_to t('voicemail_messages.index.actions.download'), sip_account_voicemail_message_path(@sip_account, voicemail_message, :format => :wav), :method => :get + %td.actions + - if @sip_account.registration && can?(:call, voicemail_message) + = link_to t('voicemail_messages.index.actions.call'), call_sip_account_voicemail_message_path(@sip_account, voicemail_message), :method => :put + %td.actions + - if can?(:edit, voicemail_message) && voicemail_message.read_epoch > 0 + = link_to t('voicemail_messages.index.actions.mark_unread'), mark_unread_sip_account_voicemail_message_path(@sip_account, voicemail_message), :method => :put + - else + = link_to t('voicemail_messages.index.actions.mark_read'), mark_read_sip_account_voicemail_message_path(@sip_account, voicemail_message), :method => :put + %td.actions + - if can? :destroy, voicemail_message + = link_to t('voicemail_messages.index.actions.destroy'), sip_account_voicemail_message_path(@sip_account, voicemail_message), :method => :delete + + %footer.entries-nav= render :partial => "voicemail_messages/navigation" + = image_submit_tag('icons/cross-16x.png', :confirm => t("voicemail_messages.index.actions.confirm_selected")) + = t("voicemail_messages.index.actions.destroy_multiple") diff --git a/app/views/voicemail_messages/_navigation.html.haml b/app/views/voicemail_messages/_navigation.html.haml new file mode 100644 index 0000000..2277bf2 --- /dev/null +++ b/app/views/voicemail_messages/_navigation.html.haml @@ -0,0 +1,9 @@ +%nav + %ol.abc + %li + %a{ :href => "?type=" }= t('voicemail_messages.index.navigation.all', :count => @messages_count) + %a{ :href => "?type=read" }= t('voicemail_messages.index.navigation.read', :count => @messages_read_count) + %a{ :href => "?type=unread" }= t('voicemail_messages.index.navigation.unread', :count => @messages_unread_count) + +.pagination + = will_paginate @voicemail_messages diff --git a/app/views/voicemail_messages/index.html.haml b/app/views/voicemail_messages/index.html.haml new file mode 100644 index 0000000..53f8090 --- /dev/null +++ b/app/views/voicemail_messages/index.html.haml @@ -0,0 +1,6 @@ +- if @type + - title t("voicemail_messages.index.page_title_#{@type}") +- else + - title t("voicemail_messages.index.page_title") + += render "index_core", :voicemail_messages => @voicemail_messages diff --git a/app/views/voicemail_settings/_form.html.haml b/app/views/voicemail_settings/_form.html.haml new file mode 100644 index 0000000..6d5f845 --- /dev/null +++ b/app/views/voicemail_settings/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@sip_account,@voicemail_setting]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('voicemail_settings.form.submit') \ No newline at end of file diff --git a/app/views/voicemail_settings/_form_core.html.haml b/app/views/voicemail_settings/_form_core.html.haml new file mode 100644 index 0000000..08bdfc2 --- /dev/null +++ b/app/views/voicemail_settings/_form_core.html.haml @@ -0,0 +1,11 @@ +.inputs + + = f.input :greeting_path, :as => :select, :label => t('voicemail_settings.form.greeting.label'), :hint => conditional_hint('voicemail_settings.form.greeting.hint'), :collection => @greeting_files + = f.input :name_path, :as => :select, :label => t('voicemail_settings.form.name.label'), :hint => conditional_hint('voicemail_settings.form.name.hint'), :collection => @name_files + + = f.input :password, :label => t('voicemail_settings.form.pin.label'), :hint => conditional_hint('voicemail_settings.form.pin.hint') + + = f.input :notify, :as => :boolean, :label => t('voicemail_settings.form.notify.label'), :hint => conditional_hint('voicemail_settings.form.notify.hint') + = f.input :attachment, :as => :boolean, :label => t('voicemail_settings.form.attachment.label'), :hint => conditional_hint('voicemail_settings.form.attachment.hint') + = f.input :mark_read, :as => :boolean, :label => t('voicemail_settings.form.mark_read.label'), :hint => conditional_hint('voicemail_settings.form.mark_read.hint') + = f.input :purge, :as => :boolean, :label => t('voicemail_settings.form.purge.label'), :hint => conditional_hint('voicemail_settings.form.purge.hint') diff --git a/app/views/voicemail_settings/edit.html.haml b/app/views/voicemail_settings/edit.html.haml new file mode 100644 index 0000000..6bd7031 --- /dev/null +++ b/app/views/voicemail_settings/edit.html.haml @@ -0,0 +1,3 @@ +- title t("voicemail_settings.edit.page_title") + += render "form" diff --git a/app/views/voicemail_settings/show.html.haml b/app/views/voicemail_settings/show.html.haml new file mode 100644 index 0000000..30e12d0 --- /dev/null +++ b/app/views/voicemail_settings/show.html.haml @@ -0,0 +1,26 @@ +- title t("voicemail_settings.show.page_title") + +%p + %strong= t('voicemail_settings.show.greeting_path') + ":" + = File.basename(@voicemail_setting.greeting_path.to_s) + +%p + %strong= t('voicemail_settings.show.name_path') + ":" + = File.basename(@voicemail_setting.name_path.to_s) + +%p + %strong= t('voicemail_settings.show.flags') + ":" + - if @voicemail_setting.notify + %br + = "- " + t('voicemail_settings.show.notify') + - if @voicemail_setting.attachment + %br + = "- " + t('voicemail_settings.show.attachment') + - if @voicemail_setting.mark_read + %br + = "- " + t('voicemail_settings.show.mark_read') + - if @voicemail_setting.purge + %br + = "- " + t('voicemail_settings.show.purge') + += link_to t('voicemail_settings.actions.edit'), edit_sip_account_voicemail_setting_path(@sip_account, @voicemail_setting) diff --git a/app/views/whitelists/_form.html.haml b/app/views/whitelists/_form.html.haml new file mode 100644 index 0000000..c7f787a --- /dev/null +++ b/app/views/whitelists/_form.html.haml @@ -0,0 +1,7 @@ += simple_form_for([@parent, @whitelist]) do |f| + = f.error_notification + + = render "form_core", :f => f + + .actions + = f.button :submit, conditional_t('whitelists.form.submit') \ No newline at end of file diff --git a/app/views/whitelists/_form_core.html.haml b/app/views/whitelists/_form_core.html.haml new file mode 100644 index 0000000..38f1487 --- /dev/null +++ b/app/views/whitelists/_form_core.html.haml @@ -0,0 +1,8 @@ +.inputs + = f.input :name, :label => t('whitelists.form.name.label'), :hint => conditional_hint('whitelists.form.name.hint') + + %h3= t('whitelists.form.phone_numbers.label') + - if !t('whitelists.form.phone_numbers.hint').blank? + %p= t('whitelists.form.phone_numbers.hint') + = f.simple_fields_for :phone_numbers do |phone_number| + = render "phone_numbers/form_core", :f => phone_number diff --git a/app/views/whitelists/_index_core.html.haml b/app/views/whitelists/_index_core.html.haml new file mode 100644 index 0000000..b4c5b0c --- /dev/null +++ b/app/views/whitelists/_index_core.html.haml @@ -0,0 +1,15 @@ +%table + %tr + %th= t('whitelists.index.name') + %th= t('whitelists.index.phone_numbers') + + - reset_cycle + - for whitelist in whitelists + %tr{:class => cycle('odd', 'even')} + %td= whitelist.name || '-' + %td + = render 'phone_numbers/listing', :phone_numbers => whitelist.phone_numbers + %br + = render :partial => 'shared/create_link', :locals => {:parent => whitelist, :child_class => PhoneNumber, :short_link => true} + + =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => whitelist.whitelistable, :child => whitelist} \ No newline at end of file diff --git a/app/views/whitelists/edit.html.haml b/app/views/whitelists/edit.html.haml new file mode 100644 index 0000000..9f8af90 --- /dev/null +++ b/app/views/whitelists/edit.html.haml @@ -0,0 +1,3 @@ +- title t("whitelists.edit.page_title") + += render "form" diff --git a/app/views/whitelists/index.html.haml b/app/views/whitelists/index.html.haml new file mode 100644 index 0000000..0873189 --- /dev/null +++ b/app/views/whitelists/index.html.haml @@ -0,0 +1,6 @@ +- title t("whitelists.index.page_title") + +- if @whitelists.count > 0 + = render "index_core", :whitelists => @whitelists + += render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => Whitelist} \ No newline at end of file diff --git a/app/views/whitelists/new.html.haml b/app/views/whitelists/new.html.haml new file mode 100644 index 0000000..f1101ad --- /dev/null +++ b/app/views/whitelists/new.html.haml @@ -0,0 +1,3 @@ +- title t("whitelists.new.page_title") + += render "form" diff --git a/app/views/whitelists/show.html.haml b/app/views/whitelists/show.html.haml new file mode 100644 index 0000000..77652f9 --- /dev/null +++ b/app/views/whitelists/show.html.haml @@ -0,0 +1,7 @@ +- title t("whitelists.show.page_title") + +%p + %strong= t('whitelists.show.name') + ":" + = @whitelist.name + += render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @parent, :child => @whitelist } \ No newline at end of file -- cgit v1.2.3