summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorStefan Wintermeyer <stefan.wintermeyer@amooma.de>2012-12-17 12:01:45 +0100
committerStefan Wintermeyer <stefan.wintermeyer@amooma.de>2012-12-17 12:01:45 +0100
commitb80bd744ad873f6fc43018bc4bfb90677de167bd (patch)
tree072c4b0e33d442528555b82c415f5e7a1712b2b0 /app
parent3e706c2025ecc5523e81ad649639ef2ff75e7bac (diff)
Start of GS5.
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/amooma-logo.pngbin0 -> 1314 bytes
-rw-r--r--app/assets/images/bg-body.pngbin0 -> 1768 bytes
-rw-r--r--app/assets/images/gradients/light-to-dark-blue-x63.pngbin0 -> 187 bytes
-rw-r--r--app/assets/images/gradients/white-gray-x29-reverse.pngbin0 -> 123 bytes
-rw-r--r--app/assets/images/gradients/white-gray-x29.pngbin0 -> 126 bytes
-rw-r--r--app/assets/images/gradients/white-texture-x63.pngbin0 -> 6927 bytes
-rw-r--r--app/assets/images/icons/cellphone-32x.pngbin0 -> 1938 bytes
-rw-r--r--app/assets/images/icons/clock-32x.pngbin0 -> 5968 bytes
-rw-r--r--app/assets/images/icons/cross-16x.pngbin0 -> 3350 bytes
-rw-r--r--app/assets/images/icons/facebook-32x.pngbin0 -> 402 bytes
-rw-r--r--app/assets/images/icons/fax-32x.pngbin0 -> 5762 bytes
-rw-r--r--app/assets/images/icons/headphones-16x.pngbin0 -> 3285 bytes
-rw-r--r--app/assets/images/icons/headphones-32x.pngbin0 -> 5906 bytes
-rw-r--r--app/assets/images/icons/house-32x.pngbin0 -> 5741 bytes
-rw-r--r--app/assets/images/icons/mic-32x.pngbin0 -> 5837 bytes
-rw-r--r--app/assets/images/icons/microphone-16x.pngbin0 -> 3239 bytes
-rw-r--r--app/assets/images/icons/microphone-32x.pngbin0 -> 5837 bytes
-rw-r--r--app/assets/images/icons/mute-16x.pngbin0 -> 3349 bytes
-rw-r--r--app/assets/images/icons/phone-down-32x.pngbin0 -> 5879 bytes
-rw-r--r--app/assets/images/icons/phone-mobile-32x.pngbin0 -> 5509 bytes
-rw-r--r--app/assets/images/icons/phone-up-32x.pngbin0 -> 5861 bytes
-rw-r--r--app/assets/images/icons/search-13x16.pngbin0 -> 849 bytes
-rw-r--r--app/assets/images/icons/skype-32x.pngbin0 -> 625 bytes
-rw-r--r--app/assets/images/icons/star-16x.pngbin0 -> 3379 bytes
-rw-r--r--app/assets/images/icons/suitcase-32x.pngbin0 -> 5550 bytes
-rw-r--r--app/assets/images/icons/tag-16x.pngbin0 -> 3286 bytes
-rw-r--r--app/assets/images/icons/twitter-32x.pngbin0 -> 397 bytes
-rw-r--r--app/assets/images/icons/unmute-16x.pngbin0 -> 3373 bytes
-rw-r--r--app/assets/images/icons/user-16x.pngbin0 -> 3287 bytes
-rw-r--r--app/assets/images/icons/user-female-16x.pngbin0 -> 1680 bytes
-rw-r--r--app/assets/images/icons/user-male-16x.pngbin0 -> 1559 bytes
-rw-r--r--app/assets/images/logo.pngbin0 -> 7490 bytes
-rw-r--r--app/assets/images/rails.pngbin0 -> 6646 bytes
-rw-r--r--app/assets/images/stubs/user-36x.jpgbin0 -> 2093 bytes
-rw-r--r--app/assets/images/user.pngbin0 -> 4800 bytes
-rw-r--r--app/assets/javascripts/api/rows.js.coffee3
-rw-r--r--app/assets/javascripts/application.js13
-rw-r--r--app/assets/javascripts/config_siemens.js.coffee3
-rw-r--r--app/assets/javascripts/core.coffee5
-rw-r--r--app/assets/javascripts/page.js.coffee3
-rw-r--r--app/assets/javascripts/softkeys.js.coffee25
-rw-r--r--app/assets/javascripts/vendor/autoresize.jquery.js94
-rwxr-xr-xapp/assets/javascripts/vendor/fancybox/jquery.easing-1.3.pack.js205
-rwxr-xr-xapp/assets/javascripts/vendor/fancybox/jquery.fancybox-1.3.4.pack.js46
-rwxr-xr-xapp/assets/javascripts/vendor/fancybox/jquery.mousewheel-3.0.4.pack.js14
-rw-r--r--app/assets/javascripts/vendor/html5boilerplate.js20
-rwxr-xr-xapp/assets/javascripts/vendor/jquery-1.6.2.min.js18
-rw-r--r--app/assets/javascripts/vendor/jquery.condom.js52
-rw-r--r--app/assets/javascripts/vendor/jquery.easy-slider-1.7.js225
-rw-r--r--app/assets/javascripts/vendor/jquery.survival-kit.coffee36
-rw-r--r--app/assets/javascripts/vendor/jquery.tmpl.js486
-rwxr-xr-xapp/assets/javascripts/vendor/modernizr-2.0.6.min.js1116
-rw-r--r--app/assets/stylesheets/api/rows.css.scss3
-rw-r--r--app/assets/stylesheets/app/layouts/_app.scss24
-rw-r--r--app/assets/stylesheets/app/layouts/_conference.scss136
-rw-r--r--app/assets/stylesheets/app/layouts/_phone-book-entry.scss176
-rw-r--r--app/assets/stylesheets/app/pages/_phone_book.scss25
-rw-r--r--app/assets/stylesheets/app/shared/_contents.scss374
-rw-r--r--app/assets/stylesheets/app/shared/_footers.scss90
-rw-r--r--app/assets/stylesheets/app/shared/_handheld.scss25
-rw-r--r--app/assets/stylesheets/app/shared/_headers.scss145
-rw-r--r--app/assets/stylesheets/app/shared/_ie.scss7
-rw-r--r--app/assets/stylesheets/app/shared/_media.scss16
-rw-r--r--app/assets/stylesheets/app/shared/_print.scss17
-rw-r--r--app/assets/stylesheets/application.css.scss117
-rw-r--r--app/assets/stylesheets/scaffolds.css.scss56
-rw-r--r--app/assets/stylesheets/vendor/README1
-rw-r--r--app/assets/stylesheets/vendor/boilerplate-1.0/README15
-rw-r--r--app/assets/stylesheets/vendor/boilerplate-1.0/_reset.scss37
-rw-r--r--app/assets/stylesheets/vendor/boilerplate-1.0/_styles.scss171
-rw-r--r--app/assets/stylesheets/vendor/boilerplate-2.0/README16
-rw-r--r--app/assets/stylesheets/vendor/boilerplate-2.0/_styles.scss209
-rw-r--r--app/assets/stylesheets/vendor/easy-slider/_numeric.scss44
-rw-r--r--app/assets/stylesheets/vendor/facebox/_facebox.scss85
-rw-r--r--app/assets/stylesheets/vendor/fancy-box/README4
-rwxr-xr-xapp/assets/stylesheets/vendor/fancy-box/_fancy-box.scss336
-rw-r--r--app/assets/stylesheets/vendor/fancy-buttons/README3
-rw-r--r--app/assets/stylesheets/vendor/fancy-buttons/_fancy-buttons.scss195
-rw-r--r--app/assets/stylesheets/vendor/fancy-buttons/_fancy-gradient.scss28
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_blog.scss99
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_effects.scss97
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_forms.scss313
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_headers.scss36
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_images.scss121
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_lists.scss37
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_loader.scss11
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_navigation.scss230
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_secure.scss3
-rw-r--r--app/assets/stylesheets/vendor/survival-kit/_tools.scss267
-rw-r--r--app/controllers/access_authorizations_controller.rb68
-rw-r--r--app/controllers/acd_agents_controller.rb73
-rw-r--r--app/controllers/acd_callers_controller.rb41
-rw-r--r--app/controllers/addresses_controller.rb41
-rw-r--r--app/controllers/api/rows_controller.rb91
-rw-r--r--app/controllers/application_controller.rb161
-rw-r--r--app/controllers/automatic_call_distributors_controller.rb100
-rw-r--r--app/controllers/call_forwards_controller.rb127
-rw-r--r--app/controllers/call_histories_controller.rb100
-rw-r--r--app/controllers/calls_controller.rb6
-rw-r--r--app/controllers/callthroughs_controller.rb75
-rw-r--r--app/controllers/conference_invitees_controller.rb93
-rw-r--r--app/controllers/conferences_controller.rb82
-rw-r--r--app/controllers/config_polycom_controller.rb330
-rw-r--r--app/controllers/config_siemens_controller.rb1239
-rw-r--r--app/controllers/config_siemens_sort_controller.rb371
-rw-r--r--app/controllers/config_snom_controller.rb1169
-rw-r--r--app/controllers/fax_accounts_controller.rb82
-rw-r--r--app/controllers/fax_documents_controller.rb82
-rw-r--r--app/controllers/freeswitch_voicemail_msgs_controller.rb7
-rw-r--r--app/controllers/gemeinschaft_setups_controller.rb61
-rw-r--r--app/controllers/gs_cluster_sync_log_entries_controller.rb25
-rw-r--r--app/controllers/gs_nodes_controller.rb170
-rw-r--r--app/controllers/gui_functions_controller.rb73
-rw-r--r--app/controllers/hunt_group_members_controller.rb67
-rw-r--r--app/controllers/hunt_groups_controller.rb55
-rw-r--r--app/controllers/manufacturers_controller.rb49
-rw-r--r--app/controllers/page_controller.rb24
-rw-r--r--app/controllers/phone_book_entries_controller.rb135
-rw-r--r--app/controllers/phone_books_controller.rb105
-rw-r--r--app/controllers/phone_models_controller.rb52
-rw-r--r--app/controllers/phone_number_ranges_controller.rb56
-rw-r--r--app/controllers/phone_numbers_controller.rb226
-rw-r--r--app/controllers/phone_sip_accounts_controller.rb60
-rw-r--r--app/controllers/phones_controller.rb72
-rw-r--r--app/controllers/ringtones_controller.rb67
-rw-r--r--app/controllers/sessions_controller.rb44
-rw-r--r--app/controllers/sip_accounts_controller.rb98
-rw-r--r--app/controllers/sip_domains_controller.rb41
-rw-r--r--app/controllers/softkeys_controller.rb91
-rw-r--r--app/controllers/system_messages_controller.rb30
-rw-r--r--app/controllers/tenants_controller.rb91
-rw-r--r--app/controllers/user_group_memberships_controller.rb48
-rw-r--r--app/controllers/user_groups_controller.rb69
-rw-r--r--app/controllers/users_controller.rb85
-rw-r--r--app/controllers/voicemail_messages_controller.rb140
-rw-r--r--app/controllers/voicemail_settings_controller.rb91
-rw-r--r--app/controllers/whitelists_controller.rb61
-rw-r--r--app/helpers/access_authorizations_helper.rb2
-rw-r--r--app/helpers/acd_agents_helper.rb2
-rw-r--r--app/helpers/acd_callers_helper.rb2
-rw-r--r--app/helpers/addresses_helper.rb2
-rw-r--r--app/helpers/api/rows_helper.rb2
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/automatic_call_distributors_helper.rb2
-rw-r--r--app/helpers/call_forward_cases_helper.rb2
-rw-r--r--app/helpers/call_forwards_helper.rb2
-rw-r--r--app/helpers/callthroughs_helper.rb2
-rw-r--r--app/helpers/conference_invitees_helper.rb2
-rw-r--r--app/helpers/conferences_helper.rb2
-rw-r--r--app/helpers/config_siemens_helper.rb2
-rw-r--r--app/helpers/error_messages_helper.rb23
-rw-r--r--app/helpers/fax_accounts_helper.rb2
-rw-r--r--app/helpers/fax_documents_helper.rb2
-rw-r--r--app/helpers/gemeinschaft_setups_helper.rb2
-rw-r--r--app/helpers/gs_cluster_sync_log_entries_helper.rb2
-rw-r--r--app/helpers/gs_nodes_helper.rb2
-rw-r--r--app/helpers/gui_functions_helper.rb2
-rw-r--r--app/helpers/hunt_group_members_helper.rb2
-rw-r--r--app/helpers/hunt_groups_helper.rb2
-rw-r--r--app/helpers/layout_helper.rb70
-rw-r--r--app/helpers/manufacturers_helper.rb2
-rw-r--r--app/helpers/page_helper.rb2
-rw-r--r--app/helpers/phone_book_entries_helper.rb2
-rw-r--r--app/helpers/phone_books_helper.rb2
-rw-r--r--app/helpers/phone_models_helper.rb2
-rw-r--r--app/helpers/phone_number_ranges_helper.rb2
-rw-r--r--app/helpers/phone_numbers_helper.rb2
-rw-r--r--app/helpers/phone_sip_accounts_helper.rb2
-rw-r--r--app/helpers/phones_helper.rb2
-rw-r--r--app/helpers/phones_sip_accounts_helper.rb2
-rw-r--r--app/helpers/ringtones_helper.rb2
-rw-r--r--app/helpers/sessions_helper.rb2
-rw-r--r--app/helpers/sip_accounts_helper.rb2
-rw-r--r--app/helpers/sip_domains_helper.rb2
-rw-r--r--app/helpers/softkeys_helper.rb2
-rw-r--r--app/helpers/system_messages_helper.rb2
-rw-r--r--app/helpers/tenants_helper.rb2
-rw-r--r--app/helpers/user_groups_helper.rb2
-rw-r--r--app/helpers/users_helper.rb2
-rw-r--r--app/helpers/whitelists_helper.rb2
-rw-r--r--app/mailers/.gitkeep0
-rw-r--r--app/mailers/notifications.rb110
-rw-r--r--app/models/.gitkeep0
-rw-r--r--app/models/ability.rb170
-rw-r--r--app/models/access_authorization.rb41
-rw-r--r--app/models/acd_agent.rb39
-rw-r--r--app/models/acd_caller.rb6
-rw-r--r--app/models/address.rb8
-rw-r--r--app/models/api.rb5
-rw-r--r--app/models/api/row.rb152
-rw-r--r--app/models/area_code.rb22
-rw-r--r--app/models/automatic_call_distributor.rb21
-rw-r--r--app/models/call.rb36
-rw-r--r--app/models/call_forward.rb262
-rw-r--r--app/models/call_forward_case.rb13
-rw-r--r--app/models/call_history.rb199
-rw-r--r--app/models/callthrough.rb60
-rw-r--r--app/models/conference.rb63
-rw-r--r--app/models/conference_invitee.rb39
-rw-r--r--app/models/country.rb21
-rw-r--r--app/models/dial_in_number_store.rb16
-rw-r--r--app/models/fax_account.rb77
-rw-r--r--app/models/fax_document.rb82
-rw-r--r--app/models/fax_resolution.rb15
-rw-r--r--app/models/fax_thumbnail.rb8
-rw-r--r--app/models/freeswitch_alias.rb23
-rw-r--r--app/models/freeswitch_call.rb24
-rw-r--r--app/models/freeswitch_cdr.rb4
-rw-r--r--app/models/freeswitch_channel.rb24
-rw-r--r--app/models/freeswitch_complete.rb23
-rw-r--r--app/models/freeswitch_fifo_bridge.rb23
-rw-r--r--app/models/freeswitch_fifo_caller.rb23
-rw-r--r--app/models/freeswitch_fifo_outbound.rb23
-rw-r--r--app/models/freeswitch_interface.rb23
-rw-r--r--app/models/freeswitch_nat.rb23
-rw-r--r--app/models/freeswitch_registration.rb23
-rw-r--r--app/models/freeswitch_task.rb23
-rw-r--r--app/models/freeswitch_voicemail_pref.rb23
-rw-r--r--app/models/gemeinschaft_setup.rb8
-rw-r--r--app/models/gs_cluster_sync_log_entry.rb99
-rw-r--r--app/models/gs_node.rb30
-rw-r--r--app/models/gui_function.rb40
-rw-r--r--app/models/gui_function_membership.rb7
-rw-r--r--app/models/hunt_group.rb43
-rw-r--r--app/models/hunt_group_member.rb67
-rw-r--r--app/models/language.rb11
-rw-r--r--app/models/manufacturer.rb46
-rw-r--r--app/models/oui.rb17
-rw-r--r--app/models/phone.rb240
-rw-r--r--app/models/phone_book.rb33
-rw-r--r--app/models/phone_book_entry.rb109
-rw-r--r--app/models/phone_model.rb56
-rw-r--r--app/models/phone_number.rb304
-rw-r--r--app/models/phone_number_range.rb16
-rw-r--r--app/models/phone_sip_account.rb17
-rw-r--r--app/models/remote_gs_node/gs_cluster_sync_log_entry.rb9
-rw-r--r--app/models/ringtone.rb15
-rw-r--r--app/models/sip_account.rb221
-rw-r--r--app/models/sip_domain.rb16
-rw-r--r--app/models/softkey.rb100
-rw-r--r--app/models/softkey_function.rb13
-rw-r--r--app/models/system_message.rb7
-rw-r--r--app/models/tenant.rb243
-rw-r--r--app/models/tenant_membership.rb25
-rw-r--r--app/models/user.rb208
-rw-r--r--app/models/user_group.rb33
-rw-r--r--app/models/user_group_membership.rb26
-rw-r--r--app/models/voicemail_message.rb52
-rw-r--r--app/models/voicemail_setting.rb12
-rw-r--r--app/models/whitelist.rb23
-rw-r--r--app/uploaders/audio_uploader.rb49
-rw-r--r--app/uploaders/document_uploader.rb49
-rw-r--r--app/uploaders/image_uploader.rb63
-rw-r--r--app/uploaders/thumbnail_uploader.rb62
-rw-r--r--app/uploaders/tiff_uploader.rb49
-rw-r--r--app/views/access_authorizations/_form.html.haml7
-rw-r--r--app/views/access_authorizations/_form_core.html.haml11
-rw-r--r--app/views/access_authorizations/_index_core.html.haml21
-rw-r--r--app/views/access_authorizations/edit.html.haml3
-rw-r--r--app/views/access_authorizations/index.html.haml6
-rw-r--r--app/views/access_authorizations/new.html.haml3
-rw-r--r--app/views/access_authorizations/show.html.haml22
-rw-r--r--app/views/acd_agents/_form.html.haml7
-rw-r--r--app/views/acd_agents/_form_core.html.haml7
-rw-r--r--app/views/acd_agents/_index_core.html.haml18
-rw-r--r--app/views/acd_agents/_listing.html.haml8
-rw-r--r--app/views/acd_agents/edit.html.haml3
-rw-r--r--app/views/acd_agents/index.html.haml6
-rw-r--r--app/views/acd_agents/new.html.haml3
-rw-r--r--app/views/acd_agents/show.html.haml28
-rw-r--r--app/views/acd_callers/_index_core.html.haml21
-rw-r--r--app/views/acd_callers/index.html.haml6
-rw-r--r--app/views/acd_callers/show.html.haml25
-rw-r--r--app/views/addresses/_form.html.haml7
-rw-r--r--app/views/addresses/_form_core.html.haml9
-rw-r--r--app/views/addresses/_index_core.html.haml23
-rw-r--r--app/views/addresses/edit.html.haml9
-rw-r--r--app/views/addresses/index.html.haml6
-rw-r--r--app/views/addresses/new.html.haml3
-rw-r--r--app/views/addresses/show.html.haml28
-rw-r--r--app/views/api/rows/_form.html.erb21
-rw-r--r--app/views/api/rows/edit.html.erb3
-rw-r--r--app/views/api/rows/index.html.erb29
-rw-r--r--app/views/api/rows/new.html.erb3
-rw-r--r--app/views/api/rows/show.html.erb56
-rw-r--r--app/views/automatic_call_distributors/_form.html.haml8
-rw-r--r--app/views/automatic_call_distributors/_form_core.html.haml16
-rw-r--r--app/views/automatic_call_distributors/_index_core.html.haml39
-rw-r--r--app/views/automatic_call_distributors/edit.html.haml3
-rw-r--r--app/views/automatic_call_distributors/index.html.haml6
-rw-r--r--app/views/automatic_call_distributors/new.html.haml3
-rw-r--r--app/views/automatic_call_distributors/show.html.haml59
-rw-r--r--app/views/call_forwards/_form.html.haml7
-rw-r--r--app/views/call_forwards/_form_core.html.haml15
-rw-r--r--app/views/call_forwards/_index_core.html.haml31
-rw-r--r--app/views/call_forwards/edit.html.haml3
-rw-r--r--app/views/call_forwards/index.html.haml6
-rw-r--r--app/views/call_forwards/new.html.haml3
-rw-r--r--app/views/call_forwards/show.html.haml33
-rw-r--r--app/views/call_histories/_index_core.html.haml65
-rw-r--r--app/views/call_histories/_navigation.html.haml11
-rw-r--r--app/views/call_histories/index.html.haml6
-rw-r--r--app/views/calls/_index_core.html.haml9
-rw-r--r--app/views/calls/index.html.haml6
-rw-r--r--app/views/callthroughs/_form.html.haml7
-rw-r--r--app/views/callthroughs/_form_core.html.haml24
-rw-r--r--app/views/callthroughs/_index_core.html.haml17
-rw-r--r--app/views/callthroughs/edit.html.haml3
-rw-r--r--app/views/callthroughs/index.html.haml6
-rw-r--r--app/views/callthroughs/new.html.haml3
-rw-r--r--app/views/callthroughs/show.html.haml27
-rw-r--r--app/views/conference_invitees/_form.html.haml7
-rw-r--r--app/views/conference_invitees/_form_core.html.haml7
-rw-r--r--app/views/conference_invitees/_index_core.html.haml17
-rw-r--r--app/views/conference_invitees/edit.html.haml3
-rw-r--r--app/views/conference_invitees/index.html.haml6
-rw-r--r--app/views/conference_invitees/new.html.haml3
-rw-r--r--app/views/conference_invitees/show.html.haml20
-rw-r--r--app/views/conferences/_form.html.haml7
-rw-r--r--app/views/conferences/_form_core.html.haml11
-rw-r--r--app/views/conferences/_index_core.html.haml53
-rw-r--r--app/views/conferences/edit.html.haml3
-rw-r--r--app/views/conferences/index.html.haml6
-rw-r--r--app/views/conferences/new.html.haml3
-rw-r--r--app/views/conferences/show.html.haml43
-rw-r--r--app/views/config_polycom/_call_history.xml.haml18
-rw-r--r--app/views/config_polycom/_call_history_menu.xml.haml13
-rw-r--r--app/views/config_polycom/_phone_book.xml.haml18
-rw-r--r--app/views/config_polycom/config_files.xml.builder12
-rw-r--r--app/views/config_polycom/idle_screen.xml.haml7
-rw-r--r--app/views/config_polycom/settings.xml.erb8
-rw-r--r--app/views/config_polycom/settings_directory.xml.haml16
-rw-r--r--app/views/config_siemens/_menu_list.xml.haml33
-rw-r--r--app/views/config_siemens/clean-up.xml.erb5
-rw-r--r--app/views/config_siemens/index.xml.erb5
-rw-r--r--app/views/config_siemens/write.xml.erb10
-rw-r--r--app/views/config_snom/_snom_phone_directory.xml.haml19
-rw-r--r--app/views/config_snom/_snom_phone_input.xml.haml19
-rw-r--r--app/views/config_snom/_snom_phone_menu.xml.haml17
-rw-r--r--app/views/config_snom/_snom_phone_text.xml.haml16
-rw-r--r--app/views/config_snom/call_history.xml.haml2
-rw-r--r--app/views/config_snom/idle_screen.xml.haml33
-rw-r--r--app/views/config_snom/log_in.xml.haml3
-rw-r--r--app/views/config_snom/show.xml.haml151
-rw-r--r--app/views/config_snom/state_settings.xml.haml49
-rw-r--r--app/views/config_snom/switch_protocol.xml.builder18
-rw-r--r--app/views/fax_accounts/_form.html.haml7
-rw-r--r--app/views/fax_accounts/_form_core.html.haml11
-rw-r--r--app/views/fax_accounts/_index_core.html.haml35
-rw-r--r--app/views/fax_accounts/edit.html.haml3
-rw-r--r--app/views/fax_accounts/index.html.haml6
-rw-r--r--app/views/fax_accounts/new.html.haml3
-rw-r--r--app/views/fax_accounts/show.html.haml21
-rw-r--r--app/views/fax_documents/_form.html.haml7
-rw-r--r--app/views/fax_documents/_form_core.html.haml7
-rw-r--r--app/views/fax_documents/_index_core.html.haml33
-rw-r--r--app/views/fax_documents/edit.html.haml9
-rw-r--r--app/views/fax_documents/index.html.haml5
-rw-r--r--app/views/fax_documents/new.html.haml3
-rw-r--r--app/views/fax_documents/show.html.haml36
-rw-r--r--app/views/freeswitch_voicemail_msgs/_index_core.html.haml12
-rw-r--r--app/views/freeswitch_voicemail_msgs/index.html.haml3
-rw-r--r--app/views/gemeinschaft_setups/new.de.html.haml25
-rw-r--r--app/views/gemeinschaft_setups/new.html.haml25
-rw-r--r--app/views/gs_cluster_sync_log_entries/_form.html.haml7
-rw-r--r--app/views/gs_cluster_sync_log_entries/_form_core.html.haml6
-rw-r--r--app/views/gs_cluster_sync_log_entries/_index_core.html.haml17
-rw-r--r--app/views/gs_cluster_sync_log_entries/edit.html.haml3
-rw-r--r--app/views/gs_cluster_sync_log_entries/index.html.haml6
-rw-r--r--app/views/gs_cluster_sync_log_entries/new.html.haml3
-rw-r--r--app/views/gs_cluster_sync_log_entries/show.html.haml19
-rw-r--r--app/views/gs_nodes/_form.html.haml7
-rw-r--r--app/views/gs_nodes/_form_core.html.haml7
-rw-r--r--app/views/gs_nodes/_index_core.html.haml19
-rw-r--r--app/views/gs_nodes/edit.html.haml3
-rw-r--r--app/views/gs_nodes/index.html.haml6
-rw-r--r--app/views/gs_nodes/new.html.haml3
-rw-r--r--app/views/gs_nodes/show.html.haml22
-rw-r--r--app/views/gs_nodes/sync.xml.haml88
-rw-r--r--app/views/gui_functions/_form.html.haml7
-rw-r--r--app/views/gui_functions/_form_core.html.haml10
-rw-r--r--app/views/gui_functions/_index_core.html.haml26
-rw-r--r--app/views/gui_functions/edit.html.haml3
-rw-r--r--app/views/gui_functions/index.html.haml6
-rw-r--r--app/views/gui_functions/new.html.haml3
-rw-r--r--app/views/gui_functions/show.html.haml18
-rw-r--r--app/views/hunt_group_members/_form.html.haml7
-rw-r--r--app/views/hunt_group_members/_form_core.html.haml4
-rw-r--r--app/views/hunt_group_members/_index_core.html.haml20
-rw-r--r--app/views/hunt_group_members/_listing.html.haml8
-rw-r--r--app/views/hunt_group_members/edit.html.haml3
-rw-r--r--app/views/hunt_group_members/index.html.haml6
-rw-r--r--app/views/hunt_group_members/new.html.haml3
-rw-r--r--app/views/hunt_group_members/show.html.haml19
-rw-r--r--app/views/hunt_groups/_form.html.haml7
-rw-r--r--app/views/hunt_groups/_form_core.html.haml4
-rw-r--r--app/views/hunt_groups/_index_core.html.haml34
-rw-r--r--app/views/hunt_groups/edit.html.haml3
-rw-r--r--app/views/hunt_groups/index.html.haml6
-rw-r--r--app/views/hunt_groups/new.html.haml3
-rw-r--r--app/views/hunt_groups/show.html.haml26
-rw-r--r--app/views/layouts/application.html.haml46
-rw-r--r--app/views/manufacturers/_form.html.haml7
-rw-r--r--app/views/manufacturers/_form_core.html.haml4
-rw-r--r--app/views/manufacturers/_index_core.html.haml18
-rw-r--r--app/views/manufacturers/edit.html.haml3
-rw-r--r--app/views/manufacturers/index.html.haml5
-rw-r--r--app/views/manufacturers/new.html.haml3
-rw-r--r--app/views/manufacturers/show.html.haml18
-rw-r--r--app/views/notifications/new_fax.text.erb8
-rw-r--r--app/views/notifications/new_password.text.erb3
-rw-r--r--app/views/notifications/new_pin.text.erb7
-rw-r--r--app/views/notifications/new_voicemail.text.erb9
-rw-r--r--app/views/page/conference.html.haml80
-rw-r--r--app/views/page/index.de.html.haml16
-rw-r--r--app/views/page/index.html.haml16
-rw-r--r--app/views/phone_book_entries/_form.html.haml7
-rw-r--r--app/views/phone_book_entries/_form_core.html.haml27
-rw-r--r--app/views/phone_book_entries/_index_core.de.html.haml36
-rw-r--r--app/views/phone_book_entries/_index_core.html.haml36
-rw-r--r--app/views/phone_book_entries/_navigation.html.haml8
-rw-r--r--app/views/phone_book_entries/edit.html.haml9
-rw-r--r--app/views/phone_book_entries/index.html.haml19
-rw-r--r--app/views/phone_book_entries/new.html.haml3
-rw-r--r--app/views/phone_book_entries/show.html.haml146
-rw-r--r--app/views/phone_book_entries/show.html.haml.examlple194
-rw-r--r--app/views/phone_books/_form.html.haml7
-rw-r--r--app/views/phone_books/_form_core.html.haml3
-rw-r--r--app/views/phone_books/_index_core.html.haml16
-rw-r--r--app/views/phone_books/edit.html.haml3
-rw-r--r--app/views/phone_books/index.html.haml6
-rw-r--r--app/views/phone_books/new.html.haml3
-rw-r--r--app/views/phone_books/show.html.haml13
-rw-r--r--app/views/phone_models/_form.html.haml7
-rw-r--r--app/views/phone_models/_form_core.html.haml4
-rw-r--r--app/views/phone_models/_index_core.html.haml19
-rw-r--r--app/views/phone_models/edit.html.haml3
-rw-r--r--app/views/phone_models/index.html.haml6
-rw-r--r--app/views/phone_models/new.html.haml3
-rw-r--r--app/views/phone_models/show.html.haml18
-rw-r--r--app/views/phone_number_ranges/_form.html.haml7
-rw-r--r--app/views/phone_number_ranges/_form_core.html.haml3
-rw-r--r--app/views/phone_number_ranges/_index_core.html.haml21
-rw-r--r--app/views/phone_number_ranges/edit.html.haml3
-rw-r--r--app/views/phone_number_ranges/index.html.haml6
-rw-r--r--app/views/phone_number_ranges/new.html.haml3
-rw-r--r--app/views/phone_number_ranges/show.html.haml18
-rw-r--r--app/views/phone_numbers/_form.html.haml7
-rw-r--r--app/views/phone_numbers/_form_core.html.haml10
-rw-r--r--app/views/phone_numbers/_index_core.html.haml13
-rw-r--r--app/views/phone_numbers/_listing.html.haml8
-rw-r--r--app/views/phone_numbers/edit.html.haml3
-rw-r--r--app/views/phone_numbers/index.html.haml6
-rw-r--r--app/views/phone_numbers/new.html.haml3
-rw-r--r--app/views/phone_numbers/show.html.haml27
-rw-r--r--app/views/phone_sip_accounts/_form.html.haml7
-rw-r--r--app/views/phone_sip_accounts/_form_core.html.haml2
-rw-r--r--app/views/phone_sip_accounts/_index_core.html.haml13
-rw-r--r--app/views/phone_sip_accounts/index.html.haml6
-rw-r--r--app/views/phone_sip_accounts/new.html.haml3
-rw-r--r--app/views/phone_sip_accounts/show.html.haml13
-rw-r--r--app/views/phones/_form.html.haml7
-rw-r--r--app/views/phones/_form_core.html.haml8
-rw-r--r--app/views/phones/_index_core.html.haml15
-rw-r--r--app/views/phones/edit.html.haml3
-rw-r--r--app/views/phones/index.html.haml6
-rw-r--r--app/views/phones/new.html.haml3
-rw-r--r--app/views/phones/show.html.haml31
-rw-r--r--app/views/ringtones/_form.html.haml7
-rw-r--r--app/views/ringtones/_form_core.html.haml3
-rw-r--r--app/views/ringtones/_index_core.html.haml11
-rw-r--r--app/views/ringtones/edit.html.haml3
-rw-r--r--app/views/ringtones/index.html.haml6
-rw-r--r--app/views/ringtones/new.html.haml3
-rw-r--r--app/views/ringtones/show.html.haml12
-rw-r--r--app/views/sessions/new.html.haml8
-rw-r--r--app/views/shared/_create_link.html.haml11
-rw-r--r--app/views/shared/_flash.html.haml19
-rw-r--r--app/views/shared/_header.de.html.haml41
-rw-r--r--app/views/shared/_header.html.haml41
-rw-r--r--app/views/shared/_index_view_edit_destroy_part.html.haml29
-rw-r--r--app/views/shared/_show_edit_destroy_part.html.haml16
-rw-r--r--app/views/shared/_system_message.html.haml10
-rw-r--r--app/views/sip_accounts/_form.html.haml7
-rw-r--r--app/views/sip_accounts/_form_core.html.haml12
-rw-r--r--app/views/sip_accounts/_index_core.html.haml28
-rw-r--r--app/views/sip_accounts/edit.html.haml3
-rw-r--r--app/views/sip_accounts/index.html.haml6
-rw-r--r--app/views/sip_accounts/new.html.haml3
-rw-r--r--app/views/sip_accounts/show.html.haml50
-rw-r--r--app/views/sip_domains/_form.html.haml7
-rw-r--r--app/views/sip_domains/_form_core.html.haml3
-rw-r--r--app/views/sip_domains/_index_core.html.haml11
-rw-r--r--app/views/sip_domains/edit.html.haml3
-rw-r--r--app/views/sip_domains/index.html.haml6
-rw-r--r--app/views/sip_domains/new.html.haml3
-rw-r--r--app/views/sip_domains/show.html.haml10
-rw-r--r--app/views/softkeys/_form.html.haml7
-rw-r--r--app/views/softkeys/_form_core.html.haml12
-rw-r--r--app/views/softkeys/_index_core.html.haml14
-rw-r--r--app/views/softkeys/edit.html.haml3
-rw-r--r--app/views/softkeys/index.html.haml6
-rw-r--r--app/views/softkeys/new.html.haml3
-rw-r--r--app/views/softkeys/show.html.haml7
-rw-r--r--app/views/system_messages/_form.html.haml7
-rw-r--r--app/views/system_messages/_form_core.html.haml2
-rw-r--r--app/views/system_messages/_index_core.html.haml11
-rw-r--r--app/views/system_messages/index.html.haml3
-rw-r--r--app/views/system_messages/new.html.haml3
-rw-r--r--app/views/system_messages/show.html.haml8
-rw-r--r--app/views/tenants/_admin_area.de.html.haml118
-rw-r--r--app/views/tenants/_admin_area.html.haml116
-rw-r--r--app/views/tenants/_form.html.haml24
-rw-r--r--app/views/tenants/_form_core.html.haml3
-rw-r--r--app/views/tenants/_index_core.html.haml17
-rw-r--r--app/views/tenants/edit.html.haml3
-rw-r--r--app/views/tenants/index.html.haml6
-rw-r--r--app/views/tenants/new.html.haml3
-rw-r--r--app/views/tenants/show.html.haml14
-rw-r--r--app/views/user_group_memberships/_form.html.haml7
-rw-r--r--app/views/user_group_memberships/_form_core.html.haml2
-rw-r--r--app/views/user_group_memberships/_index_core.html.haml13
-rw-r--r--app/views/user_group_memberships/edit.html.haml3
-rw-r--r--app/views/user_group_memberships/index.html.haml7
-rw-r--r--app/views/user_group_memberships/new.html.haml3
-rw-r--r--app/views/user_group_memberships/show.html.haml7
-rw-r--r--app/views/user_groups/_form.html.haml7
-rw-r--r--app/views/user_groups/_form_core.html.haml3
-rw-r--r--app/views/user_groups/_index_core.html.haml24
-rw-r--r--app/views/user_groups/edit.html.haml3
-rw-r--r--app/views/user_groups/index.html.haml6
-rw-r--r--app/views/user_groups/new.html.haml3
-rw-r--r--app/views/user_groups/show.html.haml20
-rw-r--r--app/views/users/_form.html.haml16
-rw-r--r--app/views/users/_form_core.html.haml27
-rw-r--r--app/views/users/_index_core.html.haml18
-rw-r--r--app/views/users/_listing.html.haml8
-rw-r--r--app/views/users/edit.html.haml3
-rw-r--r--app/views/users/index.html.haml6
-rw-r--r--app/views/users/new.html.haml3
-rw-r--r--app/views/users/show.html.haml96
-rw-r--r--app/views/voicemail_messages/_index_core.html.haml44
-rw-r--r--app/views/voicemail_messages/_navigation.html.haml9
-rw-r--r--app/views/voicemail_messages/index.html.haml6
-rw-r--r--app/views/voicemail_settings/_form.html.haml7
-rw-r--r--app/views/voicemail_settings/_form_core.html.haml11
-rw-r--r--app/views/voicemail_settings/edit.html.haml3
-rw-r--r--app/views/voicemail_settings/show.html.haml26
-rw-r--r--app/views/whitelists/_form.html.haml7
-rw-r--r--app/views/whitelists/_form_core.html.haml8
-rw-r--r--app/views/whitelists/_index_core.html.haml15
-rw-r--r--app/views/whitelists/edit.html.haml3
-rw-r--r--app/views/whitelists/index.html.haml6
-rw-r--r--app/views/whitelists/new.html.haml3
-rw-r--r--app/views/whitelists/show.html.haml7
555 files changed, 21265 insertions, 0 deletions
diff --git a/app/assets/images/amooma-logo.png b/app/assets/images/amooma-logo.png
new file mode 100644
index 0000000..11096ff
--- /dev/null
+++ b/app/assets/images/amooma-logo.png
Binary files differ
diff --git a/app/assets/images/bg-body.png b/app/assets/images/bg-body.png
new file mode 100644
index 0000000..777eff6
--- /dev/null
+++ b/app/assets/images/bg-body.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/gradients/light-to-dark-blue-x63.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/gradients/white-gray-x29-reverse.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/gradients/white-gray-x29.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/gradients/white-texture-x63.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/cellphone-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/clock-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/cross-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/facebook-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/fax-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/headphones-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/headphones-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/house-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/mic-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/microphone-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/microphone-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/mute-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/phone-down-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/phone-mobile-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/phone-up-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/search-13x16.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/skype-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/star-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/suitcase-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/tag-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/twitter-32x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/unmute-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/user-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/user-female-16x.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/icons/user-male-16x.png
Binary files differ
diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png
new file mode 100644
index 0000000..e4432b8
--- /dev/null
+++ b/app/assets/images/logo.png
Binary files differ
diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png
new file mode 100644
index 0000000..d5edc04
--- /dev/null
+++ b/app/assets/images/rails.png
Binary files 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
--- /dev/null
+++ b/app/assets/images/stubs/user-36x.jpg
Binary files differ
diff --git a/app/assets/images/user.png b/app/assets/images/user.png
new file mode 100644
index 0000000..c0e33c7
--- /dev/null
+++ b/app/assets/images/user.png
Binary files 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("<div/>")[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('<p id="fancybox-error">The requested content cannot be loaded.<br />Please try again later.</p>');
+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('<div class="fancybox-inline-tmp" />').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("<img />").attr({id:"fancybox-img",src:v.src,alt:e.title}).appendTo(m);Q()};v.src=c;break;case "swf":e.scrolling="no";C='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+e.width+'" height="'+e.height+'"><param name="movie" value="'+c+
+'"></param>';P="";b.each(e.swf,function(x,H){C+='<param name="'+x+'" value="'+H+'"></param>';P+=" "+x+'="'+H+'"'});C+='<embed src="'+c+'" type="application/x-shockwave-flash" width="'+e.width+'" height="'+e.height+'"'+P+"></embed></object>";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('<div style="width:'+a+";height:"+c+
+";overflow: "+(e.scrolling=="auto"?"auto":e.scrolling=="yes"?"scroll":"hidden")+';position:relative;"></div>');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"?'<table id="fancybox-title-float-wrap" cellpadding="0" cellspacing="0"><tr><td id="fancybox-title-float-left"></td><td id="fancybox-title-float-main">'+s+'</td><td id="fancybox-title-float-right"></td></tr></table>':'<div id="fancybox-title-'+d.titlePosition+'">'+s+"</div>":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('<iframe id="fancybox-frame" name="fancybox-frame'+(new Date).getTime()+'" frameborder="0" hspace="0" '+(b.browser.msie?'allowtransparency="true""':"")+' scrolling="'+e.scrolling+'" src="'+d.href+'"></iframe>').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;k<C;k++)if(typeof a[k]=="object")b(a[k]).data("fancybox",b.extend({},g,a[k]));else a[k]=b({}).data("fancybox",b.extend({content:a[k]},g));o=jQuery.merge(o,a)}else{if(typeof a=="object")b(a).data("fancybox",b.extend({},g,a));else a=b({}).data("fancybox",b.extend({content:a},g));o.push(a)}if(q>o.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&&a<l.length){q=a;I()}else if(d.cyclic&&l.length>1){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('<div id="fancybox-tmp"></div>'),t=b('<div id="fancybox-loading"><div></div></div>'),u=b('<div id="fancybox-overlay"></div>'),f=b('<div id="fancybox-wrap"></div>'));D=b('<div id="fancybox-outer"></div>').append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>').appendTo(f);
+D.append(j=b('<div id="fancybox-content"></div>'),E=b('<a id="fancybox-close"></a>'),n=b('<div id="fancybox-title"></div>'),z=b('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'),A=b('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>'));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('<iframe id="fancybox-hide-sel-frame" src="'+(/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank")+'" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').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"?"<!doctype html>":"")+"<html><body>"),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;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function bZ(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bY(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bC.test(a)?d(a,e):bY(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)bY(a+"["+e+"]",b[e],c,d);else d(a,b)}function bX(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bR,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bX(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bX(a,c,d,e,"*",g));return l}function bW(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bN),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bA(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bv:bw;if(d>0){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<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bg(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=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;i<s.length;i++)g=s[i],g.origType.replace(x,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)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(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)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(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(H)return H.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?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<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;a.setAttribute("className","t"),a.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",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="<div style='width:4px;'></div>",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",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<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:|^on/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(o);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(o);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(n," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-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<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=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<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=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<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,N(a.origType,a.selector),f.extend({},a,{handler:M,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,N(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?E:D):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=E;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=E;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=E,this.stopPropagation()},isDefaultPrevented:D,isPropagationStopped:D,isImmediatePropagationStopped:D};var F=function(a){var b=a.relatedTarget,c=!1,d=a.type;a.type=a.data,b!==this&&(b&&(c=f.contains(this,b)),c||(f.event.handle.apply(this,arguments),a.type=d))},G=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?G:F,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?G:F)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&K("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&K("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var H,I=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-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;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var L={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||D,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=x.exec(h),k="",j&&(k=j[0],h=h.replace(x,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,L[h]?(a.push(L[h]+k),h=h+k):h=(L[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+N(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+N(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?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;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){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<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\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;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=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 b<c[3]-0},gt:function(a,b,c){return b>c[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<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=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<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",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 href='#'></a>",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="<p class='TEST'></p>";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="<div class='test e'></div><div class='test'></div>";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;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0: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<e;d++)i=a[d],j[i]||(j[i]=T.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-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<e;d++){g=this[d];while(g){if(l?l.index(g)>-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:]+)/,_=/<tbody/i,ba=/<|&#?\w+;/,bb=/<(?:script|object|embed|option|style)/i,bc=/checked\s*(?:[^=]|=\s*.checked.)/i,bd=/\/(java|ecma)script/i,be=/^\s*<!(?:\[CDATA\[|\-\-)/,bf={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_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<div>","</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></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bc.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bg(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bm)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i;b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bb.test(a[0])&&(f.support.checkClone||!bc.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?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></$2>");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]==="<table>"&&!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<r;i++)bl(k[i]);else bl(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||bd.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bn=/alpha\([^)]*\)/i,bo=/opacity=([^)]*)/,bp=/([A-Z]|^ms)/g,bq=/^-?\d+(?:px)?$/i,br=/^-?\d/,bs=/^[+\-]=/,bt=/[^+\-\.\de]+/g,bu={position:"absolute",visibility:"hidden",display:"block"},bv=["Left","Right"],bw=["Top","Bottom"],bx,by,bz;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bx(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bs.test(d)&&(d=+d.replace(bt,"")+parseFloat(f.css(a,c)),h="number"),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bx)return bx(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bA(a,b,d);f.swap(a,bu,function(){e=bA(a,b,d)});return e}},set:function(a,b){if(!bq.test(b))return b;b=parseFloat(b);if(b>=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\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/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("<div>").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<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cs(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cr("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cr("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cs(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cj.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=ck.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cr("show",1),slideUp:cr("hide",1),slideToggle:cr("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cn||cp(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!cl&&(co?(cl=!0,g=function(){cl&&(co(g),e.tick())},co(g)):cl=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cn||cp(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=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<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cl),cl=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var ct=/^t(?:able|d|h)$/i,cu=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cv(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!ct.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";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();
+ *
+ * <div id="slider">
+ * <ul>
+ * <li><img src="images/01.jpg" alt="" /></li>
+ * <li><img src="images/02.jpg" alt="" /></li>
+ * <li><img src="images/03.jpg" alt="" /></li>
+ * <li><img src="images/04.jpg" alt="" /></li>
+ * <li><img src="images/05.jpg" alt="" /></li>
+ * </ul>
+ * </div>
+ *
+ */
+
+(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 += '<ol id="'+ options.numericId +'"></ol>';
+ } else {
+ if(options.firstShow) html += '<span id="'+ options.firstId +'"><a href=\"javascript:void(0);\">'+ options.firstText +'</a></span>';
+ html += ' <span id="'+ options.prevId +'"><a href=\"javascript:void(0);\">'+ options.prevText +'</a></span>';
+ html += ' <span id="'+ options.nextId +'"><a href=\"javascript:void(0);\">'+ options.nextText +'</a></span>';
+ if(options.lastShow) html += ' <span id="'+ options.lastId +'"><a href=\"javascript:void(0);\">'+ options.lastText +'</a></span>';
+ };
+
+ html += options.controlsAfter;
+ $(obj).after(html);
+ };
+
+ if(options.numeric){
+ for(var i=0;i<s;i++){
+ $(document.createElement("li"))
+ .attr('id',options.numericId + (i+1))
+ .html('<a rel='+ i +' href=\"javascript:void(0);\">'+ (i+1) +'</a>')
+ .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("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;");
+ }
+ });
+
+ 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
+ * <html> 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);
+ }
+ }
+
+ // <style> elements in IE6-9 are considered 'NoScope' elements and therefore will be removed
+ // when injected with innerHTML. To get around this you need to prepend the 'NoScope' element
+ // with a 'scoped' element, in our case the soft-hyphen entity as it won't mess with our measurements.
+ // http://msdn.microsoft.com/en-us/library/ms533897%28VS.85%29.aspx
+ style = ['&shy;', '<style>', rule, '</style>'].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 = '<svg/>';
+ 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 <input> 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 = '<elem></elem>';
+ 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 <font> 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 <html> element, if it exists:
+ docElement.className = docElement.className.replace(/\bno-js\b/, '')
+
+ // Add the new classes to the <html> 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
+// ----------------------------------------
+// <div class="blog-item">
+// <h6 class="date">14 de Julio 2010</h6>
+// <h3><a href="#">Lorem My Ipsum</a></h3>
+// <img src="image.jpg" width="194" height="146" alt="Blog Thumb"/>
+// <p>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...</p>
+// <p class="read-more"><a href="#">Nota Completa</a></p>
+// </div>
+@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
+// ----------------------------------------
+// <div class="pagination">
+// <span class="previous_page disabled">← Previous</span>
+// <em>1</em>
+// <a rel="next" href="/?page=2">2</a>
+// <a href="/?page=3">3</a>
+// <a href="/?page=4">4</a>
+// <a class="next_page" rel="next" href="/?page=2">Next →</a>
+// </div>
+
+// 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.
+//
+// <form action="#" accept-charset="utf-8" class="search-box">
+// <input type="text" class="text" value="Search..." name="q" />
+// <input type="submit" class="button" value="" />
+// </form>
+@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
+// <h1><a href=""></a></h1>
+@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 => "<!-- Phone not found -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- MAC not specified -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- Tenant not found -->",
+ )
+ 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 => "<!-- #{@phone.errors.messages.inspect} -->",
+ )
+ 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 => "<!-- #{@sip_account.errors.messages.inspect} -->",
+ )
+ 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 => "<!-- #{phone_sip_account.errors.messages.inspect} -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- Call forwarding not set: #{@sip_account.errors.messages.inspect} -->",
+ )
+ 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 => "<!-- ERROR #{error_messages.join(',')} #{call_forwarding.to_s}) -->",
+ )
+ elsif call_forwarding.active
+ render(
+ :status => 200,
+ :layout => false,
+ :content_type => 'text/plain',
+ :text => "<!-- ON #{call_forwarding.to_s} -->",
+ )
+ else
+ render(
+ :status => 200,
+ :layout => false,
+ :content_type => 'text/plain',
+ :text => "<!-- OFF #{call_forwarding.to_s} -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- HuntGroupMember not found -->",
+ )
+ 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 => "<!-- #{hunt_group_member.errors.inspect} -->",
+ )
+ else
+ render(
+ :status => 200,
+ :layout => false,
+ :content_type => 'text/plain',
+ :text => "<!-- Member #{hunt_group_member.id} toggled -->",
+ )
+ 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 => "<!-- Phone or SipAccount not found -->",
+ )
+ 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 => "<!-- Tenant not found -->",
+ )
+ 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 => "<!-- #{@phone.errors.messages.inspect} -->",
+ )
+ 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 => "<!-- #{@sip_account.errors.messages.inspect} -->",
+ )
+ 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 => "<!-- #{phone_sip_account.errors.messages.inspect} -->",
+ )
+ 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 => "<!-- Phone not found -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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:#{sip_account.auth_name}@#{sip_account.host}>"
+ }
+ @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 <sip:#{softkey.number}@#{sip_account.host}>|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 <sip:#{softkey.number}@#{sip_account.host}>|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 => "<!-- Phone not found -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ 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 => "<!-- Call forwarding not set: #{@sip_account.errors.messages.inspect} -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ return
+ end
+
+ if ! @hunt_group
+ render(
+ :status => 404,
+ :layout => false,
+ :content_type => 'text/plain',
+ :text => "<!-- HuntGroup not found -->",
+ )
+ 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 => "<!-- HuntGroupMember not found -->",
+ )
+ 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 => "<!-- #{hunt_group_member.errors.inspect} -->",
+ )
+ 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 => "<!-- SipAccount not found -->",
+ )
+ return
+ end
+
+ if ! @acd
+ render(
+ :status => 404,
+ :layout => false,
+ :content_type => 'text/plain',
+ :text => "<!-- AutomaticCallDistributor not found -->",
+ )
+ 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 => "<!-- ACD Agent not found -->",
+ )
+ 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 => "<!-- #{acd_agent.errors.inspect} -->",
+ )
+ 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 => "<exit />",
+ )
+ 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 => "<!-- Document not found -->",
+ )
+ 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 => "<!-- Node not found -->",
+ )
+ 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('<span class=\"created_at\">#{(I18n.l @system_message.created_at, :format => :short )}</span> #{@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 => "<!-- Message not found -->",
+ )
+ 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
--- /dev/null
+++ b/app/mailers/.gitkeep
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
--- /dev/null
+++ b/app/models/.gitkeep
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 %>
+
+ <div class="inputs">
+ <%= 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 %>
+ </div>
+
+ <div class="actions">
+ <%= f.button :submit %>
+ </div>
+<% 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 @@
+<h1>Editing row</h1>
+
+<%= 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 @@
+<h1>Listing rows</h1>
+
+<table>
+ <tr>
+ <th>ID</th>
+ <th>User name</th>
+ <th>Last name</th>
+ <th>First name</th>
+ <th>Internal extension</th>
+ <th>Office phone number</th>
+ <th>Fax phone number</th>
+ <th>Email</th>
+ <th>Pin</th>
+ </tr>
+
+<% @rows.each do |row| %>
+ <tr class='<%= cycle('odd', 'even') %>'>
+ <td><%= row.id %></td>
+ <td><%= link_to row.user_name, tenant_user_path(row.user.current_tenant, row.user) %></td>
+ <td><%= row.last_name %></td>
+ <td><%= row.first_name %></td>
+ <td><%= row.internal_extension %></td>
+ <td><%= row.office_phone_number %></td>
+ <td><%= row.fax_phone_number %></td>
+ <td><%= row.email %></td>
+ <td><%= row.pin %></td>
+ </tr>
+<% end %>
+</table> \ 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 @@
+<h1>New row</h1>
+
+<%= 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 @@
+<p id="notice"><%= notice %></p>
+
+<p>
+ <b>User name:</b>
+ <%= @row.user_name %>
+</p>
+
+<p>
+ <b>Last name:</b>
+ <%= @row.last_name %>
+</p>
+
+<p>
+ <b>Middle name:</b>
+ <%= @row.middle_name %>
+</p>
+
+<p>
+ <b>First name:</b>
+ <%= @row.first_name %>
+</p>
+
+<p>
+ <b>Office phone number:</b>
+ <%= @row.office_phone_number %>
+</p>
+
+<p>
+ <b>Internal extension:</b>
+ <%= @row.internal_extension %>
+</p>
+
+<p>
+ <b>Mobile phone number:</b>
+ <%= @row.mobile_phone_number %>
+</p>
+
+<p>
+ <b>Fax phone number:</b>
+ <%= @row.fax_phone_number %>
+</p>
+
+<p>
+ <b>Email:</b>
+ <%= @row.email %>
+</p>
+
+<p>
+ <b>Pin:</b>
+ <%= @row.pin %>
+</p>
+
+<p>
+ <b>Photo file name:</b>
+ <%= @row.photo_file_name %>
+</p> \ 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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<PHONE_CONFIG>
+ <ALL
+ <% @settings.each do |key, value| %>
+ <%= key %>="<%= value %>"
+ <% end %>
+ />
+</PHONE_CONFIG>
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 @@
+<DlsMessage xsi:schemaLocation="http://www.siemens.com/DLS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.siemens.com/DLS">
+ <Message nonce="<%=@my_nonce%>">
+ <Action>CleanUp</Action>
+ </Message>
+</DlsMessage> \ 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 @@
+<DlsMessage xsi:schemaLocation="http://www.siemens.com/DLS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.siemens.com/DLS">
+ <Message nonce="<%=@my_nonce%>">
+ <Action>ReadAllItems</Action>
+ </Message>
+</DlsMessage> \ 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 @@
+<DlsMessage xsi:schemaLocation="http://www.siemens.com/DLS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.siemens.com/DLS">
+ <Message nonce="<%=@my_nonce%>">
+ <Action>WriteItems</Action>
+ <ItemList>
+ <% @new_settings.each do |setting| %>
+ <Item name="<%=setting[0]%>" <% if ! setting[1].nil? %>index="<%=setting[1]%>"<%end%>><%=setting[2]%></Item>
+ <% end %>
+ </ItemList>
+ </Message>
+</DlsMessage> \ 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]
+
+ != "\<!-- sip accounts: #{@sip_accounts.count} --\>"
+ - @sip_accounts.each_with_index do |sip_account, array_index|
+ - index = array_index + 1
+ != "\<!-- sip account #{array_index}: #{sip_account[:idle_text]}, #{sip_account[:active]} --\>"
+ %user_active{:idx => index, :perm => 'R'}= sip_account[:active]
+ %user_pname{:idx => index, :perm => 'R'}= sip_account[:pname]
+ %user_pass{:idx => index, :perm => 'R'}= sip_account[:pass]
+ %user_host{:idx => index, :perm => 'R'}= sip_account[:host]
+ %user_outbound{:idx => index, :perm => 'R'}= sip_account[:outbound]
+ %user_name{:idx => index, :perm => 'R'}= sip_account[:name]
+ %user_realname{:idx => index, :perm => 'R'}= sip_account[:realname]
+ %user_idle_text{:idx => index, :perm => 'R'}= sip_account[:idle_text]
+ %user_mailbox{:idx => index, :perm => 'R'}= sip_account[:mailbox]
+ %user_expiry{:idx => index, :perm => 'R'}= ''
+ %user_server_type{:idx => index, :perm => 'R'}= 'default'
+ %user_send_local_name{:idx => index, :perm => 'RW'}= 'on'
+ %user_dtmf_info{:idx => index, :perm => 'RW'}= 'off'
+ %user_dp_exp{:idx => index, :perm => 'RW'}= ''
+ %user_dp_str{:idx => index, :perm => 'RW'}= ''
+ %user_dp{:idx => index, :perm => 'RW'}= ''
+ %user_q{:idx => index, :perm => 'RW'}= '1.0'
+ %user_failover_identity{:idx => index, :perm => 'RW'}= 'none'
+ %user_full_sdp_answer{:idx => index, :perm => 'RW'}= 'on'
+ %user_dynamic_payload{:idx => index, :perm => 'RW'}= 'on'
+ %user_g726_packing_order{:idx => index, :perm => 'R'}= 'on'
+ %user_srtp{:idx => index, :perm => 'RW'}= 'off'
+ %user_savp{:idx => index, :perm => 'RW'}= 'off'
+ %codec_size{:idx => index, :perm => 'RW'}= '20'
+ %codec1_name{:idx => index, :perm => 'RW'}= "0"
+ %codec2_name{:idx => index, :perm => 'RW'}= "8"
+ %codec3_name{:idx => index, :perm => 'RW'}= "3"
+ %codec4_name{:idx => index, :perm => 'RW'}= "9"
+ %codec5_name{:idx => index, :perm => 'RW'}= "2"
+ %codec6_name{:idx => index, :perm => 'RW'}= "18"
+ %codec7_name{:idx => index, :perm => 'RW'}= "4"
+ %record_missed_calls{:idx => index, :perm => 'RW'}= 'on'
+ %record_received_calls{:idx => index, :perm => 'RW'}= 'off'
+ %record_missed_calls_cwi_off{:idx => index, :perm => 'RW'}= 'off'
+ %record_dialed_calls{:idx => index, :perm => 'RW'}= 'off'
+
+ / all sip accounts done
+
+ %functionKeys
+ - @softkeys.each_with_index do |softkey, index|
+ - if softkey[:data]
+ %fkey{:idx => index, :context => (softkey[:context] ? softkey[:context].to_s : 'active'), :label => softkey[:label], :perm => 'RW'}= softkey[:data]
+ - elsif softkey[:general_type]
+ %fkey{:idx => index, :context => (softkey[:context] ? softkey[:context].to_s : 'active'), :label => softkey[:label], :perm => 'RW'}
+ %general{:type => softkey[:general_type]}
+ %default_state{:name => 'initial'}
+ %appearance
+ %line_info_layer
+ %line_format{:line => '0'}= '$state $type'
+ %line_format{:line => '1'}= '$continue $name'
+ - if softkey[:subscription]
+ %subscription{:type => 'dialog', :to => softkey[:subscription][:to], :for => softkey[:subscription][:for]}
+ %NotifyParsingRules{:type => 'applies'}
+ %level1{:translates_to => 'OK'}= "/dialog-info[@entity=\"sip:#{softkey[:subscription][:to]}\"]"
+ %NotifyParsingRules{:type => 'state'}
+ %level1{:translates_to => 'available'}= '/dialog-info/dialog/state[.="terminated"]'
+ %level2{:translates_to => 'ringing'}= '/dialog-info/dialog/state[.="early"]'
+ %level3{:translates_to => 'active'}= '/dialog-info/dialog/state[.="confirmed"]'
+ %level4{:fetch_content => 'true'}= '/dialog-info/dialog/state'
+ %default{:translates_to => 'unknown'}
+ - if softkey[:actions]
+ %action
+ - softkey[:actions].each do |action|
+ - if action[:type] == :url
+ %url{:target => action[:target], :when => action[:when]}
+
+ %uploads
+ - if @state_settings_url
+ %file{:url => @state_settings_url, :type => "gui_xml_state_settings"}
diff --git a/app/views/config_snom/state_settings.xml.haml b/app/views/config_snom/state_settings.xml.haml
new file mode 100644
index 0000000..ac0e872
--- /dev/null
+++ b/app/views/config_snom/state_settings.xml.haml
@@ -0,0 +1,49 @@
+!!! XML
+%SnomIPPhoneMenu{:state => 'relevant', :title => "Gemeinschaft #{GEMEINSCHAFT_VERSION}"}
+ %MenuItem{:name => '$(lang:menu100_phone_book)'}
+ %URL= "#{@base_url}/#{@sip_account_ids.first}/phone_book.xml"
+ %Menu{:name => '$(lang:menu100_call_lists)'}
+ %MenuItem{:name => '$(lang:list_missed)'}
+ - @sip_account_ids.each_with_index do |id, index|
+ %If{:condition => "$(current_line)==#{index+1}"}
+ %URL= "#{@base_url}/#{id}/call_history_missed.xml"
+ %MenuItem{:name => '$(lang:list_taken)'}
+ - @sip_account_ids.each_with_index do |id, index|
+ %If{:condition => "$(current_line)==#{index+1}"}
+ %URL= "#{@base_url}/#{id}/call_history_received.xml"
+ %MenuItem{:name => '$(lang:list_dialed)'}
+ - @sip_account_ids.each_with_index do |id, index|
+ %If{:condition => "$(current_line)==#{index+1}"}
+ %URL= "#{@base_url}/#{id}/call_history_dialed.xml"
+ %MenuItem{:name => '$(lang:sel100_activeline)'}
+ %Action= 'active_line'
+
+ - if @enable_login
+ %MenuItem{:name => 'Log in'}
+ %URL= "#{@base_url}/log_in.xml"
+ - if @enable_logout
+ %MenuItem{:name => 'Log out'}
+ %URL= "#{@base_url}/log_out.xml"
+
+ %Menu{:name => '$(lang:preferences_settings)'}
+ %MenuItem{:name => '$(lang:menu_gen_contrast)'}
+ %Action= 'contrast'
+ %MenuItem{:name => '$(lang:use_backlight)'}
+ %Action= 'use_backlight'
+ %MenuItem{:name => '$(lang:use_backlight) $(lang:backlight_when_active)'}
+ %Action= 'backlight_active'
+ %MenuItem{:name => '$(lang:use_backlight) $(lang:backlight_when_idle)'}
+ %Action= 'backlight_idle'
+ %MenuItem{:name => '$(lang:menu_equalizer)'}
+ %Action= 'equalizer'
+ %Menu{:name => '$(lang:maintenance_settings)'}
+ %MenuItem{:name => '$(lang:system_information_menu)'}
+ %Action= 'sysinfo'
+ %MenuItem{:name => '$(lang:sel100_reboot)'}
+ %Action= 'reboot'
+ %If{:condition => '$(set:admin_mode)'}
+ %MenuItem{:name => '$(lang:reset_settings)'}
+ %Action= 'reset_settings'
+ %If{:condition => '$(update_available)'}
+ %MenuItem{:name => '$(lang:update_header)'}
+ %Action= 'software_update' \ No newline at end of file
diff --git a/app/views/config_snom/switch_protocol.xml.builder b/app/views/config_snom/switch_protocol.xml.builder
new file mode 100644
index 0000000..cd71486
--- /dev/null
+++ b/app/views/config_snom/switch_protocol.xml.builder
@@ -0,0 +1,18 @@
+xml.instruct! # <?xml version="1.0" encoding="UTF-8"?>
+
+xml.settings {
+ xml.tag!( 'phone-settings' ) {
+ xml.auto_reboot_on_setting_change( 'on', :perm => 'RW' )
+ xml.settings_refresh_timer( '60', :perm => 'RW' )
+ xml.reset_settings( 'main net stack user fkey speeddial phonebook', :perm => 'RW' )
+ #xml.dhcp( 'off', :perm => 'RW' )
+ #xml.ip_adr( @ip_address, :perm => 'RW' )
+ xml.setting_server( @prov_url, :perm => 'RW' )
+ }
+}
+
+
+# Local Variables:
+# mode: ruby
+# End:
+
diff --git a/app/views/fax_accounts/_form.html.haml b/app/views/fax_accounts/_form.html.haml
new file mode 100644
index 0000000..0a5a4c0
--- /dev/null
+++ b/app/views/fax_accounts/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for([ @parent, @fax_account ]) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .actions
+ = f.button :submit, conditional_t('fax_accounts.form.submit') \ No newline at end of file
diff --git a/app/views/fax_accounts/_form_core.html.haml b/app/views/fax_accounts/_form_core.html.haml
new file mode 100644
index 0000000..8153e5f
--- /dev/null
+++ b/app/views/fax_accounts/_form_core.html.haml
@@ -0,0 +1,11 @@
+.inputs
+ = f.input :name, :label => t('fax_accounts.form.name.label'), :hint => conditional_hint('fax_accounts.form.name.hint')
+ = f.input :station_id, :label => t('fax_accounts.form.station_id.label'), :hint => conditional_hint('fax_accounts.form.station_id.hint')
+ = f.input :retries, :label => t('fax_accounts.form.retries.label'), :hint => conditional_hint('fax_accounts.form.retries.hint')
+ = f.input :email, :label => t('fax_accounts.form.email.label'), :hint => conditional_hint('fax_accounts.form.email.hint')
+
+ = f.input :days_till_auto_delete, :label => t('fax_accounts.form.days_till_auto_delete.label'), :hint => conditional_hint('fax_accounts.form.days_till_auto_delete.hint')
+
+ %h2= t('phone_numbers.name')
+ = f.simple_fields_for :phone_numbers, @fax_account.phone_numbers do |phone_number|
+ = render "phone_numbers/form_core", :f => phone_number
diff --git a/app/views/fax_accounts/_index_core.html.haml b/app/views/fax_accounts/_index_core.html.haml
new file mode 100644
index 0000000..50dc2eb
--- /dev/null
+++ b/app/views/fax_accounts/_index_core.html.haml
@@ -0,0 +1,35 @@
+%table
+ %tr
+ %th= t('fax_accounts.index.name')
+ %th
+ = t('fax_accounts.index.phone_numbers')
+ %br
+ = t('fax_accounts.index.station_id')
+ %th
+ = t('fax_accounts.index.received')
+ = '/'
+ = t('fax_accounts.index.sent')
+ %br
+ %small
+ = t('fax_accounts.index.last_update')
+
+ - reset_cycle
+ - for fax_account in fax_accounts
+ %tr{:class => cycle('odd', 'even')}
+ %td= truncate(fax_account.name, :length => 15)
+ %td
+ =render 'phone_numbers/listing', :phone_numbers => fax_account.phone_numbers.order(:number)
+ %br
+ = truncate(fax_account.station_id, :length => 20)
+ %td
+ = link_to fax_account.fax_documents.inbound.count, fax_account_fax_documents_path(fax_account, :anchor => "fax_document_#{fax_account.fax_documents.inbound.first.try(:id)}")
+ = '/'
+ = link_to fax_account.fax_documents.outbound.count, fax_account_fax_documents_path(fax_account, :anchor => "fax_document_#{fax_account.fax_documents.outbound.first.try(:id)}")
+ - if fax_account.fax_documents.count > 0
+ %br
+ %small
+ = time_ago_in_words(fax_account.fax_documents.order(:updated_at).last.updated_at)
+ %td
+ - if can?(:new, FaxDocument, :fax_account_id => fax_account.id)
+ = link_to t('fax_accounts.index.send_a_fax'), new_fax_account_fax_document_path(fax_account)
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => fax_account.fax_accountable, :child => fax_account} \ No newline at end of file
diff --git a/app/views/fax_accounts/edit.html.haml b/app/views/fax_accounts/edit.html.haml
new file mode 100644
index 0000000..86f664d
--- /dev/null
+++ b/app/views/fax_accounts/edit.html.haml
@@ -0,0 +1,3 @@
+- title t("fax_accounts.edit.page_title")
+
+= render "form"
diff --git a/app/views/fax_accounts/index.html.haml b/app/views/fax_accounts/index.html.haml
new file mode 100644
index 0000000..309a10d
--- /dev/null
+++ b/app/views/fax_accounts/index.html.haml
@@ -0,0 +1,6 @@
+- title t("fax_accounts.index.page_title")
+
+- if @fax_accounts.count > 0
+ = render "index_core", {:fax_accounts => @fax_accounts, :fax_accountable => @parent}
+
+= render :partial => 'shared/create_link', :locals => {:parent => @parent, :child_class => FaxAccount} \ No newline at end of file
diff --git a/app/views/fax_accounts/new.html.haml b/app/views/fax_accounts/new.html.haml
new file mode 100644
index 0000000..9a67100
--- /dev/null
+++ b/app/views/fax_accounts/new.html.haml
@@ -0,0 +1,3 @@
+- title t("fax_accounts.new.page_title")
+
+= render "form"
diff --git a/app/views/fax_accounts/show.html.haml b/app/views/fax_accounts/show.html.haml
new file mode 100644
index 0000000..95fb7b2
--- /dev/null
+++ b/app/views/fax_accounts/show.html.haml
@@ -0,0 +1,21 @@
+- title t("fax_accounts.show.page_title")
+
+%p
+ %strong= t('fax_accounts.show.name') + ":"
+ = @fax_account.name
+%p
+ %strong= t('fax_accounts.show.email') + ":"
+ = @fax_account.email
+%p
+ %strong= t('fax_accounts.show.days_till_auto_delete') + ":"
+ = @fax_account.days_till_auto_delete
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @parent, :child => @fax_account }
+
+= render :partial => 'shared/create_link', :locals => { :parent => @fax_account, :child_class => FaxDocument }
+
+%h2= t('phone_numbers.index.page_title')
+- if @fax_account.phone_numbers.count > 0
+ = render "phone_numbers/index_core", :phone_numbers => @fax_account.phone_numbers
+ %br
+= render :partial => 'shared/create_link', :locals => { :parent => @fax_account, :child_class => PhoneNumber } \ No newline at end of file
diff --git a/app/views/fax_documents/_form.html.haml b/app/views/fax_documents/_form.html.haml
new file mode 100644
index 0000000..e240371
--- /dev/null
+++ b/app/views/fax_documents/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for([@fax_account,@fax_document]) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .actions
+ = f.button :submit, conditional_t('fax_documents.form.submit') \ No newline at end of file
diff --git a/app/views/fax_documents/_form_core.html.haml b/app/views/fax_documents/_form_core.html.haml
new file mode 100644
index 0000000..2a53cd5
--- /dev/null
+++ b/app/views/fax_documents/_form_core.html.haml
@@ -0,0 +1,7 @@
+.inputs
+ = f.input :document, :label => t('fax_documents.form.document.label'), :hint => conditional_hint('fax_documents.form.document.hint')
+ = f.association :fax_resolution, :label => t('fax_documents.form.fax_resolution.label'), :hint => conditional_hint('fax_documents.form.fax_resolution.hint'), :include_blank => false
+
+ = f.simple_fields_for :destination_phone_number, @phone_number do |u|
+ = render "phone_numbers/form_core", :f => u
+
diff --git a/app/views/fax_documents/_index_core.html.haml b/app/views/fax_documents/_index_core.html.haml
new file mode 100644
index 0000000..4e15509
--- /dev/null
+++ b/app/views/fax_documents/_index_core.html.haml
@@ -0,0 +1,33 @@
+%table
+ %tr
+ %th= t('fax_documents.index.sent_at')
+ %th= t('fax_documents.index.state')
+ %th= t('fax_documents.index.result')
+ %th
+ = t('fax_documents.index.phone_number')
+ %br
+ = t('fax_documents.index.remote_station_id')
+ %th= t('fax_documents.index.thumbnails')
+
+ - reset_cycle
+ - for fax_document in fax_documents
+ %tr{:class => cycle('odd', 'even'), :id => "fax_document_#{fax_document.id}"}
+ - if fax_document.sent_at
+ %td= "#{fax_document.inbound ? '&#8680;' : '&#8678;'} #{fax_document.sent_at}".html_safe
+ %td= t("fax_documents.states.#{fax_document.state}")
+ %td= t("fax_documents.result_codes.code_#{fax_document.result_code}") + " (#{fax_document.result_code})"
+ - else
+ %td{ :colspan => 3 }= t("fax_documents.states.#{fax_document.state}")
+ %td
+ - if fax_document.inbound
+ = "#{fax_document.caller_id_number} #{fax_document.caller_id_name}"
+ - else
+ = fax_document.destination_phone_number
+ %br
+ = fax_document.remote_station_id
+ %td
+ - fax_document.fax_thumbnails.limit(5).each do |fax_thumbnail|
+ =image_tag fax_thumbnail.thumbnail_url(:mini), :class => 'FaxThumbnail', :alt => "Thumbnail of page \##{fax_thumbnail.position}"
+ - if can?(:show, fax_document) && !fax_document.document.blank? && File.readable?(fax_document.document.path)
+ = link_to t('fax_documents.index.actions.download'), fax_account_fax_document_path(@fax_account, fax_document, :format => :pdf), :method => :get
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => fax_document.fax_account, :child => fax_document}
diff --git a/app/views/fax_documents/edit.html.haml b/app/views/fax_documents/edit.html.haml
new file mode 100644
index 0000000..5da92c6
--- /dev/null
+++ b/app/views/fax_documents/edit.html.haml
@@ -0,0 +1,9 @@
+- title t("fax_documents.edit.page_title")
+
+= render "form"
+
+%p
+ - if can? :edit, @fax_document
+ = link_to t('fax_documents.edit.actions.edit'), @fax_document
+ |
+ = link_to t('fax_documents.edit.actions.view_all'), fax_documents_path
diff --git a/app/views/fax_documents/index.html.haml b/app/views/fax_documents/index.html.haml
new file mode 100644
index 0000000..11199dd
--- /dev/null
+++ b/app/views/fax_documents/index.html.haml
@@ -0,0 +1,5 @@
+- title t("fax_documents.index.page_title")
+
+= render "index_core", :fax_documents => @fax_documents
+
+= render :partial => 'shared/create_link', :locals => {:parent => @fax_account, :child_class => FaxDocument} \ No newline at end of file
diff --git a/app/views/fax_documents/new.html.haml b/app/views/fax_documents/new.html.haml
new file mode 100644
index 0000000..be02860
--- /dev/null
+++ b/app/views/fax_documents/new.html.haml
@@ -0,0 +1,3 @@
+- title t("fax_documents.new.page_title")
+
+= render "form"
diff --git a/app/views/fax_documents/show.html.haml b/app/views/fax_documents/show.html.haml
new file mode 100644
index 0000000..4703e1d
--- /dev/null
+++ b/app/views/fax_documents/show.html.haml
@@ -0,0 +1,36 @@
+- title t("fax_documents.show.page_title")
+- child = @fax_document
+- parent = @fax_document.fax_account
+
+%p
+ %strong= t('fax_documents.index.state') + ":"
+ = t("fax_documents.states.#{@fax_document.state}")
+
+%p
+ %strong= t('fax_documents.index.result_code') + ":"
+ = @fax_document.result_code
+
+%p
+ %strong= t('fax_documents.index.result_text') + ":"
+ = t("fax_documents.result_codes.code_#{@fax_document.result_code}")
+
+%p
+ %strong= t('fax_documents.show.document_transferred_pages') + ":"
+ = @fax_document.document_transferred_pages
+%p
+ %strong= t('fax_documents.show.remote_station_id') + ":"
+ = @fax_document.remote_station_id
+%p
+ %strong= t('fax_documents.show.fax_resolution') + ":"
+ = @fax_document.fax_resolution
+
+- if @fax_document.fax_thumbnails.count > 0
+ - i = @fax_document.fax_thumbnails.count
+ - i = 10 if i > 10
+ - @fax_document.fax_thumbnails.limit(i).each do |fax_thumbnail|
+ =image_tag fax_thumbnail.thumbnail_url(:medium), :class => 'FaxThumbnail', :alt => "Thumbnail of page \##{fax_thumbnail.position}"
+
+- if @fax_document.document.path
+ = link_to t("fax_documents.index.actions.download_pdf"), "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}.pdf"
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @fax_document.fax_account, :child => @fax_document }
diff --git a/app/views/freeswitch_voicemail_msgs/_index_core.html.haml b/app/views/freeswitch_voicemail_msgs/_index_core.html.haml
new file mode 100644
index 0000000..58d9944
--- /dev/null
+++ b/app/views/freeswitch_voicemail_msgs/_index_core.html.haml
@@ -0,0 +1,12 @@
+%table
+ %tr
+ %th= t('freeswitch_voicemail_msgs.index.created_epoch')
+ %th= t('freeswitch_voicemail_msgs.index.message_len')
+ %th= t('freeswitch_voicemail_msgs.index.file_path')
+
+ - reset_cycle
+ - for freeswitch_voicemail_msg in freeswitch_voicemail_msgs
+ %tr{:class => cycle('odd', 'even')}
+ %td= freeswitch_voicemail_msg.created_epoch
+ %td= freeswitch_voicemail_msg.message_len
+ %td= freeswitch_voicemail_msg.file_path \ No newline at end of file
diff --git a/app/views/freeswitch_voicemail_msgs/index.html.haml b/app/views/freeswitch_voicemail_msgs/index.html.haml
new file mode 100644
index 0000000..5083c6f
--- /dev/null
+++ b/app/views/freeswitch_voicemail_msgs/index.html.haml
@@ -0,0 +1,3 @@
+- title t("freeswitch_voicemail_msgs.index.page_title")
+
+= render "index_core", :freeswitch_voicemail_msgs => @freeswitch_voicemail_msgs \ No newline at end of file
diff --git a/app/views/gemeinschaft_setups/new.de.html.haml b/app/views/gemeinschaft_setups/new.de.html.haml
new file mode 100644
index 0000000..5e79115
--- /dev/null
+++ b/app/views/gemeinschaft_setups/new.de.html.haml
@@ -0,0 +1,25 @@
+- title "Konfiguration einer Gemeinschaft #{GEMEINSCHAFT_VERSION} Installation"
+
+= simple_form_for(@gemeinschaft_setup) do |f|
+ = f.error_notification
+
+ %h2 Admin-Konto
+ %p
+ Dieser erste Benutzer des Systems hat automatisch Admin-Rechte.
+
+ = f.simple_fields_for :user, @user do |u|
+ = render "users/form_core", :f => u
+
+ %h2 SIP-Domain
+ %p In den meisten Fällen sollten Sie den gleichen Wert für SIP-Realm und SIP-Domain benutzen. Wenn Sie mit diesen Begriffen nichts anfangen können, dann geben Sie hier bitte die IP-Adresse dieses Servers ein.
+
+ = f.simple_fields_for :sip_domain, @sip_domain do |s|
+ = render "sip_domains/form_core", :f => s
+
+ %h2 Allgemeine Informationen
+
+ = f.association :country, :label => t('gemeinschaft_setups.form.country_id.label'), :hint => conditional_hint('gemeinschaft_setups.form.country_id.hint'), :include_blank => false
+ = f.association :language, :label => t('gemeinschaft_setups.form.language_id.label'), :hint => conditional_hint('gemeinschaft_setups.form.language_id.hint'), :include_blank => false
+
+ .actions
+ = f.button :submit, conditional_t('gemeinschaft_setups.form.submit') \ No newline at end of file
diff --git a/app/views/gemeinschaft_setups/new.html.haml b/app/views/gemeinschaft_setups/new.html.haml
new file mode 100644
index 0000000..f5f0e81
--- /dev/null
+++ b/app/views/gemeinschaft_setups/new.html.haml
@@ -0,0 +1,25 @@
+- title "Configure a new Gemeinschaft #{GEMEINSCHAFT_VERSION} server"
+
+= simple_form_for(@gemeinschaft_setup) do |f|
+ = f.error_notification
+
+ %h3 Admin user account
+ %p
+ This is the first user of this system who has admin rights by default.
+
+ = f.simple_fields_for :user, @user do |u|
+ = render "users/form_core", :f => u
+
+ %h3 SIP domain
+ %p You should use the same value for the SIP realm as for the SIP domain to ensure compatibility with different phone models. In case you have no clue what we are talking about: Just enter the IP address of this server.
+
+ = f.simple_fields_for :sip_domain, @sip_domain do |s|
+ = render "sip_domains/form_core", :f => s
+
+ %h3 General information
+
+ = f.association :country, :label => t('gemeinschaft_setups.form.country_id.label'), :hint => conditional_hint('gemeinschaft_setups.form.country_id.hint'), :include_blank => false
+ = f.association :language, :label => t('gemeinschaft_setups.form.language_id.label'), :hint => conditional_hint('gemeinschaft_setups.form.language_id.hint'), :include_blank => false
+
+ .actions
+ = f.button :submit, conditional_t('gemeinschaft_setups.form.submit') \ No newline at end of file
diff --git a/app/views/gs_cluster_sync_log_entries/_form.html.haml b/app/views/gs_cluster_sync_log_entries/_form.html.haml
new file mode 100644
index 0000000..2c47d88
--- /dev/null
+++ b/app/views/gs_cluster_sync_log_entries/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for(@gs_cluster_sync_log_entry) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .actions
+ = f.button :submit, conditional_t('gs_cluster_sync_log_entries.form.submit') \ No newline at end of file
diff --git a/app/views/gs_cluster_sync_log_entries/_form_core.html.haml b/app/views/gs_cluster_sync_log_entries/_form_core.html.haml
new file mode 100644
index 0000000..50b4630
--- /dev/null
+++ b/app/views/gs_cluster_sync_log_entries/_form_core.html.haml
@@ -0,0 +1,6 @@
+.inputs
+ = f.input :gs_node_id, :label => t('gs_cluster_sync_log_entries.form.gs_node_id.label'), :hint => conditional_hint('gs_cluster_sync_log_entries.form.gs_node_id.hint')
+ = f.input :class_name, :label => t('gs_cluster_sync_log_entries.form.class_name.label'), :hint => conditional_hint('gs_cluster_sync_log_entries.form.class_name.hint')
+ = f.input :action, :label => t('gs_cluster_sync_log_entries.form.action.label'), :hint => conditional_hint('gs_cluster_sync_log_entries.form.action.hint')
+ = f.input :content, :label => t('gs_cluster_sync_log_entries.form.content.label'), :hint => conditional_hint('gs_cluster_sync_log_entries.form.content.hint')
+ = f.input :status, :label => t('gs_cluster_sync_log_entries.form.status.label'), :hint => conditional_hint('gs_cluster_sync_log_entries.form.status.hint')
diff --git a/app/views/gs_cluster_sync_log_entries/_index_core.html.haml b/app/views/gs_cluster_sync_log_entries/_index_core.html.haml
new file mode 100644
index 0000000..05cbda8
--- /dev/null
+++ b/app/views/gs_cluster_sync_log_entries/_index_core.html.haml
@@ -0,0 +1,17 @@
+%table
+ %tr
+ %th= t('gs_cluster_sync_log_entries.index.gs_node_id')
+ %th= t('gs_cluster_sync_log_entries.index.class_name')
+ %th= t('gs_cluster_sync_log_entries.index.action')
+ %th= t('gs_cluster_sync_log_entries.index.content')
+ %th= t('gs_cluster_sync_log_entries.index.status')
+
+ - reset_cycle
+ - for gs_cluster_sync_log_entry in gs_cluster_sync_log_entries
+ %tr{:class => cycle('odd', 'even')}
+ %td= gs_cluster_sync_log_entry.gs_node_id
+ %td= gs_cluster_sync_log_entry.class_name
+ %td= gs_cluster_sync_log_entry.action
+ %td= gs_cluster_sync_log_entry.content
+ %td= gs_cluster_sync_log_entry.status
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => gs_cluster_sync_log_entry} \ No newline at end of file
diff --git a/app/views/gs_cluster_sync_log_entries/edit.html.haml b/app/views/gs_cluster_sync_log_entries/edit.html.haml
new file mode 100644
index 0000000..b0c65f3
--- /dev/null
+++ b/app/views/gs_cluster_sync_log_entries/edit.html.haml
@@ -0,0 +1,3 @@
+- title t("gs_cluster_sync_log_entries.edit.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/gs_cluster_sync_log_entries/index.html.haml b/app/views/gs_cluster_sync_log_entries/index.html.haml
new file mode 100644
index 0000000..68be7e0
--- /dev/null
+++ b/app/views/gs_cluster_sync_log_entries/index.html.haml
@@ -0,0 +1,6 @@
+- title t("gs_cluster_sync_log_entries.index.page_title")
+
+- if @gs_cluster_sync_log_entries && @gs_cluster_sync_log_entries.count > 0
+ = render "index_core", :gs_cluster_sync_log_entries => @gs_cluster_sync_log_entries
+
+= render :partial => 'shared/create_link', :locals => {:child_class => GsClusterSyncLogEntry} \ No newline at end of file
diff --git a/app/views/gs_cluster_sync_log_entries/new.html.haml b/app/views/gs_cluster_sync_log_entries/new.html.haml
new file mode 100644
index 0000000..01b795c
--- /dev/null
+++ b/app/views/gs_cluster_sync_log_entries/new.html.haml
@@ -0,0 +1,3 @@
+- title t("gs_cluster_sync_log_entries.new.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/gs_cluster_sync_log_entries/show.html.haml b/app/views/gs_cluster_sync_log_entries/show.html.haml
new file mode 100644
index 0000000..733576d
--- /dev/null
+++ b/app/views/gs_cluster_sync_log_entries/show.html.haml
@@ -0,0 +1,19 @@
+- title t("gs_cluster_sync_log_entries.show.page_title")
+
+%p
+ %strong= t('gs_cluster_sync_log_entries.show.gs_node_id') + ":"
+ = @gs_cluster_sync_log_entry.gs_node_id
+%p
+ %strong= t('gs_cluster_sync_log_entries.show.class_name') + ":"
+ = @gs_cluster_sync_log_entry.class_name
+%p
+ %strong= t('gs_cluster_sync_log_entries.show.action') + ":"
+ = @gs_cluster_sync_log_entry.action
+%p
+ %strong= t('gs_cluster_sync_log_entries.show.content') + ":"
+ = @gs_cluster_sync_log_entry.content
+%p
+ %strong= t('gs_cluster_sync_log_entries.show.status') + ":"
+ = @gs_cluster_sync_log_entry.status
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :child => @gs_cluster_sync_log_entry } \ No newline at end of file
diff --git a/app/views/gs_nodes/_form.html.haml b/app/views/gs_nodes/_form.html.haml
new file mode 100644
index 0000000..28a4e5d
--- /dev/null
+++ b/app/views/gs_nodes/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for(@gs_node) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .actions
+ = f.button :submit, conditional_t('gs_nodes.form.submit') \ No newline at end of file
diff --git a/app/views/gs_nodes/_form_core.html.haml b/app/views/gs_nodes/_form_core.html.haml
new file mode 100644
index 0000000..2355e52
--- /dev/null
+++ b/app/views/gs_nodes/_form_core.html.haml
@@ -0,0 +1,7 @@
+.inputs
+ = f.input :name, :label => t('gs_nodes.form.name.label'), :hint => conditional_hint('gs_nodes.form.name.hint')
+ = f.input :ip_address, :label => t('gs_nodes.form.ip_address.label'), :hint => conditional_hint('gs_nodes.form.ip_address.hint')
+ = f.input :site, :label => t('gs_nodes.form.site.label'), :hint => conditional_hint('gs_nodes.form.site.hint')
+ = f.input :element_name, :label => t('gs_nodes.form.element_name.label'), :hint => conditional_hint('gs_nodes.form.element_name.hint')
+ = f.input :push_updates_to, :label => t('gs_nodes.form.push_updates_to.label'), :hint => conditional_hint('gs_nodes.form.push_updates_to.hint')
+ = f.input :accepts_updates_from, :label => t('gs_nodes.form.accepts_updates_from.label'), :hint => conditional_hint('gs_nodes.form.accepts_updates_from.hint')
diff --git a/app/views/gs_nodes/_index_core.html.haml b/app/views/gs_nodes/_index_core.html.haml
new file mode 100644
index 0000000..72633e1
--- /dev/null
+++ b/app/views/gs_nodes/_index_core.html.haml
@@ -0,0 +1,19 @@
+%table
+ %tr
+ %th= t('gs_nodes.index.name')
+ %th= t('gs_nodes.index.ip_address')
+ %th= t('gs_nodes.index.site')
+ %th= t('gs_nodes.index.element_name')
+ %th= t('gs_nodes.index.push_updates_to')
+ %th= t('gs_nodes.index.accepts_updates_from')
+
+ - reset_cycle
+ - for gs_node in gs_nodes
+ %tr{:class => cycle('odd', 'even')}
+ %td= gs_node.name
+ %td= gs_node.ip_address
+ %td= gs_node.site
+ %td= gs_node.element_name
+ %td= gs_node.push_updates_to
+ %td= gs_node.accepts_updates_from
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => gs_node} \ No newline at end of file
diff --git a/app/views/gs_nodes/edit.html.haml b/app/views/gs_nodes/edit.html.haml
new file mode 100644
index 0000000..c025b05
--- /dev/null
+++ b/app/views/gs_nodes/edit.html.haml
@@ -0,0 +1,3 @@
+- title t("gs_nodes.edit.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/gs_nodes/index.html.haml b/app/views/gs_nodes/index.html.haml
new file mode 100644
index 0000000..4670cef
--- /dev/null
+++ b/app/views/gs_nodes/index.html.haml
@@ -0,0 +1,6 @@
+- title t("gs_nodes.index.page_title")
+
+- if @gs_nodes && @gs_nodes.count > 0
+ = render "index_core", :gs_nodes => @gs_nodes
+
+= render :partial => 'shared/create_link', :locals => {:child_class => GsNode} \ No newline at end of file
diff --git a/app/views/gs_nodes/new.html.haml b/app/views/gs_nodes/new.html.haml
new file mode 100644
index 0000000..230ce33
--- /dev/null
+++ b/app/views/gs_nodes/new.html.haml
@@ -0,0 +1,3 @@
+- title t("gs_nodes.new.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/gs_nodes/show.html.haml b/app/views/gs_nodes/show.html.haml
new file mode 100644
index 0000000..ca70a50
--- /dev/null
+++ b/app/views/gs_nodes/show.html.haml
@@ -0,0 +1,22 @@
+- title t("gs_nodes.show.page_title")
+
+%p
+ %strong= t('gs_nodes.show.name') + ":"
+ = @gs_node.name
+%p
+ %strong= t('gs_nodes.show.ip_address') + ":"
+ = @gs_node.ip_address
+%p
+ %strong= t('gs_nodes.show.site') + ":"
+ = @gs_node.site
+%p
+ %strong= t('gs_nodes.show.element_name') + ":"
+ = @gs_node.element_name
+%p
+ %strong= t('gs_nodes.show.push_updates_to') + ":"
+ = @gs_node.push_updates_to
+%p
+ %strong= t('gs_nodes.show.accepts_updates_from') + ":"
+ = @gs_node.accepts_updates_from
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :child => @gs_node } \ No newline at end of file
diff --git a/app/views/gs_nodes/sync.xml.haml b/app/views/gs_nodes/sync.xml.haml
new file mode 100644
index 0000000..0f097f8
--- /dev/null
+++ b/app/views/gs_nodes/sync.xml.haml
@@ -0,0 +1,88 @@
+!!! XML
+%gemeinschaft_sync{ :date => Time.now, :newer_as => @newer_as, :class => @request_class }
+ %node{ :id => @node.id, :ip_address => @node.ip_address, :name => @node.name }
+
+ - if !@tenants.blank?
+ %tenants
+ - @tenants.each do |tenant|
+ %tenant{ :name => tenant.name }
+
+ - if !@user_groups.blank?
+ %user_groups
+ - @user_groups.each do |user_group|
+ %user_group{ :name => user_group.name, :description => user_group.description, :position => user_group.position, :tenant => user_group.tenant.try(:name), :created_at => user_group.created_at, :updated_at => user_group.updated_at }
+
+ - if !@users.blank?
+ %users
+ - @users.each do |user|
+ %user{ :user_name => user.user_name, :first_name => user.first_name, :middle_name => user.middle_name, :last_name => user.last_name, :password_digest => user.password_digest, :pin_salt => user.pin_salt, :pin_hash => user.pin_hash, :male => user.male.to_s, :send_voicemail_as_email_attachment => user.send_voicemail_as_email_attachment.to_s, :uuid => user.uuid, :language => user.language.try(:code), :importer_checksum => user.importer_checksum, :email => user.email, :created_at => user.created_at, :updated_at => user.updated_at, :current_tenant => user.current_tenant.try(:name), :gs_node => user.gs_node.try(:name) }
+
+ - if !@user_group_memberships.blank?
+ %user_group_memberships
+ - @user_group_memberships.each do |membership|
+ %user_group_membership{ :user_group => membership.user_group.try(:name), :user_uuid => membership.user.try(:uuid), :state => membership.state, :created_at => membership.created_at, :updated_at => membership.updated_at }
+
+ - if !@sip_accounts.blank?
+ %sip_accounts
+ - @sip_accounts.each do |sip_account|
+ %sip_account{ :sip_accountable_type => sip_account.sip_accountable_type, :sip_accountable_uuid => sip_account.sip_accountable.try(:uuid), :uuid => sip_account.uuid, :auth_name => sip_account.auth_name, :caller_name => sip_account.caller_name, :password => sip_account.password, :voicemail_pin => sip_account.voicemail_pin, :created_at => sip_account.created_at, :updated_at => sip_account.updated_at, :tenant => sip_account.tenant.try(:name), :sip_domain => sip_account.sip_domain.try(:host), :call_waiting => sip_account.call_waiting.to_s, :clir => sip_account.clir.to_s, :clip_no_screening => sip_account.clip_no_screening, :clip => sip_account.clip.to_s, :description => sip_account.description, :callforward_rules_act_per_sip_account => sip_account.callforward_rules_act_per_sip_account.to_s, :hotdeskable => sip_account.hotdeskable.to_s, :gs_node => sip_account.gs_node.try(:name) }
+
+ - if !@conferences.blank?
+ %conferences
+ - @conferences.each do |conference|
+ %conference{ :uuid => conference.uuid, :name => conference.name, :start => conference.start, :end => conference.end, :conferenceable_type => conference.conferenceable_type, :conferenceable_uuid => conference.conferenceable.try(:uuid), :description => conference.description, :pin => conference.pin, :state => conference.state, :open_for_anybody => conference.open_for_anybody.to_s, :max_members => conference.max_members, :announce_new_member_by_name => conference.announce_new_member_by_name.to_s, :announce_left_member_by_name => conference.announce_left_member_by_name.to_s, :created_at => conference.created_at, :updated_at => conference.updated_at }
+
+ - if !@fax_accounts.blank?
+ %fax_accounts
+ - @fax_accounts.each do |fax_account|
+ %fax_account{ :uuid => fax_account.uuid, :name => fax_account.name, :email => fax_account.email, :tenant => fax_account.tenant.try(:name), :fax_accountable_type => fax_account.fax_accountable_type, :fax_accountable_uuid => fax_account.fax_accountable.try(:uuid), :station_id => fax_account.station_id, :days_till_auto_delete => fax_account.days_till_auto_delete, :retries => fax_account.retries, :created_at => fax_account.created_at, :updated_at => fax_account.updated_at }
+
+ - if !@phone_books.blank?
+ %phone_books
+ - @phone_books.each do |phone_book|
+ %phone_book{ :uuid => phone_book.uuid, :name => phone_book.name, :description => phone_book.description, :state => phone_book.state, :phone_bookable_type => phone_book.phone_bookable_type, :phone_bookable_uuid => phone_book.phone_bookable.try(:uuid), :created_at => phone_book.created_at, :updated_at => phone_book.updated_at }
+
+ - if !@phone_book_entries.blank?
+ %phone_book_entries
+ - @phone_book_entries.each do |phone_book_entry|
+ %phone_book_entry{ :uuid => phone_book_entry.uuid, :phone_book_uuid => phone_book_entry.phone_book.try(:uuid), :created_at => phone_book_entry.created_at, :updated_at => phone_book_entry.updated_at, :first_name => phone_book_entry.first_name, :middle_name => phone_book_entry.middle_name, :last_name => phone_book_entry.last_name, :title => phone_book_entry.title, :nickname => phone_book_entry.nickname, :organization => phone_book_entry.organization, :is_organization => phone_book_entry.is_organization.to_s, :department => phone_book_entry.department, :job_title => phone_book_entry.job_title, :is_male => phone_book_entry.is_male.to_s, :birthday => phone_book_entry.birthday, :birth_name => phone_book_entry.birth_name, :state => phone_book_entry.state, :description => phone_book_entry.description, :position => phone_book_entry.position, :homepage_personal => phone_book_entry.homepage_personal, :homepage_organization => phone_book_entry.homepage_organization, :twitter_account => phone_book_entry.twitter_account, :facebook_account => phone_book_entry.facebook_account, :google_plus_account => phone_book_entry.google_plus_account, :xing_account => phone_book_entry.xing_account, :linkedin_account => phone_book_entry.linkedin_account, :mobileme_account => phone_book_entry.mobileme_account, :created_at => phone_book_entry.created_at, :updated_at => phone_book_entry.updated_at, :first_name_phonetic => phone_book_entry.first_name_phonetic, :last_name_phonetic => phone_book_entry.last_name_phonetic, :organization_phonetic => phone_book_entry.organization_phonetic }
+
+ - if !@phone_numbers.blank?
+ %phone_numbers
+ - @phone_numbers.each do |phone_number|
+ %phone_number{ :uuid => phone_number.uuid, :name => phone_number.name, :number => phone_number.number, :phone_numberable_type => phone_number.phone_numberable_type, :phone_numberable_uuid => phone_number.phone_numberable.try(:uuid), :position => phone_number.position, :gs_node => phone_number.gs_node.try(:name), :created_at => phone_number.created_at, :updated_at => phone_number.updated_at }
+
+ - if !@call_forwards.blank?
+ %call_forwards
+ - @call_forwards.each do |call_forward|
+ %call_forward{ :uuid => call_forward.uuid, :service => call_forward.call_forward_case.try(:value), :timeout => call_forward.timeout, :destination => call_forward.destination, :source => call_forward.source, :active => call_forward.active.to_s, :created_at => call_forward.created_at, :updated_at => call_forward.updated_at, :phone_number_uuid => call_forward.phone_number.try(:uuid), :depth => call_forward.depth, :call_forwardable_type => call_forward.call_forwardable_type, :call_forwardable_uuid => call_forward.call_forwardable.try(:uuid), :position => call_forward.position}
+
+ - if !@softkeys.blank?
+ %softkeys
+ - @softkeys.each do |softkey|
+ %softkey{ :uuid => softkey.uuid, :function => softkey.softkey_function.try(:name), :number => softkey.number, :label => softkey.label, :position => softkey.position, :created_at => softkey.created_at, :updated_at => softkey.updated_at, :sip_account_uuid => softkey.sip_account.try(:uuid), :call_forward_uuid => softkey.call_forward.try(:uuid) }
+
+ - if !@ringtones.blank?
+ %ringtones
+ - @ringtones.each do |ringtone|
+ %ringtone{ :bellcore_id => ringtone.bellcore_id, :ringtoneable_type => ringtone.ringtoneable_type, :ringtoneable_uuid => ringtone.ringtoneable.try(:uuid), :created_at => ringtone.created_at, :updated_at => ringtone.updated_at }
+
+ - if !@conference_invitees.blank?
+ %conference_invitees
+ - @conference_invitees.each do |conference_invitee|
+ %conference_invitee{ :uuid => conference_invitee.uuid, :conference_uuid => conference_invitee.conference.try(:uuid), :phone_number_uuid => conference_invitee.phone_number.try(:uuid), :pin => conference_invitee.pin, :speaker => conference_invitee.speaker.to_s, :moderator => conference_invitee.moderator.to_s, :phone_book_entry_uuid => conference_invitee.phone_book_entry.try(:uuid), :created_at => conference_invitee.created_at, :updated_at => conference_invitee.updated_at }
+
+ - if !@fax_documents.blank?
+ %fax_documents
+ - @fax_documents.each do |fax_document|
+ %fax_document{ :inbound => fax_document.inbound.to_s, :state => fax_document.state, :transmission_time => fax_document.transmission_time, :sent_at => fax_document.sent_at, :document_total_pages => fax_document.document_total_pages, :document_transferred_pages => fax_document.document_transferred_pages, :ecm_requested => fax_document.ecm_requested.to_s, :ecm_used => fax_document.ecm_used.to_s, :image_resolution => fax_document.image_resolution, :image_size => fax_document.image_size, :local_station_id => fax_document.local_station_id, :result_code => fax_document.result_code, :remote_station_id => fax_document.remote_station_id, :success => fax_document.success.to_s, :transfer_rate => fax_document.transfer_rate, :document => fax_document.document, :created_at => fax_document.created_at, :updated_at => fax_document.updated_at, :fax_account_uuid => fax_document.fax_account.try(:uuid), :caller_id_number => fax_document.caller_id_number, :caller_id_name => fax_document.caller_id_name, :retry_counter => fax_document.retry_counter, :tiff => fax_document.tiff, :fax_resolution => fax_document.fax_resolution.try(:resolution_value), :document => fax_document.document.to_s,:uuid => fax_document.uuid }
+
+ - if !@call_histories.blank?
+ %call_histories
+ - @call_histories.each do |call_history|
+ %call_history{ :call_historyable_type => call_history.call_historyable_type, :call_historyable_uuid => call_history.call_historyable_uuid, :entry_type => call_history.entry_type, :caller_account_type => call_history.caller_account_type, :caller_account_uuid => call_history.caller_account_uuid, :caller_id_number => call_history.caller_id_number, :caller_id_name => call_history.caller_id_name, :caller_channel_uuid => call_history.caller_channel_uuid, :callee_account_type => call_history.callee_account_type, :callee_account_uuid => call_history.callee_account_uuid, :callee_id_number => call_history.callee_id_number, :callee_id_name => call_history.callee_id_name, :auth_account_type => call_history.auth_account_type, :auth_account_uuid => call_history.auth_account_uuid, :forwarding_service => call_history.forwarding_service, :destination_number => call_history.destination_number, :start_stamp => call_history.start_stamp, :duration => call_history.duration, :result => call_history.result, :read_flag => call_history.read_flag.to_s, :returned_flag => call_history.returned_flag.to_s, :created_at => call_history.created_at, :updated_at => call_history.updated_at }
+
+ - if !@deleted_items.blank?
+ %deleted_items
+ - @deleted_items.each do |deleted_item|
+ %deleted_item{ :class_name => deleted_item['class_name'], :uuid => deleted_item['uuid'] }
diff --git a/app/views/gui_functions/_form.html.haml b/app/views/gui_functions/_form.html.haml
new file mode 100644
index 0000000..0b2a201
--- /dev/null
+++ b/app/views/gui_functions/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for(@gui_function) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .actions
+ = f.button :submit, conditional_t('gui_functions.form.submit') \ No newline at end of file
diff --git a/app/views/gui_functions/_form_core.html.haml b/app/views/gui_functions/_form_core.html.haml
new file mode 100644
index 0000000..c2d6cd7
--- /dev/null
+++ b/app/views/gui_functions/_form_core.html.haml
@@ -0,0 +1,10 @@
+.inputs
+ = f.input :category, :label => t('gui_functions.form.category.label'), :hint => conditional_hint('gui_functions.form.category.hint')
+ = f.input :name, :label => t('gui_functions.form.name.label'), :hint => conditional_hint('gui_functions.form.name.hint')
+ = f.input :description, :label => t('gui_functions.form.description.label'), :hint => conditional_hint('gui_functions.form.description.hint')
+
+ - counter = 0
+ = f.fields_for :gui_function_memberships do |gui_function_membership|
+ = gui_function_membership.hidden_field :user_group_id
+ = gui_function_membership.input :activated, :label => @user_groups[counter], :hint => conditional_hint('gui_functions.form.activated.hint')
+ - counter = counter + 1 \ No newline at end of file
diff --git a/app/views/gui_functions/_index_core.html.haml b/app/views/gui_functions/_index_core.html.haml
new file mode 100644
index 0000000..093a0d7
--- /dev/null
+++ b/app/views/gui_functions/_index_core.html.haml
@@ -0,0 +1,26 @@
+%table
+ %tr
+ %th= t('gui_functions.index.category')
+ %th= t('gui_functions.index.name')
+ - @user_groups.each do |user_group|
+ %th= user_group
+
+ - reset_cycle
+ - for gui_function in gui_functions
+ %tr{:class => cycle('odd', 'even')}
+ %td= gui_function.category
+ %td
+ = gui_function.name
+ - if !gui_function.description.blank?
+ %br
+ %i= gui_function.description
+ - @user_groups.each do |user_group|
+ - if gui_function.gui_function_memberships.find_by_user_group_id(user_group.id)
+ - if gui_function.gui_function_memberships.find_by_user_group_id(user_group.id).activated == true
+ %td= 'x'
+ - else
+ %td= ''
+ - else
+ %td= 'x'
+
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:child => gui_function} \ No newline at end of file
diff --git a/app/views/gui_functions/edit.html.haml b/app/views/gui_functions/edit.html.haml
new file mode 100644
index 0000000..f43b5bc
--- /dev/null
+++ b/app/views/gui_functions/edit.html.haml
@@ -0,0 +1,3 @@
+- title t("gui_functions.edit.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/gui_functions/index.html.haml b/app/views/gui_functions/index.html.haml
new file mode 100644
index 0000000..ef909f0
--- /dev/null
+++ b/app/views/gui_functions/index.html.haml
@@ -0,0 +1,6 @@
+- title t("gui_functions.index.page_title")
+
+- if @gui_functions && @gui_functions.count > 0
+ = render "index_core", :gui_functions => @gui_functions
+
+= render :partial => 'shared/create_link', :locals => {:child_class => GuiFunction} \ No newline at end of file
diff --git a/app/views/gui_functions/new.html.haml b/app/views/gui_functions/new.html.haml
new file mode 100644
index 0000000..6c57e9c
--- /dev/null
+++ b/app/views/gui_functions/new.html.haml
@@ -0,0 +1,3 @@
+- title t("gui_functions.new.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/gui_functions/show.html.haml b/app/views/gui_functions/show.html.haml
new file mode 100644
index 0000000..0fc2dd9
--- /dev/null
+++ b/app/views/gui_functions/show.html.haml
@@ -0,0 +1,18 @@
+- title t("gui_functions.show.page_title")
+
+%p
+ %strong= t('gui_functions.show.name') + ":"
+ = @gui_function.name
+%p
+ %strong= t('gui_functions.show.description') + ":"
+ = @gui_function.description
+
+- @user_groups.each do |user_group|
+ %p
+ %strong= "#{user_group}:"
+ - if @gui_function.gui_function_memberships.where(:user_group_id => user_group.id, :activated => true).count > 0
+ = 'x'
+ - else
+ = 'not activated'
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :child => @gui_function } \ No newline at end of file
diff --git a/app/views/hunt_group_members/_form.html.haml b/app/views/hunt_group_members/_form.html.haml
new file mode 100644
index 0000000..1ab7850
--- /dev/null
+++ b/app/views/hunt_group_members/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for([@hunt_group, @hunt_group_member]) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .actions
+ = f.button :submit, conditional_t('hunt_group_members.form.submit') \ No newline at end of file
diff --git a/app/views/hunt_group_members/_form_core.html.haml b/app/views/hunt_group_members/_form_core.html.haml
new file mode 100644
index 0000000..2f03fce
--- /dev/null
+++ b/app/views/hunt_group_members/_form_core.html.haml
@@ -0,0 +1,4 @@
+.inputs
+ = f.input :name, :label => t('hunt_group_members.form.name.label'), :hint => conditional_hint('hunt_group_members.form.name.hint')
+ = f.input :active, :label => t('hunt_group_members.form.active.label'), :hint => conditional_hint('hunt_group_members.form.active.hint')
+ = f.input :can_switch_status_itself, :label => t('hunt_group_members.form.can_switch_status_itself.label'), :hint => conditional_hint('hunt_group_members.form.can_switch_status_itself.hint')
diff --git a/app/views/hunt_group_members/_index_core.html.haml b/app/views/hunt_group_members/_index_core.html.haml
new file mode 100644
index 0000000..46b64c8
--- /dev/null
+++ b/app/views/hunt_group_members/_index_core.html.haml
@@ -0,0 +1,20 @@
+%table
+ %tr
+ %th= t('hunt_group_members.index.name')
+ %th= t('hunt_group_members.index.active')
+ %th= t('hunt_group_members.index.can_switch_status_itself')
+ %th= t('hunt_group_members.index.phone_numbers')
+
+ - reset_cycle
+ - for hunt_group_member in hunt_group_members
+ %tr{:class => cycle('odd', 'even')}
+ %td= hunt_group_member.name
+ %td= hunt_group_member.active
+ %td= hunt_group_member.can_switch_status_itself
+ %td
+ - if hunt_group_member.phone_numbers.count > 0
+ =render 'phone_numbers/listing', :phone_numbers => hunt_group_member.phone_numbers
+ %br
+ = render :partial => 'shared/create_link', :locals => {:parent => hunt_group_member, :child_class => PhoneNumber, :short_link => true}
+
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => hunt_group_member.hunt_group, :child => hunt_group_member} \ No newline at end of file
diff --git a/app/views/hunt_group_members/_listing.html.haml b/app/views/hunt_group_members/_listing.html.haml
new file mode 100644
index 0000000..b31fd22
--- /dev/null
+++ b/app/views/hunt_group_members/_listing.html.haml
@@ -0,0 +1,8 @@
+- amount_of_hunt_group_members = hunt_group_members.count
+- if amount_of_hunt_group_members > 0
+ - if amount_of_hunt_group_members < 30
+ = hunt_group_members.map{|hunt_group_member| hunt_group_member}.join(', ')
+ - else
+ = hunt_group_members.limit(15).map{|hunt_group_member| hunt_group_member}.join(', ') + ', '
+ = '[...]'
+ = hunt_group_members.offset(amount_of_hunt_group_members - 15).map{|hunt_group_member| hunt_group_member}.join(', ') \ No newline at end of file
diff --git a/app/views/hunt_group_members/edit.html.haml b/app/views/hunt_group_members/edit.html.haml
new file mode 100644
index 0000000..93d7b0a
--- /dev/null
+++ b/app/views/hunt_group_members/edit.html.haml
@@ -0,0 +1,3 @@
+- title t("hunt_group_members.edit.page_title")
+
+= render "form"
diff --git a/app/views/hunt_group_members/index.html.haml b/app/views/hunt_group_members/index.html.haml
new file mode 100644
index 0000000..99dc929
--- /dev/null
+++ b/app/views/hunt_group_members/index.html.haml
@@ -0,0 +1,6 @@
+- title t("hunt_group_members.index.page_title")
+
+- if @hunt_group_members.count > 0
+ = render "index_core", :hunt_group_members => @hunt_group_members
+
+= render :partial => 'shared/create_link', :locals => {:parent => @hunt_group, :child_class => HuntGroupMember} \ No newline at end of file
diff --git a/app/views/hunt_group_members/new.html.haml b/app/views/hunt_group_members/new.html.haml
new file mode 100644
index 0000000..99f52ad
--- /dev/null
+++ b/app/views/hunt_group_members/new.html.haml
@@ -0,0 +1,3 @@
+- title t("hunt_group_members.new.page_title")
+
+= render "form"
diff --git a/app/views/hunt_group_members/show.html.haml b/app/views/hunt_group_members/show.html.haml
new file mode 100644
index 0000000..80123b8
--- /dev/null
+++ b/app/views/hunt_group_members/show.html.haml
@@ -0,0 +1,19 @@
+- title t("hunt_group_members.show.page_title")
+
+%p
+ %strong= t('hunt_group_members.show.name') + ":"
+ = @hunt_group_member.name
+%p
+ %strong= t('hunt_group_members.show.active') + ":"
+ = @hunt_group_member.active
+%p
+ %strong= t('hunt_group_members.show.can_switch_status_itself') + ":"
+ = @hunt_group_member.can_switch_status_itself
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @hunt_group, :child => @hunt_group_member }
+
+%h2= t('hunt_group_members.form.phone_numbers.label')
+- if @hunt_group_member.phone_numbers.count > 0
+ = render 'phone_numbers/index_core', :phone_numbers => @hunt_group_member.phone_numbers
+ %br
+= render :partial => 'shared/create_link', :locals => {:parent => @hunt_group_member, :child_class => PhoneNumber} \ No newline at end of file
diff --git a/app/views/hunt_groups/_form.html.haml b/app/views/hunt_groups/_form.html.haml
new file mode 100644
index 0000000..bc2663b
--- /dev/null
+++ b/app/views/hunt_groups/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for([@tenant, @hunt_group]) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .actions
+ = f.button :submit, conditional_t('hunt_groups.form.submit') \ No newline at end of file
diff --git a/app/views/hunt_groups/_form_core.html.haml b/app/views/hunt_groups/_form_core.html.haml
new file mode 100644
index 0000000..10a0111
--- /dev/null
+++ b/app/views/hunt_groups/_form_core.html.haml
@@ -0,0 +1,4 @@
+.inputs
+ = f.input :name, :label => t('hunt_groups.form.name.label'), :hint => conditional_hint('hunt_groups.form.name.hint')
+ = f.input :strategy, :as => :select, :label => t('hunt_groups.form.strategy.label'), :hint => conditional_hint('hunt_groups.form.strategy.hint'), :include_blank => false, :collection => HUNT_GROUP_STRATEGIES.map {|x| [I18n.t('hunt_groups.strategies.' + x), x] }
+ = f.input :seconds_between_jumps, :collection => VALID_SECONDS_BETWEEN_JUMPS_VALUES, :label => t('hunt_groups.form.seconds_between_jumps.label'), :hint => conditional_hint('hunt_groups.form.seconds_between_jumps.hint') \ No newline at end of file
diff --git a/app/views/hunt_groups/_index_core.html.haml b/app/views/hunt_groups/_index_core.html.haml
new file mode 100644
index 0000000..3000e97
--- /dev/null
+++ b/app/views/hunt_groups/_index_core.html.haml
@@ -0,0 +1,34 @@
+- show_seconds = hunt_groups.map{|x| ! x.seconds_between_jumps.nil? }.include?(true)
+
+%table
+ %tr
+ %th= t('hunt_groups.index.name')
+ %th= t('hunt_groups.index.strategy')
+ - if show_seconds
+ %th= t('hunt_groups.index.seconds_between_jumps')
+ %th= t('hunt_groups.index.phone_numbers')
+ %th= t('hunt_groups.index.hunt_group_members')
+
+ - reset_cycle
+ - for hunt_group in hunt_groups
+ %tr{:class => cycle('odd', 'even')}
+ %td= hunt_group.name
+ %td= t("hunt_groups.strategies.#{hunt_group.strategy}")
+ - if show_seconds
+ %td= hunt_group.seconds_between_jumps
+ %td
+ - if hunt_group.phone_numbers.count > 0
+ =render 'phone_numbers/listing', :phone_numbers => hunt_group.phone_numbers
+ %br
+ = render :partial => 'shared/create_link', :locals => {:parent => hunt_group, :child_class => PhoneNumber, :short_link => true}
+
+ %td
+ - if hunt_group.hunt_group_members.count > 3
+ = link_to hunt_group.hunt_group_members.count, hunt_group_hunt_group_members_path(hunt_group)
+ %br
+ - elsif hunt_group.hunt_group_members.count > 0
+ =render 'hunt_group_members/listing', :hunt_group_members => hunt_group.hunt_group_members
+ %br
+ = render :partial => 'shared/create_link', :locals => {:parent => hunt_group, :child_class => HuntGroupMember, :short_link => true}
+
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => hunt_group.tenant, :child => hunt_group} \ No newline at end of file
diff --git a/app/views/hunt_groups/edit.html.haml b/app/views/hunt_groups/edit.html.haml
new file mode 100644
index 0000000..f2ef998
--- /dev/null
+++ b/app/views/hunt_groups/edit.html.haml
@@ -0,0 +1,3 @@
+- title t("hunt_groups.edit.page_title")
+
+= render "form"
diff --git a/app/views/hunt_groups/index.html.haml b/app/views/hunt_groups/index.html.haml
new file mode 100644
index 0000000..62bc2aa
--- /dev/null
+++ b/app/views/hunt_groups/index.html.haml
@@ -0,0 +1,6 @@
+- title t("hunt_groups.index.page_title")
+
+- if @hunt_groups.count > 0
+ = render "index_core", :hunt_groups => @hunt_groups
+
+= render :partial => 'shared/create_link', :locals => {:parent => @tenant, :child_class => HuntGroup} \ No newline at end of file
diff --git a/app/views/hunt_groups/new.html.haml b/app/views/hunt_groups/new.html.haml
new file mode 100644
index 0000000..a40e579
--- /dev/null
+++ b/app/views/hunt_groups/new.html.haml
@@ -0,0 +1,3 @@
+- title t("hunt_groups.new.page_title")
+
+= render "form"
diff --git a/app/views/hunt_groups/show.html.haml b/app/views/hunt_groups/show.html.haml
new file mode 100644
index 0000000..009af50
--- /dev/null
+++ b/app/views/hunt_groups/show.html.haml
@@ -0,0 +1,26 @@
+- title t("hunt_groups.show.page_title")
+
+%p
+ %strong= t('hunt_groups.show.name') + ":"
+ = @hunt_group.name
+%p
+ %strong= t('hunt_groups.show.strategy') + ":"
+ = t("hunt_groups.strategies.#{@hunt_group.strategy}")
+- if @hunt_group.seconds_between_jumps
+ %p
+ %strong= t('hunt_groups.show.seconds_between_jumps') + ":"
+ = @hunt_group.seconds_between_jumps
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @tenant, :child => @hunt_group }
+
+%h2= t('hunt_groups.form.phone_numbers.label')
+- if @hunt_group.phone_numbers.count > 0
+ = render 'phone_numbers/index_core', :phone_numbers => @hunt_group.phone_numbers
+ %br
+= render :partial => 'shared/create_link', :locals => {:parent => @hunt_group, :child_class => PhoneNumber}
+
+%h2= t('hunt_groups.form.hunt_group_members.label')
+- if @hunt_group.hunt_group_members.count > 0
+ = render 'hunt_group_members/index_core', :hunt_group_members => @hunt_group.hunt_group_members
+ %br
+= render :partial => 'shared/create_link', :locals => {:parent => @hunt_group, :child_class => HuntGroupMember} \ No newline at end of file
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
new file mode 100644
index 0000000..f2aff0a
--- /dev/null
+++ b/app/views/layouts/application.html.haml
@@ -0,0 +1,46 @@
+!!! 5
+<!--[if lt IE 7]> <html lang="en" class="no-js ie6"> <![endif]-->
+<!--[if IE 7]> <html lang="en" class="no-js ie7"> <![endif]-->
+<!--[if IE 8]> <html lang="en" class="no-js ie8"> <![endif]-->
+<!--[if gt IE 8]><!-->
+%html.no-js{ :lang => "en" }
+ ~#OPTIMIZE Make html lang attribute reflect the actual language.
+ <!--<![endif]-->
+ %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 '&#8681;'.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 '&#8679;'.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