\n"
+" The calendar is shared between employees and fully integrated "
+"with\n"
+" other applications such as the employee holidays or the "
+"business\n"
+" opportunities.\n"
+"
\n"
+" "
+msgstr ""
#. module: base_calendar
#: help:calendar.alarm,description:0
@@ -1262,129 +1188,103 @@ msgid ""
"calendar component, than that provided by the "
"\"SUMMARY\" property"
msgstr ""
-"Facilita una descripción más completa del componente del calendario que la "
-"facilitada por la propiedad \"RESUMEN\""
#. module: base_calendar
#: view:calendar.event:0
msgid "Responsible User"
-msgstr "Usuario responsable"
+msgstr ""
#. module: base_calendar
+#: view:crm.meeting:0
+msgid "Select Weekdays"
+msgstr ""
+
+#. module: base_calendar
+#: code:addons/base_calendar/base_calendar.py:1519
#: selection:calendar.attendee,availability:0
#: selection:calendar.event,show_as:0
#: selection:calendar.todo,show_as:0
-#: selection:res.users,availability:0
+#: selection:crm.meeting,show_as:0
+#, python-format
msgid "Busy"
-msgstr "Ocupado"
+msgstr ""
#. module: base_calendar
#: model:ir.model,name:base_calendar.model_calendar_event
msgid "Calendar Event"
-msgstr "Evento de calendario"
-
-#. module: base_calendar
-#: selection:calendar.attendee,state:0
-#: selection:calendar.event,state:0
-#: selection:calendar.todo,state:0
-msgid "Tentative"
-msgstr "Provisional"
-
-#. module: base_calendar
-#: field:calendar.event,interval:0
-#: field:calendar.todo,interval:0
-msgid "Repeat every"
-msgstr "Repetir cada"
-
-#. module: base_calendar
-#: selection:calendar.event,end_type:0
-#: selection:calendar.todo,end_type:0
-msgid "Fix amout of times"
-msgstr "Cantidad fija de veces"
+msgstr ""
#. module: base_calendar
#: field:calendar.event,recurrency:0
#: field:calendar.todo,recurrency:0
+#: field:crm.meeting,recurrency:0
msgid "Recurrent"
-msgstr "Recurrente"
+msgstr ""
#. module: base_calendar
#: field:calendar.event,rrule_type:0
#: field:calendar.todo,rrule_type:0
+#: field:crm.meeting,rrule_type:0
msgid "Recurrency"
-msgstr "Recurrencia"
+msgstr ""
#. module: base_calendar
-#: model:ir.actions.act_window,name:base_calendar.action_view_attendee_form
-#: model:ir.ui.menu,name:base_calendar.menu_attendee_invitations
-msgid "Event Invitations"
-msgstr "Invitaciones al evento"
-
-#. module: base_calendar
-#: selection:base.calendar.set.exrule,week_list:0
#: selection:calendar.event,week_list:0
#: selection:calendar.todo,week_list:0
+#: selection:crm.meeting,week_list:0
msgid "Thursday"
-msgstr "Jueves"
+msgstr ""
#. module: base_calendar
#: field:calendar.event,exrule:0
#: field:calendar.todo,exrule:0
+#: field:crm.meeting,exrule:0
msgid "Exception Rule"
-msgstr "Regla de excepción"
+msgstr ""
#. module: base_calendar
#: help:calendar.attendee,language:0
msgid ""
"To specify the language for text values in aproperty or property parameter."
msgstr ""
-"Para indicar el idioma de los valores de texto en una propiedad o parámetro "
-"de propiedad."
#. module: base_calendar
#: view:calendar.event:0
msgid "Details"
-msgstr "Detalles"
+msgstr ""
#. module: base_calendar
#: help:calendar.event,exrule:0
#: help:calendar.todo,exrule:0
+#: help:crm.meeting,exrule:0
msgid ""
"Defines a rule or repeating pattern of time to exclude from the recurring "
"rule."
msgstr ""
-"Define una regla o patrón de repetición de tiempo a excluir de la regla "
-"recurrente."
#. module: base_calendar
-#: field:base.calendar.set.exrule,month_list:0
#: field:calendar.event,month_list:0
#: field:calendar.todo,month_list:0
+#: field:crm.meeting,month_list:0
msgid "Month"
-msgstr "Mes"
-
-#. module: base_calendar
-#: view:base_calendar.invite.attendee:0
-#: view:calendar.event:0
-msgid "Invite People"
-msgstr "Invitar personas"
-
-#. module: base_calendar
-#: help:calendar.event,rrule:0
-#: help:calendar.todo,rrule:0
-msgid ""
-"Defines a rule or repeating pattern for recurring events\n"
-"e.g.: Every other month on the last Sunday of the month for 10 occurrences: "
-" FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU"
msgstr ""
-"Define una regla o patrón repetitivo para eventos recurrentes.\n"
-"Por ejemplo: Para 10 ocurrencias cada último domingo de cada dos meses : "
-"FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=-1SU"
+
+#. module: base_calendar
+#: selection:calendar.event,rrule_type:0
+#: selection:calendar.todo,rrule_type:0
+#: selection:crm.meeting,rrule_type:0
+msgid "Day(s)"
+msgstr ""
+
+#. module: base_calendar
+#: view:calendar.event:0
+msgid "Confirmed Events"
+msgstr ""
#. module: base_calendar
#: field:calendar.attendee,dir:0
msgid "URI Reference"
-msgstr "Referencia URI"
+msgstr ""
#. module: base_calendar
#: field:calendar.alarm,description:0
@@ -1393,110 +1293,115 @@ msgstr "Referencia URI"
#: field:calendar.event,name:0
#: field:calendar.todo,description:0
#: field:calendar.todo,name:0
+#: field:crm.meeting,description:0
msgid "Description"
-msgstr "Descripción"
+msgstr ""
#. module: base_calendar
-#: selection:base.calendar.set.exrule,month_list:0
#: selection:calendar.event,month_list:0
#: selection:calendar.todo,month_list:0
+#: selection:crm.meeting,month_list:0
msgid "May"
-msgstr "Mayo"
-
-#. module: base_calendar
-#: field:base_calendar.invite.attendee,type:0
-#: view:calendar.attendee:0
-msgid "Type"
-msgstr "Tipo"
-
-#. module: base_calendar
-#: view:calendar.attendee:0
-msgid "Search Invitations"
-msgstr "Buscar invitaciones"
+msgstr ""
#. module: base_calendar
#: selection:calendar.alarm,trigger_occurs:0
#: selection:res.alarm,trigger_occurs:0
msgid "After"
-msgstr "Después de"
+msgstr ""
#. module: base_calendar
#: selection:calendar.alarm,state:0
msgid "Stop"
-msgstr "Parar"
+msgstr ""
#. module: base_calendar
#: model:ir.model,name:base_calendar.model_ir_values
msgid "ir.values"
-msgstr "ir.valores"
+msgstr ""
#. module: base_calendar
-#: model:ir.model,name:base_calendar.model_ir_model
-msgid "Objects"
-msgstr "Objetos"
+#: view:crm.meeting:0
+msgid "Search Meetings"
+msgstr ""
+
+#. module: base_calendar
+#: model:ir.model,name:base_calendar.model_ir_attachment
+msgid "ir.attachment"
+msgstr ""
+
+#. module: base_calendar
+#: model:ir.model,name:base_calendar.model_crm_meeting_type
+msgid "Meeting Type"
+msgstr ""
#. module: base_calendar
-#: view:calendar.attendee:0
#: selection:calendar.attendee,state:0
msgid "Delegated"
-msgstr "Delegada"
+msgstr ""
#. module: base_calendar
-#: field:base.calendar.set.exrule,sa:0
#: field:calendar.event,sa:0
#: field:calendar.todo,sa:0
+#: field:crm.meeting,sa:0
msgid "Sat"
-msgstr "Sáb"
+msgstr ""
#. module: base_calendar
-#: view:calendar.event:0
-msgid "Choose day where repeat the meeting"
-msgstr "Eligir día en el que repetir la cita"
+#: model:ir.actions.act_window,help:base_calendar.action_res_alarm_view
+msgid ""
+"
\n"
+" Click to setup a new alarm type.\n"
+"
\n"
+" You can define a customized type of calendar alarm that may "
+"be\n"
+" assigned to calendar events or meetings.\n"
+"
\n"
+" "
+msgstr ""
#. module: base_calendar
-#: selection:base.calendar.set.exrule,freq:0
-msgid "Minutely"
-msgstr "Cada minuto"
+#: selection:crm.meeting,state:0
+msgid "Unconfirmed"
+msgstr ""
#. module: base_calendar
#: help:calendar.attendee,sent_by:0
msgid "Specify the user that is acting on behalf of the calendar user"
msgstr ""
-"Indique el usuario que está actuando en nombre del usuario del calendario."
#. module: base_calendar
#: view:calendar.event:0
-#: field:calendar.event.edit.all,date_deadline:0
+#: field:calendar.event,date_deadline:0
+#: field:calendar.todo,date_deadline:0
+#: field:crm.meeting,date_deadline:0
msgid "End Date"
-msgstr "Fecha final"
+msgstr ""
#. module: base_calendar
-#: selection:base.calendar.set.exrule,month_list:0
#: selection:calendar.event,month_list:0
#: selection:calendar.todo,month_list:0
+#: selection:crm.meeting,month_list:0
msgid "February"
-msgstr "Febrero"
-
-#. module: base_calendar
-#: selection:calendar.event,freq:0
-#: selection:calendar.todo,freq:0
-msgid "Months"
-msgstr "Meses"
+msgstr ""
#. module: base_calendar
#: selection:calendar.attendee,cutype:0
msgid "Resource"
-msgstr "Recurso"
+msgstr ""
#. module: base_calendar
+#: field:crm.meeting.type,name:0
#: field:res.alarm,name:0
msgid "Name"
-msgstr "Nombre"
+msgstr ""
#. module: base_calendar
-#: model:ir.model,name:base_calendar.model_calendar_alarm
-msgid "Event alarm information"
-msgstr "Información del aviso del evento"
+#: field:calendar.event,exdate:0
+#: field:calendar.todo,exdate:0
+#: field:crm.meeting,exdate:0
+msgid "Exception Date/Times"
+msgstr ""
#. module: base_calendar
#: help:calendar.alarm,name:0
@@ -1504,154 +1409,162 @@ msgid ""
"Contains the text to be used as the message subject for "
"email or contains the text to be used for display"
msgstr ""
-"Contiene el texto a usar como asunto del mensaje para correos electrónicos, "
-"o contiene el texto a mostrar"
#. module: base_calendar
-#: field:calendar.event,alarm_id:0
-#: field:calendar.event,base_calendar_alarm_id:0
-#: field:calendar.todo,alarm_id:0
-#: field:calendar.todo,base_calendar_alarm_id:0
-msgid "Alarm"
-msgstr "Alarma"
-
-#. module: base_calendar
-#: code:addons/base_calendar/wizard/base_calendar_set_exrule.py:90
-#, python-format
-msgid "Please Apply Recurrency before applying Exception Rule."
+#: model:ir.model,name:base_calendar.model_mail_message
+msgid "Message"
+msgstr ""
+
+#. module: base_calendar
+#: field:calendar.event,base_calendar_alarm_id:0
+#: field:calendar.todo,base_calendar_alarm_id:0
+#: field:crm.meeting,base_calendar_alarm_id:0
+msgid "Alarm"
msgstr ""
-"Por favor, aplique la repetición antes de aplicar la excepción de la regla"
#. module: base_calendar
#: field:calendar.attendee,sent_by_uid:0
msgid "Sent By User"
-msgstr "Enviado por usuario"
+msgstr ""
#. module: base_calendar
-#: selection:base.calendar.set.exrule,month_list:0
#: selection:calendar.event,month_list:0
#: selection:calendar.todo,month_list:0
+#: selection:crm.meeting,month_list:0
msgid "April"
-msgstr "Abril"
+msgstr ""
+
+#. module: base_calendar
+#: code:addons/base_calendar/crm_meeting.py:106
+#, python-format
+msgid "Email addresses not found"
+msgstr ""
#. module: base_calendar
#: view:calendar.event:0
msgid "Recurrency period"
-msgstr "Periodo de recurrencia"
+msgstr ""
#. module: base_calendar
-#: field:base.calendar.set.exrule,week_list:0
#: field:calendar.event,week_list:0
#: field:calendar.todo,week_list:0
+#: field:crm.meeting,week_list:0
msgid "Weekday"
-msgstr "Día de la semana"
+msgstr ""
+
+#. module: base_calendar
+#: code:addons/base_calendar/base_calendar.py:1013
+#, python-format
+msgid "Interval cannot be negative."
+msgstr ""
#. module: base_calendar
-#: field:base.calendar.set.exrule,byday:0
#: field:calendar.event,byday:0
#: field:calendar.todo,byday:0
+#: field:crm.meeting,byday:0
msgid "By day"
-msgstr "Por día"
+msgstr ""
+
+#. module: base_calendar
+#: code:addons/base_calendar/base_calendar.py:441
+#, python-format
+msgid "First you have to specify the date of the invitation."
+msgstr ""
#. module: base_calendar
#: field:calendar.alarm,model_id:0
msgid "Model"
-msgstr "Modelo"
+msgstr ""
#. module: base_calendar
#: selection:calendar.alarm,action:0
msgid "Audio"
-msgstr "Audio"
+msgstr ""
#. module: base_calendar
#: field:calendar.event,id:0
#: field:calendar.todo,id:0
+#: field:crm.meeting,id:0
msgid "ID"
-msgstr "ID"
+msgstr ""
#. module: base_calendar
#: selection:calendar.attendee,role:0
msgid "For information Purpose"
-msgstr "Con propósito informativo"
+msgstr ""
#. module: base_calendar
-#: view:base_calendar.invite.attendee:0
-msgid "Invite"
-msgstr "Invitar"
+#: field:calendar.event,select1:0
+#: field:calendar.todo,select1:0
+#: field:crm.meeting,select1:0
+msgid "Option"
+msgstr ""
#. module: base_calendar
#: model:ir.model,name:base_calendar.model_calendar_attendee
msgid "Attendee information"
-msgstr "Información asistentes"
+msgstr ""
#. module: base_calendar
#: field:calendar.alarm,res_id:0
msgid "Resource ID"
-msgstr "ID del registro"
+msgstr ""
#. module: base_calendar
#: selection:calendar.attendee,state:0
msgid "Needs Action"
-msgstr "Necesita acción"
+msgstr ""
#. module: base_calendar
#: field:calendar.attendee,sent_by:0
msgid "Sent By"
-msgstr "Enviado por"
+msgstr ""
#. module: base_calendar
#: field:calendar.event,sequence:0
#: field:calendar.todo,sequence:0
+#: field:crm.meeting,sequence:0
msgid "Sequence"
-msgstr "Secuencia"
+msgstr ""
#. module: base_calendar
#: help:calendar.event,alarm_id:0
#: help:calendar.todo,alarm_id:0
+#: help:crm.meeting,alarm_id:0
msgid "Set an alarm at this time, before the event occurs"
-msgstr "Configure una alarma en este momento, antes de que ocurra el evento"
+msgstr ""
#. module: base_calendar
-#: selection:base_calendar.invite.attendee,type:0
-msgid "Internal User"
-msgstr "Usuario interno"
-
-#. module: base_calendar
-#: view:calendar.attendee:0
#: view:calendar.event:0
+#: view:crm.meeting:0
msgid "Accept"
-msgstr "Aceptar"
+msgstr ""
#. module: base_calendar
-#: selection:base.calendar.set.exrule,week_list:0
#: selection:calendar.event,week_list:0
#: selection:calendar.todo,week_list:0
+#: selection:crm.meeting,week_list:0
msgid "Saturday"
-msgstr "Sábado"
+msgstr ""
#. module: base_calendar
-#: view:calendar.attendee:0
-msgid "Invitation To"
-msgstr "Invitación a"
+#: field:calendar.event,interval:0
+#: field:calendar.todo,interval:0
+#: field:crm.meeting,interval:0
+msgid "Repeat Every"
+msgstr ""
#. module: base_calendar
-#: selection:base.calendar.set.exrule,byday:0
#: selection:calendar.event,byday:0
#: selection:calendar.todo,byday:0
+#: selection:crm.meeting,byday:0
msgid "Second"
-msgstr "Segundo"
+msgstr ""
#. module: base_calendar
#: field:calendar.attendee,availability:0
-#: field:res.users,availability:0
msgid "Free/Busy"
-msgstr "Libre/Ocupado"
-
-#. module: base_calendar
-#: field:calendar.event,end_type:0
-#: field:calendar.todo,end_type:0
-msgid "Way to end reccurency"
-msgstr "Forma de terminar recurrencia"
+msgstr ""
#. module: base_calendar
#: field:calendar.alarm,duration:0
@@ -1659,20 +1572,16 @@ msgstr "Forma de terminar recurrencia"
#: field:calendar.event,duration:0
#: field:calendar.todo,date:0
#: field:calendar.todo,duration:0
+#: field:crm.meeting,duration:0
#: field:res.alarm,duration:0
#: field:res.alarm,trigger_duration:0
msgid "Duration"
-msgstr "Duración"
-
-#. module: base_calendar
-#: selection:base_calendar.invite.attendee,type:0
-msgid "External Email"
-msgstr "Email externo"
+msgstr ""
#. module: base_calendar
#: field:calendar.alarm,trigger_date:0
msgid "Trigger Date"
-msgstr "Fecha activación"
+msgstr ""
#. module: base_calendar
#: help:calendar.alarm,attach:0
@@ -1684,19 +1593,10 @@ msgid ""
" * Points to a procedure resource, which is invoked when "
" the alarm is triggered for procedure."
msgstr ""
-"* Apunta a un recurso de sonido, que se escucha cuando la alarma se activa "
-"por audio.\n"
-"* El archivo que está intentando ser enviado como adjunto en el correo "
-"electrónico.\n"
-"* Apunta a un recurso de procedimiento, que se invoca cuando la alarma se "
-"activa por procedimiento."
#. module: base_calendar
-#: selection:base.calendar.set.exrule,byday:0
#: selection:calendar.event,byday:0
#: selection:calendar.todo,byday:0
+#: selection:crm.meeting,byday:0
msgid "Fifth"
-msgstr "Quinto"
-
-#~ msgid "Set Exclude range"
-#~ msgstr "Fijar el rango de exclusión"
+msgstr ""
diff --git a/addons/base_report_designer/base_report_designer_installer.xml b/addons/base_report_designer/base_report_designer_installer.xml
index 657eae8fd63..99898f5a703 100644
--- a/addons/base_report_designer/base_report_designer_installer.xml
+++ b/addons/base_report_designer/base_report_designer_installer.xml
@@ -20,7 +20,7 @@
-
+
diff --git a/addons/base_report_designer/installer.py b/addons/base_report_designer/installer.py
index a21b6655af7..7fddc27f037 100644
--- a/addons/base_report_designer/installer.py
+++ b/addons/base_report_designer/installer.py
@@ -23,7 +23,6 @@ from openerp.osv import fields
from openerp.osv import osv
import base64
from openerp.tools.translate import _
-from openerp.modules.module import get_module_resource
class base_report_designer_installer(osv.osv_memory):
_name = 'base_report_designer.installer'
@@ -31,13 +30,13 @@ class base_report_designer_installer(osv.osv_memory):
def default_get(self, cr, uid, fields, context=None):
data = super(base_report_designer_installer, self).default_get(cr, uid, fields, context=context)
- plugin_file = open(get_module_resource('base_report_designer','plugin', 'openerp_report_designer.zip'),'rb')
- data['plugin_file'] = base64.encodestring(plugin_file.read())
+ base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
+ data['plugin_file'] = base_url + '/base_report_designer/static/base-report-designer-plugin/openerp_report_designer.zip'
return data
_columns = {
'name':fields.char('File name', size=34),
- 'plugin_file':fields.binary('OpenObject Report Designer Plug-in', readonly=True, help="OpenObject Report Designer plug-in file. Save as this file and install this plug-in in OpenOffice."),
+ 'plugin_file':fields.char('OpenObject Report Designer Plug-in', size=256, readonly=True, help="OpenObject Report Designer plug-in file. Save as this file and install this plug-in in OpenOffice."),
'description':fields.text('Description', readonly=True)
}
diff --git a/addons/base_report_designer/plugin/openerp_report_designer.zip b/addons/base_report_designer/static/base-report-designer-plugin/openerp_report_designer.zip
similarity index 100%
rename from addons/base_report_designer/plugin/openerp_report_designer.zip
rename to addons/base_report_designer/static/base-report-designer-plugin/openerp_report_designer.zip
diff --git a/addons/base_setup/i18n/nl_BE.po b/addons/base_setup/i18n/nl_BE.po
index f22fd2c8adc..518ada07a5c 100644
--- a/addons/base_setup/i18n/nl_BE.po
+++ b/addons/base_setup/i18n/nl_BE.po
@@ -7,19 +7,19 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
-"PO-Revision-Date: 2012-10-01 11:22+0000\n"
+"PO-Revision-Date: 2013-04-26 16:24+0000\n"
"Last-Translator: Els Van Vossel (Agaplan) \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-03-16 05:12+0000\n"
-"X-Generator: Launchpad (build 16532)\n"
+"X-Launchpad-Export-Date: 2013-04-27 05:44+0000\n"
+"X-Generator: Launchpad (build 16580)\n"
#. module: base_setup
#: view:sale.config.settings:0
msgid "Emails Integration"
-msgstr ""
+msgstr "E-mailintegratie"
#. module: base_setup
#: selection:base.setup.terminology,partner:0
@@ -29,18 +29,19 @@ msgstr "Gast"
#. module: base_setup
#: view:sale.config.settings:0
msgid "Contacts"
-msgstr ""
+msgstr "Contactpersonen"
#. module: base_setup
#: model:ir.model,name:base_setup.model_base_config_settings
msgid "base.config.settings"
-msgstr ""
+msgstr "base.config.settings"
#. module: base_setup
#: field:base.config.settings,module_auth_oauth:0
msgid ""
"Use external authentication providers, sign in with google, facebook, ..."
msgstr ""
+"Gebruik externe verificatieproviders. Meld aan met Google, Facebook, ..."
#. module: base_setup
#: view:sale.config.settings:0
@@ -54,11 +55,19 @@ msgid ""
"OpenERP using specific\n"
" plugins for your preferred email application."
msgstr ""
+"OpenERP maakt het mogelijk om automatisch leads (of andere documenten)\n"
+" aan te maken op basis van inkomende e-mails. U "
+"kunt automatisch e-mails synchroniseren met OpenERP\n"
+" met behulp van reguliere POP / IMAP-accounts, "
+"via een direct e-mailintegratiescript voor uw\n"
+" e-mailserver, of door zelf uw e-mails naar "
+"OpenERP te sturen met behulp van specifieke\n"
+" plug-ins voor uw favoriete e-mailprogramma."
#. module: base_setup
#: field:sale.config.settings,module_sale:0
msgid "SALE"
-msgstr ""
+msgstr "VERKOOP"
#. module: base_setup
#: selection:base.setup.terminology,partner:0
@@ -68,24 +77,24 @@ msgstr "Lid"
#. module: base_setup
#: view:base.config.settings:0
msgid "Portal access"
-msgstr ""
+msgstr "Portaaltoegang"
#. module: base_setup
#: view:base.config.settings:0
msgid "Authentication"
-msgstr ""
+msgstr "Verificatie"
#. module: base_setup
#: view:sale.config.settings:0
msgid "Quotations and Sales Orders"
-msgstr ""
+msgstr "Offertes en verkooporders"
#. module: base_setup
#: view:base.config.settings:0
#: model:ir.actions.act_window,name:base_setup.action_general_configuration
#: model:ir.ui.menu,name:base_setup.menu_general_configuration
msgid "General Settings"
-msgstr ""
+msgstr "Algemeen"
#. module: base_setup
#: selection:base.setup.terminology,partner:0
@@ -95,12 +104,12 @@ msgstr "Donor"
#. module: base_setup
#: view:base.config.settings:0
msgid "Email"
-msgstr ""
+msgstr "E-mail"
#. module: base_setup
#: field:sale.config.settings,module_crm:0
msgid "CRM"
-msgstr ""
+msgstr "Relatiebeheer"
#. module: base_setup
#: selection:base.setup.terminology,partner:0
@@ -110,32 +119,32 @@ msgstr "Patiënt"
#. module: base_setup
#: field:base.config.settings,module_base_import:0
msgid "Allow users to import data from CSV files"
-msgstr ""
+msgstr "Gebruikers mogen gegevens importeren uit csv-bestanden"
#. module: base_setup
#: field:base.config.settings,module_multi_company:0
msgid "Manage multiple companies"
-msgstr ""
+msgstr "Meerdere bedrijven beheren"
#. module: base_setup
#: view:sale.config.settings:0
msgid "On Mail Client"
-msgstr ""
+msgstr "Op e-mailclient"
#. module: base_setup
#: view:base.config.settings:0
msgid "--db-filter=YOUR_DATABAE"
-msgstr ""
+msgstr "--db-filter=UW_DATABASE"
#. module: base_setup
#: field:sale.config.settings,module_web_linkedin:0
msgid "Get contacts automatically from linkedIn"
-msgstr ""
+msgstr "Haal contactpersonen op uit LinkedIn"
#. module: base_setup
#: field:sale.config.settings,module_plugin_thunderbird:0
msgid "Enable Thunderbird plug-in"
-msgstr ""
+msgstr "Activeer de Thunderbirdplug-in"
#. module: base_setup
#: view:base.setup.terminology:0
@@ -145,22 +154,22 @@ msgstr "res_config_contents"
#. module: base_setup
#: view:sale.config.settings:0
msgid "Customer Features"
-msgstr ""
+msgstr "Klantenopties"
#. module: base_setup
#: view:base.config.settings:0
msgid "Import / Export"
-msgstr ""
+msgstr "Import / Export"
#. module: base_setup
#: view:sale.config.settings:0
msgid "Sale Features"
-msgstr ""
+msgstr "Verkoopopties"
#. module: base_setup
#: field:sale.config.settings,module_plugin_outlook:0
msgid "Enable Outlook plug-in"
-msgstr ""
+msgstr "Activeer de Outlookplug-in"
#. module: base_setup
#: view:base.setup.terminology:0
@@ -179,7 +188,7 @@ msgstr "Huurder"
#. module: base_setup
#: help:base.config.settings,module_share:0
msgid "Share or embbed any screen of openerp."
-msgstr ""
+msgstr "Gelijk welk scherm van OpenERP delen of insluiten"
#. module: base_setup
#: selection:base.setup.terminology,partner:0
@@ -192,6 +201,8 @@ msgid ""
"When you create a new contact (person or company), you will be able to load "
"all the data from LinkedIn (photos, address, etc)."
msgstr ""
+"Als u een nieuwe contactpersoon maakt (persoon of bedrijf), kunt u alle "
+"gegevens van LinkedIn laden (foto, adres, enz.)"
#. module: base_setup
#: help:base.config.settings,module_multi_company:0
@@ -200,6 +211,9 @@ msgid ""
"companies.\n"
" This installs the module multi_company."
msgstr ""
+"Werken in omgevingen met meerdere bedrijven, met de nodige "
+"toegangsbeveiliging tussen de bedrijven.\n"
+" Hiermee installeert u de module multi_company."
#. module: base_setup
#: view:base.config.settings:0
@@ -208,6 +222,9 @@ msgid ""
"You can\n"
" launch the OpenERP Server with the option"
msgstr ""
+"Het openbare portaal is alleen toegankelijk als u zich in een enkele "
+"databasemodus bevindt. U kunt\n"
+" de OpenERP-server starten met de optie"
#. module: base_setup
#: view:base.config.settings:0
@@ -215,11 +232,13 @@ msgid ""
"You will find more options in your company details: address for the header "
"and footer, overdue payments texts, etc."
msgstr ""
+"U vindt meer opties bij de bedrijfsgegevens: adres voor de kop- en "
+"voettekst, teksten voor achterstallige betalingen, enz."
#. module: base_setup
#: model:ir.model,name:base_setup.model_sale_config_settings
msgid "sale.config.settings"
-msgstr ""
+msgstr "sale.config.settings"
#. module: base_setup
#: field:base.setup.terminology,partner:0
@@ -238,6 +257,12 @@ msgid ""
"projects,\n"
" etc."
msgstr ""
+"Wanneer u een document naar een klant stuurt,\n"
+" (offerte, factuur), kan uw klant\n"
+" aanmelden en zijn documenten bekijken,\n"
+" uw bedrijfsnieuws lezen, zijn projecten "
+"controleren,\n"
+" enz."
#. module: base_setup
#: model:ir.model,name:base_setup.model_base_setup_terminology
@@ -253,6 +278,8 @@ msgstr "Cliënt"
#: help:base.config.settings,module_portal_anonymous:0
msgid "Enable the public part of openerp, openerp becomes a public website."
msgstr ""
+"Activeer het openbare deel van OpenERP; OpenERP wordt hiermee een openbare "
+"website."
#. module: base_setup
#: help:sale.config.settings,module_plugin_thunderbird:0
@@ -265,6 +292,13 @@ msgid ""
" Partner from the selected emails.\n"
" This installs the module plugin_thunderbird."
msgstr ""
+"Met de plug-in kunt u e-mails met bijlagen koppelen aan de geselecteerde\n"
+" OpenERP-objecten. U kunt een relatie of een lead kiezen en\n"
+" de gekozen e-mail koppelen als een .eml-bestand als bijlage\n"
+" aan het geselecteerde record. U kunt documenten aanmaken "
+"voor CRM-lead\n"
+" en relaties vanuit de geselecteerde e-mails. Hiermee "
+"installeert u de module plugin_thunderbird."
#. module: base_setup
#: selection:base.setup.terminology,partner:0
@@ -280,7 +314,7 @@ msgstr "Gebruik een andere benaming voor Klant"
#: model:ir.actions.act_window,name:base_setup.action_sale_config
#: view:sale.config.settings:0
msgid "Configure Sales"
-msgstr ""
+msgstr "Verkoop instellen"
#. module: base_setup
#: help:sale.config.settings,module_plugin_outlook:0
@@ -293,16 +327,23 @@ msgid ""
" email into an OpenERP mail message with attachments.\n"
" This installs the module plugin_outlook."
msgstr ""
+"Met de Outlookplug-in kunt u een object selecteren dat u wilt toevoegen\n"
+" aan uw e-mail en de bijlagen van MS Outlook. U kunt een "
+"relatie \n"
+" of een lead kiezen en de geselecteerde e-mail archiveren in "
+"een\n"
+" OpenERP-mailbericht met bijlagen.\n"
+" Hiermee installeert u de module plugin_outlook."
#. module: base_setup
#: view:base.config.settings:0
msgid "Options"
-msgstr ""
+msgstr "Opties"
#. module: base_setup
#: field:base.config.settings,module_portal:0
msgid "Activate the customer portal"
-msgstr ""
+msgstr "Klantenportaal activeren"
#. module: base_setup
#: view:base.config.settings:0
@@ -311,36 +352,37 @@ msgid ""
" Once activated, the login page will be "
"replaced by the public website."
msgstr ""
+"Na activering zal de aanmeldpagina worden vervangen door de openbare website."
#. module: base_setup
#: field:base.config.settings,module_share:0
msgid "Allow documents sharing"
-msgstr ""
+msgstr "Sta het delen van documenten toe"
#. module: base_setup
#: view:base.config.settings:0
msgid "(company news, jobs, contact form, etc.)"
-msgstr ""
+msgstr "(bedrijfsnieuws, vacatures, contactformulier, enz.)"
#. module: base_setup
#: field:base.config.settings,module_portal_anonymous:0
msgid "Activate the public portal"
-msgstr ""
+msgstr "Openbare portaal activeren"
#. module: base_setup
#: view:base.config.settings:0
msgid "Configure outgoing email servers"
-msgstr ""
+msgstr "Uitgaande e-mailservers instellen"
#. module: base_setup
#: view:sale.config.settings:0
msgid "Social Network Integration"
-msgstr ""
+msgstr "Integratie sociale netwerken"
#. module: base_setup
#: help:base.config.settings,module_portal:0
msgid "Give your customers access to their documents."
-msgstr ""
+msgstr "Geef uw klanten toegang tot hun documenten."
#. module: base_setup
#: view:base.config.settings:0
@@ -352,7 +394,7 @@ msgstr "Afbreken"
#: view:base.config.settings:0
#: view:sale.config.settings:0
msgid "Apply"
-msgstr ""
+msgstr "Toepassen"
#. module: base_setup
#: view:base.setup.terminology:0
@@ -363,12 +405,12 @@ msgstr "Kies uw terminologie"
#: view:base.config.settings:0
#: view:sale.config.settings:0
msgid "or"
-msgstr ""
+msgstr "of"
#. module: base_setup
#: view:base.config.settings:0
msgid "Configure your company data"
-msgstr ""
+msgstr "Uw bedrijfsgegevens instellen"
#~ msgid "Logo"
#~ msgstr "Logo"
diff --git a/addons/base_status/i18n/lt.po b/addons/base_status/i18n/lt.po
new file mode 100644
index 00000000000..cdaf9b0db37
--- /dev/null
+++ b/addons/base_status/i18n/lt.po
@@ -0,0 +1,76 @@
+# Lithuanian translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2013-04-29 15:17+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Lithuanian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-04-30 05:29+0000\n"
+"X-Generator: Launchpad (build 16580)\n"
+
+#. module: base_status
+#: code:addons/base_status/base_state.py:107
+#, python-format
+msgid "Error !"
+msgstr ""
+
+#. module: base_status
+#: code:addons/base_status/base_state.py:166
+#, python-format
+msgid "%s has been opened."
+msgstr ""
+
+#. module: base_status
+#: code:addons/base_status/base_state.py:199
+#, python-format
+msgid "%s has been renewed."
+msgstr ""
+
+#. module: base_status
+#: code:addons/base_status/base_stage.py:210
+#, python-format
+msgid "Error!"
+msgstr ""
+
+#. module: base_status
+#: code:addons/base_status/base_state.py:107
+#, python-format
+msgid ""
+"You can not escalate, you are already at the top level regarding your sales-"
+"team category."
+msgstr ""
+
+#. module: base_status
+#: code:addons/base_status/base_state.py:193
+#, python-format
+msgid "%s is now pending."
+msgstr ""
+
+#. module: base_status
+#: code:addons/base_status/base_state.py:187
+#, python-format
+msgid "%s has been canceled."
+msgstr ""
+
+#. module: base_status
+#: code:addons/base_status/base_stage.py:210
+#, python-format
+msgid ""
+"You are already at the top level of your sales-team category.\n"
+"Therefore you cannot escalate furthermore."
+msgstr ""
+
+#. module: base_status
+#: code:addons/base_status/base_state.py:181
+#, python-format
+msgid "%s has been closed."
+msgstr ""
diff --git a/addons/base_vat/base_vat.py b/addons/base_vat/base_vat.py
index cfb2dbd04d0..e7501334997 100644
--- a/addons/base_vat/base_vat.py
+++ b/addons/base_vat/base_vat.py
@@ -134,6 +134,9 @@ class res_partner(osv.osv):
'vat_subjected': fields.boolean('VAT Legal Statement', help="Check this box if the partner is subjected to the VAT. It will be used for the VAT legal statement.")
}
+ def _commercial_fields(self, cr, uid, context=None):
+ return super(res_partner, self)._commercial_fields(cr, uid, context=context) + ['vat_subjected']
+
def _construct_constraint_msg(self, cr, uid, ids, context=None):
def default_vat_check(cn, vn):
# by default, a VAT number is valid if:
diff --git a/addons/contacts/i18n/lt.po b/addons/contacts/i18n/lt.po
new file mode 100644
index 00000000000..f785a308009
--- /dev/null
+++ b/addons/contacts/i18n/lt.po
@@ -0,0 +1,45 @@
+# Lithuanian translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2013-04-24 18:20+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Lithuanian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-04-25 05:20+0000\n"
+"X-Generator: Launchpad (build 16580)\n"
+
+#. module: contacts
+#: model:ir.actions.act_window,help:contacts.action_contacts
+msgid ""
+"
\n"
+" Click to add a contact in your address book.\n"
+"
\n"
+" OpenERP helps you easily track all activities related to\n"
+" a customer; discussions, history of business opportunities,\n"
+" documents, etc.\n"
+"
\n"
+" "
+msgstr ""
+"
\n"
+"Spauskite, kad sukurtumėte kontaktą adresų knygoje.\n"
+"
\n"
+"OpenERP pagalba galima stebėti visus veiksmus susijusius su\n"
+"kontaktu; bendravimas, pardavimų galimybių istorija,\n"
+"dokumentai, ir t.t.\n"
+"
\n"
+" "
+
+#. module: contacts
+#: model:ir.actions.act_window,name:contacts.action_contacts
+#: model:ir.ui.menu,name:contacts.menu_contacts
+msgid "Contacts"
+msgstr "Kontaktai"
diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py
index d78fb09eae7..4e3a704d5f3 100644
--- a/addons/crm/__init__.py
+++ b/addons/crm/__init__.py
@@ -28,6 +28,7 @@ import report
import wizard
import res_partner
import res_config
+import base_partner_merge
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index 072624a7d98..8e17a882076 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -97,6 +97,7 @@ Dashboard for CRM will include:
'board_crm_view.xml',
'res_config_view.xml',
+ 'base_partner_merge_view.xml',
],
'demo': [
'crm_demo.xml',
diff --git a/addons/crm/base_partner_merge.py b/addons/crm/base_partner_merge.py
new file mode 100644
index 00000000000..b2645e7fb79
--- /dev/null
+++ b/addons/crm/base_partner_merge.py
@@ -0,0 +1,760 @@
+#!/usr/bin/env python
+from __future__ import absolute_import
+from email.utils import parseaddr
+import functools
+import htmlentitydefs
+import itertools
+import logging
+import operator
+import re
+from ast import literal_eval
+from openerp.tools import mute_logger
+
+# Validation Library https://pypi.python.org/pypi/validate_email/1.1
+from .validate_email import validate_email
+
+from openerp.osv import osv, orm
+from openerp.osv import fields
+from openerp.osv.orm import browse_record
+from openerp.tools.translate import _
+
+pattern = re.compile("&(\w+?);")
+
+_logger = logging.getLogger('base.partner.merge')
+
+
+# http://www.php2python.com/wiki/function.html-entity-decode/
+def html_entity_decode_char(m, defs=htmlentitydefs.entitydefs):
+ try:
+ return defs[m.group(1)]
+ except KeyError:
+ return m.group(0)
+
+
+def html_entity_decode(string):
+ return pattern.sub(html_entity_decode_char, string)
+
+
+def sanitize_email(email):
+ assert isinstance(email, basestring) and email
+
+ result = re.subn(r';|/|:', ',',
+ html_entity_decode(email or ''))[0].split(',')
+
+ emails = [parseaddr(email)[1]
+ for item in result
+ for email in item.split()]
+
+ return [email.lower()
+ for email in emails
+ if validate_email(email)]
+
+
+def is_integer_list(ids):
+ return all(isinstance(i, (int, long)) for i in ids)
+
+
+class ResPartner(osv.Model):
+ _inherit = 'res.partner'
+
+ _columns = {
+ 'id': fields.integer('Id', readonly=True),
+ 'create_date': fields.datetime('Create Date', readonly=True),
+ }
+
+class MergePartnerLine(osv.TransientModel):
+ _name = 'base.partner.merge.line'
+
+ _columns = {
+ 'wizard_id': fields.many2one('base.partner.merge.automatic.wizard',
+ 'Wizard'),
+ 'min_id': fields.integer('MinID'),
+ 'aggr_ids': fields.char('Ids', required=True),
+ }
+
+ _order = 'min_id asc'
+
+
+class MergePartnerAutomatic(osv.TransientModel):
+ """
+ The idea behind this wizard is to create a list of potential partners to
+ merge. We use two objects, the first one is the wizard for the end-user.
+ And the second will contain the partner list to merge.
+ """
+ _name = 'base.partner.merge.automatic.wizard'
+
+ _columns = {
+ # Group by
+ 'group_by_email': fields.boolean('Email'),
+ 'group_by_name': fields.boolean('Name'),
+ 'group_by_is_company': fields.boolean('Is Company'),
+ 'group_by_vat': fields.boolean('VAT'),
+ 'group_by_parent_id': fields.boolean('Parent Company'),
+
+ 'state': fields.selection([('option', 'Option'),
+ ('selection', 'Selection'),
+ ('finished', 'Finished')],
+ 'State',
+ readonly=True,
+ required=True),
+ 'number_group': fields.integer("Group of Contacts", readonly=True),
+ 'current_line_id': fields.many2one('base.partner.merge.line', 'Current Line'),
+ 'line_ids': fields.one2many('base.partner.merge.line', 'wizard_id', 'Lines'),
+ 'partner_ids': fields.many2many('res.partner', string='Contacts'),
+
+ 'exclude_contact': fields.boolean('A user associated to the contact'),
+ 'exclude_journal_item': fields.boolean('Journal Items associated to the contact'),
+ 'maximum_group': fields.integer("Maximum of Group of Contacts"),
+ }
+
+ _defaults = {
+ 'state': 'option',
+ }
+
+ def get_fk_on(self, cr, table):
+ q = """ SELECT cl1.relname as table,
+ att1.attname as column
+ FROM pg_constraint as con, pg_class as cl1, pg_class as cl2,
+ pg_attribute as att1, pg_attribute as att2
+ WHERE con.conrelid = cl1.oid
+ AND con.confrelid = cl2.oid
+ AND array_lower(con.conkey, 1) = 1
+ AND con.conkey[1] = att1.attnum
+ AND att1.attrelid = cl1.oid
+ AND cl2.relname = %s
+ AND att2.attname = 'id'
+ AND array_lower(con.confkey, 1) = 1
+ AND con.confkey[1] = att2.attnum
+ AND att2.attrelid = cl2.oid
+ AND con.contype = 'f'
+ """
+ return cr.execute(q, (table,))
+
+ def _update_foreign_keys(self, cr, uid, src_partners, dst_partner, context=None):
+ _logger.debug('_update_foreign_keys for dst_partner: %s for src_partners: %r', dst_partner.id, list(map(operator.attrgetter('id'), src_partners)))
+
+ # find the many2one relation to a partner
+ proxy = self.pool.get('res.partner')
+ self.get_fk_on(cr, 'res_partner')
+
+ # ignore two tables
+
+ for table, column in cr.fetchall():
+ if 'base_partner_merge_' in table:
+ continue
+ partner_ids = tuple(map(int, src_partners))
+
+ query = "SELECT column_name FROM information_schema.columns WHERE table_name LIKE '%s'" % (table)
+ cr.execute(query, ())
+ columns = []
+ for data in cr.fetchall():
+ if data[0] != column:
+ columns.append(data[0])
+
+ query_dic = {
+ 'table': table,
+ 'column': column,
+ 'value': columns[0],
+ }
+ if len(columns) <= 1:
+ # unique key treated
+ query = """
+ UPDATE "%(table)s" as ___tu
+ SET %(column)s = %%s
+ WHERE
+ %(column)s = %%s AND
+ NOT EXISTS (
+ SELECT 1
+ FROM "%(table)s" as ___tw
+ WHERE
+ %(column)s = %%s AND
+ ___tu.%(value)s = ___tw.%(value)s
+ )""" % query_dic
+ for partner_id in partner_ids:
+ cr.execute(query, (dst_partner.id, partner_id, dst_partner.id))
+ else:
+ cr.execute("SAVEPOINT recursive_partner_savepoint")
+ try:
+ query = 'UPDATE "%(table)s" SET %(column)s = %%s WHERE %(column)s IN %%s' % query_dic
+ cr.execute(query, (dst_partner.id, partner_ids,))
+
+ if column == proxy._parent_name and table == 'res_partner':
+ query = """
+ WITH RECURSIVE cycle(id, parent_id) AS (
+ SELECT id, parent_id FROM res_partner
+ UNION
+ SELECT cycle.id, res_partner.parent_id
+ FROM res_partner, cycle
+ WHERE res_partner.id = cycle.parent_id AND
+ cycle.id != cycle.parent_id
+ )
+ SELECT id FROM cycle WHERE id = parent_id AND id = %s
+ """
+ cr.execute(query, (dst_partner.id,))
+ if cr.fetchall():
+ cr.execute("ROLLBACK TO SAVEPOINT recursive_partner_savepoint")
+ finally:
+ cr.execute("RELEASE SAVEPOINT recursive_partner_savepoint")
+
+ def _update_reference_fields(self, cr, uid, src_partners, dst_partner, context=None):
+ _logger.debug('_update_reference_fields for dst_partner: %s for src_partners: %r', dst_partner.id, list(map(operator.attrgetter('id'), src_partners)))
+
+ def update_records(model, src, field_model='model', field_id='res_id', context=None):
+ proxy = self.pool.get(model)
+ if proxy is None:
+ return
+ domain = [(field_model, '=', 'res.partner'), (field_id, '=', src.id)]
+ ids = proxy.search(cr, uid, domain, context=context)
+ return proxy.write(cr, uid, ids, {field_id: dst_partner.id}, context=context)
+
+ update_records = functools.partial(update_records, context=context)
+
+ for partner in src_partners:
+ update_records('base.calendar', src=partner, field_model='model_id.model')
+ update_records('ir.attachment', src=partner, field_model='res_model')
+ update_records('mail.followers', src=partner, field_model='res_model')
+ update_records('mail.message', src=partner)
+ update_records('marketing.campaign.workitem', src=partner, field_model='object_id.model')
+ update_records('ir.model.data', src=partner)
+
+ proxy = self.pool['ir.model.fields']
+ domain = [('ttype', '=', 'reference')]
+ record_ids = proxy.search(cr, uid, domain, context=context)
+
+ for record in proxy.browse(cr, uid, record_ids, context=context):
+ proxy_model = self.pool[record.model]
+
+ field_type = proxy_model._columns.get(record.name).__class__._type
+
+ if field_type == 'function':
+ continue
+
+ for partner in src_partners:
+ domain = [
+ (record.name, '=', 'res.partner,%d' % partner.id)
+ ]
+ model_ids = proxy_model.search(cr, uid, domain, context=context)
+ values = {
+ record.name: 'res.partner,%d' % dst_partner.id,
+ }
+ proxy_model.write(cr, uid, model_ids, values, context=context)
+
+ def _update_values(self, cr, uid, src_partners, dst_partner, context=None):
+ _logger.debug('_update_values for dst_partner: %s for src_partners: %r', dst_partner.id, list(map(operator.attrgetter('id'), src_partners)))
+
+ columns = dst_partner._columns
+ def write_serializer(column, item):
+ if isinstance(item, browse_record):
+ return item.id
+ else:
+ return item
+
+ values = dict()
+ for column, field in columns.iteritems():
+ if field._type not in ('many2many', 'one2many', 'function'):
+ for item in itertools.chain(src_partners, [dst_partner]):
+ if item[column]:
+ values[column] = write_serializer(column, item[column])
+
+ values.pop('id', None)
+ parent_id = values.pop('parent_id', None)
+ dst_partner.write(values)
+ if parent_id and parent_id != dst_partner.id:
+ try:
+ dst_partner.write({'parent_id': parent_id})
+ except (osv.except_osv, orm.except_orm):
+ _logger.info('Skip recursive partner hierarchies for parent_id %s of partner: %s', parent_id, dst_partner.id)
+
+ @mute_logger('openerp.osv.expression', 'openerp.osv.orm')
+ def _merge(self, cr, uid, partner_ids, context=None):
+ proxy = self.pool.get('res.partner')
+
+ partner_ids = proxy.exists(cr, uid, list(partner_ids), context=context)
+ if len(partner_ids) < 2:
+ return
+
+ partners = proxy.browse(cr, uid, list(partner_ids), context=context)
+ ordered_partners = sorted(sorted(partners,
+ key=operator.attrgetter('create_date'), reverse=True),
+ key=operator.attrgetter('active'), reverse=True)
+
+ dst_partner = ordered_partners[-1]
+ src_partners = ordered_partners[:-1]
+ _logger.info("dst_partner: %s", dst_partner.id)
+
+ call_it = lambda function: function(cr, uid, src_partners, dst_partner,
+ context=context)
+
+ call_it(self._update_foreign_keys)
+ call_it(self._update_reference_fields)
+ call_it(self._update_values)
+
+ _logger.info("---merged---")
+
+ for partner in src_partners:
+ partner.unlink()
+
+
+ def clean_emails(self, cr, uid, context=None):
+ """
+ Clean the email address of the partner, if there is an email field with
+ a mimum of two addresses, the system will create a new partner, with the
+ information of the previous one and will copy the new cleaned email into
+ the email field.
+ """
+ if context is None:
+ context = {}
+
+ proxy_model = self.pool['ir.model.fields']
+ field_ids = proxy_model.search(cr, uid, [('model', '=', 'res.partner'),
+ ('ttype', 'like', '%2many')],
+ context=context)
+ fields = proxy_model.read(cr, uid, field_ids, context=context)
+ reset_fields = dict((field['name'], []) for field in fields)
+
+ proxy_partner = self.pool['res.partner']
+ context['active_test'] = False
+ ids = proxy_partner.search(cr, uid, [], context=context)
+
+ fields = ['name', 'var' 'partner_id' 'is_company', 'email']
+ partners = proxy_partner.read(cr, uid, ids, fields, context=context)
+
+ partners.sort(key=operator.itemgetter('id'))
+ partners_len = len(partners)
+
+ _logger.info('partner_len: %r', partners_len)
+
+ for idx, partner in enumerate(partners):
+ if not partner['email']:
+ continue
+
+ percent = (idx / float(partners_len)) * 100.0
+ _logger.info('idx: %r', idx)
+ _logger.info('percent: %r', percent)
+ try:
+ emails = sanitize_email(partner['email'])
+ head, tail = emails[:1], emails[1:]
+ email = head[0] if head else False
+
+ proxy_partner.write(cr, uid, [partner['id']],
+ {'email': email}, context=context)
+
+ for email in tail:
+ values = dict(reset_fields, email=email)
+ proxy_partner.copy(cr, uid, partner['id'], values,
+ context=context)
+
+ except Exception:
+ _logger.exception("There is a problem with this partner: %r", partner)
+ raise
+ return True
+
+ def close_cb(self, cr, uid, ids, context=None):
+ return {'type': 'ir.actions.act_window_close'}
+
+ def _generate_query(self, fields, maximum_group=100):
+ group_fields = ', '.join(fields)
+
+ filters = []
+ for field in fields:
+ if field in ['email', 'name']:
+ filters.append((field, 'IS NOT', 'NULL'))
+
+ criteria = ' AND '.join('%s %s %s' % (field, operator, value)
+ for field, operator, value in filters)
+
+ text = [
+ "SELECT min(id), array_agg(id)",
+ "FROM res_partner",
+ ]
+
+ if criteria:
+ text.append('WHERE %s' % criteria)
+
+ text.extend([
+ "GROUP BY %s" % group_fields,
+ "HAVING COUNT(*) >= 2",
+ "ORDER BY min(id)",
+ ])
+
+ if maximum_group:
+ text.extend([
+ "LIMIT %s" % maximum_group,
+ ])
+
+ return ' '.join(text)
+
+ def _compute_selected_groupby(self, this):
+ group_by_str = 'group_by_'
+ group_by_len = len(group_by_str)
+
+ fields = [
+ key[group_by_len:]
+ for key in self._columns.keys()
+ if key.startswith(group_by_str)
+ ]
+
+ groups = [
+ field
+ for field in fields
+ if getattr(this, '%s%s' % (group_by_str, field), False)
+ ]
+
+ if not groups:
+ raise osv.except_osv(_('Error'),
+ _("You have to specify a filter for your selection"))
+
+ return groups
+
+ def next_cb(self, cr, uid, ids, context=None):
+ """
+ Don't compute any thing
+ """
+ context = dict(context or {}, active_test=False)
+ this = self.browse(cr, uid, ids[0], context=context)
+ if this.current_line_id:
+ this.current_line_id.unlink()
+ return self._next_screen(this)
+
+ def _next_screen(self, this):
+ this.refresh()
+ values = {}
+ if this.line_ids:
+ # in this case, we try to find the next record.
+ current_line = this.line_ids[0]
+ current_partner_ids = literal_eval(current_line.aggr_ids)
+ values.update({
+ 'current_line_id': current_line.id,
+ 'partner_ids': [(6, 0, current_partner_ids)],
+ 'state': 'selection',
+ })
+ else:
+ values.update({
+ 'current_line_id': False,
+ 'partner_ids': [],
+ 'state': 'finished',
+ })
+
+ this.write(values)
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': this._name,
+ 'res_id': this.id,
+ 'view_mode': 'form',
+ 'target': 'new',
+ }
+
+ def _model_is_installed(self, cr, uid, model, context=None):
+ proxy = self.pool.get('ir.model')
+ domain = [('model', '=', model)]
+ return proxy.search_count(cr, uid, domain, context=context) > 0
+
+ def _partner_use_in(self, cr, uid, aggr_ids, models, context=None):
+ """
+ Check if there is no occurence of this group of partner in the selected
+ model
+ """
+ for model, field in models.iteritems():
+ proxy = self.pool.get(model)
+ domain = [(field, 'in', aggr_ids)]
+ if proxy.search_count(cr, uid, domain, context=context):
+ return True
+ return False
+
+ def compute_models(self, cr, uid, ids, context=None):
+ """
+ Compute the different models needed by the system if you want to exclude
+ some partners.
+ """
+ assert is_integer_list(ids)
+
+ this = self.browse(cr, uid, ids[0], context=context)
+
+ models = {}
+ if this.exclude_contact:
+ models['res.users'] = 'partner_id'
+
+ if self._model_is_installed(cr, uid, 'account.move.line', context=context) and this.exclude_journal_item:
+ models['account.move.line'] = 'partner_id'
+
+ return models
+
+ def _process_query(self, cr, uid, ids, query, context=None):
+ """
+ Execute the select request and write the result in this wizard
+ """
+ proxy = self.pool.get('base.partner.merge.line')
+ this = self.browse(cr, uid, ids[0], context=context)
+ models = self.compute_models(cr, uid, ids, context=context)
+ cr.execute(query)
+
+ counter = 0
+ for min_id, aggr_ids in cr.fetchall():
+ if models and self._partner_use_in(cr, uid, aggr_ids, models, context=context):
+ continue
+ values = {
+ 'wizard_id': this.id,
+ 'min_id': min_id,
+ 'aggr_ids': aggr_ids,
+ }
+
+ proxy.create(cr, uid, values, context=context)
+ counter += 1
+
+ values = {
+ 'state': 'selection',
+ 'number_group': counter,
+ }
+
+ this.write(values)
+
+ _logger.info("counter: %s", counter)
+
+ def start_process_cb(self, cr, uid, ids, context=None):
+ """
+ Start the process.
+ * Compute the selected groups (with duplication)
+ * If the user has selected the 'exclude_XXX' fields, avoid the partners.
+ """
+ assert is_integer_list(ids)
+
+ context = dict(context or {}, active_test=False)
+ this = self.browse(cr, uid, ids[0], context=context)
+ groups = self._compute_selected_groupby(this)
+ query = self._generate_query(groups, this.maximum_group)
+ self._process_query(cr, uid, ids, query, context=context)
+
+ return self._next_screen(this)
+
+ def automatic_process_cb(self, cr, uid, ids, context=None):
+ assert is_integer_list(ids)
+ this = self.browse(cr, uid, ids[0], context=context)
+ this.start_process_cb()
+ this.refresh()
+
+ for line in this.line_ids:
+ partner_ids = literal_eval(line.aggr_ids)
+ self._merge(cr, uid, partner_ids, context=context)
+ line.unlink()
+ cr.commit()
+
+ this.write({'state': 'finished'})
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': this._name,
+ 'res_id': this.id,
+ 'view_mode': 'form',
+ 'target': 'new',
+ }
+
+ def parent_migration_process_cb(self, cr, uid, ids, context=None):
+ assert is_integer_list(ids)
+
+ context = dict(context or {}, active_test=False)
+ this = self.browse(cr, uid, ids[0], context=context)
+
+ query = """
+ SELECT
+ min(p1.id),
+ array_agg(DISTINCT p1.id)
+ FROM
+ res_partner as p1
+ INNER join
+ res_partner as p2
+ ON
+ p1.email = p2.email AND
+ p1.name = p2.name AND
+ (p1.parent_id = p2.id OR p1.id = p2.parent_id)
+ WHERE
+ p2.id IS NOT NULL
+ GROUP BY
+ p1.email,
+ p1.name,
+ CASE WHEN p1.parent_id = p2.id THEN p2.id
+ ELSE p1.id
+ END
+ HAVING COUNT(*) >= 2
+ ORDER BY
+ min(p1.id)
+ """
+
+ self._process_query(cr, uid, ids, query, context=context)
+
+ for line in this.line_ids:
+ partner_ids = literal_eval(line.aggr_ids)
+ self._merge(cr, uid, partner_ids, context=context)
+ line.unlink()
+ cr.commit()
+
+ this.write({'state': 'finished'})
+
+ cr.execute("""
+ UPDATE
+ res_partner
+ SET
+ is_company = NULL,
+ parent_id = NULL
+ WHERE
+ parent_id = id
+ """)
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': this._name,
+ 'res_id': this.id,
+ 'view_mode': 'form',
+ 'target': 'new',
+ }
+
+ def update_all_process_cb(self, cr, uid, ids, context=None):
+ assert is_integer_list(ids)
+
+ # WITH RECURSIVE cycle(id, parent_id) AS (
+ # SELECT id, parent_id FROM res_partner
+ # UNION
+ # SELECT cycle.id, res_partner.parent_id
+ # FROM res_partner, cycle
+ # WHERE res_partner.id = cycle.parent_id AND
+ # cycle.id != cycle.parent_id
+ # )
+ # UPDATE res_partner
+ # SET parent_id = NULL
+ # WHERE id in (SELECT id FROM cycle WHERE id = parent_id);
+
+ this = self.browse(cr, uid, ids[0], context=context)
+
+ self.parent_migration_process_cb(cr, uid, ids, context=None)
+
+ list_merge = [
+ {'group_by_vat': True, 'group_by_email': True, 'group_by_name': True},
+ # {'group_by_name': True, 'group_by_is_company': True, 'group_by_parent_id': True},
+ # {'group_by_email': True, 'group_by_is_company': True, 'group_by_parent_id': True},
+ # {'group_by_name': True, 'group_by_vat': True, 'group_by_is_company': True, 'exclude_journal_item': True},
+ # {'group_by_email': True, 'group_by_vat': True, 'group_by_is_company': True, 'exclude_journal_item': True},
+ # {'group_by_email': True, 'group_by_is_company': True, 'exclude_contact': True, 'exclude_journal_item': True},
+ # {'group_by_name': True, 'group_by_is_company': True, 'exclude_contact': True, 'exclude_journal_item': True}
+ ]
+
+ for merge_value in list_merge:
+ id = self.create(cr, uid, merge_value, context=context)
+ self.automatic_process_cb(cr, uid, [id], context=context)
+
+ cr.execute("""
+ UPDATE
+ res_partner
+ SET
+ is_company = NULL
+ WHERE
+ parent_id IS NOT NULL AND
+ is_company IS NOT NULL
+ """)
+
+ # cr.execute("""
+ # UPDATE
+ # res_partner as p1
+ # SET
+ # is_company = NULL,
+ # parent_id = (
+ # SELECT p2.id
+ # FROM res_partner as p2
+ # WHERE p2.email = p1.email AND
+ # p2.parent_id != p2.id
+ # LIMIT 1
+ # )
+ # WHERE
+ # p1.parent_id = p1.id
+ # """)
+
+ return self._next_screen(this)
+
+ def merge_cb(self, cr, uid, ids, context=None):
+ assert is_integer_list(ids)
+
+ context = dict(context or {}, active_test=False)
+ this = self.browse(cr, uid, ids[0], context=context)
+
+ partner_ids = set(map(int, this.partner_ids))
+ if not partner_ids:
+ this.write({'state': 'finished'})
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': this._name,
+ 'res_id': this.id,
+ 'view_mode': 'form',
+ 'target': 'new',
+ }
+
+ self._merge(cr, uid, partner_ids, context=context)
+
+ this.current_line_id.unlink()
+
+ return self._next_screen(this)
+
+ def merge_multi(self, cr, uid, ids, context=None):
+
+ active_model = context.get('active_model')
+ if active_model != 'res.partner':
+ raise osv.except_osv(
+ _('Error'),
+ _('This wizard can only used with the Partners')
+ )
+
+ partner_ids = context.get('active_ids', [])
+
+ MINIMAL_NUMBER = 2
+ if len(partner_ids) < MINIMAL_NUMBER:
+ raise osv.except_osv(
+ _('Error'),
+ _("You can't use this wizard with only one Partner")
+ )
+
+ self._merge(cr, uid, partner_ids, context=context)
+
+ return True
+
+ def auto_set_parent_id(self, cr, uid, ids, context=None):
+ assert is_integer_list(ids)
+
+ # select partner who have one least invoice
+ partner_treated = ['@gmail.com']
+ cr.execute(""" SELECT p.id, p.email
+ FROM res_partner as p
+ LEFT JOIN account_invoice as a
+ ON p.id = a.partner_id AND a.state in ('open','paid')
+ WHERE p.grade_id is NOT NULL
+ GROUP BY p.id
+ ORDER BY COUNT(a.id) DESC
+ """)
+ re_email = re.compile(r".*@")
+ for id, email in cr.fetchall():
+ # check email domain
+ email = re_email.sub("@", email or "")
+ if not email or email in partner_treated:
+ continue
+ partner_treated.append(email)
+
+ # don't update the partners if they are more of one who have invoice
+ cr.execute(""" SELECT *
+ FROM res_partner as p
+ WHERE p.id != %s AND p.email LIKE '%%%s' AND
+ EXISTS (SELECT * FROM account_invoice as a WHERE p.id = a.partner_id AND a.state in ('open','paid'))
+ """ % (id, email))
+
+ if len(cr.fetchall()) > 1:
+ _logger.info("%s MORE OF ONE COMPANY", email)
+ continue
+
+ # to display changed values
+ cr.execute(""" SELECT id,email
+ FROM res_partner
+ WHERE parent_id != %s AND id != %s AND email LIKE '%%%s'
+ """ % (id, id, email))
+ _logger.info("%r", cr.fetchall())
+
+ # upgrade
+ cr.execute(""" UPDATE res_partner
+ SET parent_id = %s
+ WHERE id != %s AND email LIKE '%%%s'
+ """ % (id, id, email))
+ return False
diff --git a/addons/crm/base_partner_merge_view.xml b/addons/crm/base_partner_merge_view.xml
new file mode 100644
index 00000000000..74e7a72d7a8
--- /dev/null
+++ b/addons/crm/base_partner_merge_view.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+ Deduplicate Contacts
+ base.partner.merge.automatic.wizard
+ form
+ form
+ new
+ {'active_test': False}
+
+
+
+
+
+ base.partner.merge.automatic.wizard.form
+ base.partner.merge.automatic.wizard
+
+
+
+
+
+
+ pool.get('base.partner.merge.automatic.wizard').merge_multi(cr, uid, None, context)
+ True
+
+ Automatic Merge
+
+ code
+ ir.actions.server
+
+
+
+
+ res.partner
+ Automatic Merge
+
+
+
+
+
+
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 2361f12d61b..61e8c75cea8 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -1053,6 +1053,14 @@ class crm_lead(base_stage, format_address, osv.osv):
message = _("%s a call for %s.%s") % (prefix, phonecall.date, suffix)
return self.message_post(cr, uid, ids, body=message, context=context)
+ def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None):
+ if not duration:
+ duration = _('unknown')
+ else:
+ duration = str(duration)
+ message = _("Meeting scheduled at '%s' Subject: %s Duration: %s hour(s)") % (meeting_date, meeting_subject, duration)
+ return self.message_post(cr, uid, ids, body=message, context=context)
+
def onchange_state(self, cr, uid, ids, state_id, context=None):
if state_id:
country_id=self.pool.get('res.country.state').browse(cr, uid, state_id, context).country_id.id
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index d94683f895f..8c22a0a2e1c 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -330,7 +330,7 @@
-
+
@@ -548,7 +548,7 @@
-
+
diff --git a/addons/crm/crm_meeting.py b/addons/crm/crm_meeting.py
index 14b69ed0e11..c31907bebe2 100644
--- a/addons/crm/crm_meeting.py
+++ b/addons/crm/crm_meeting.py
@@ -34,6 +34,13 @@ class crm_meeting(osv.Model):
'opportunity_id': fields.many2one ('crm.lead', 'Opportunity', domain="[('type', '=', 'opportunity')]"),
}
+ def create(self, cr, uid, vals, context=None):
+ res = super(crm_meeting, self).create(cr, uid, vals, context=context)
+ obj = self.browse(cr, uid, res, context=context)
+ if obj.opportunity_id:
+ self.pool.get('crm.lead').log_meeting(cr, uid, [obj.opportunity_id.id], obj.name, obj.date, obj.duration, context=context)
+ return res
+
class calendar_attendee(osv.osv):
""" Calendar Attendee """
diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml
index 3efb54f98b8..6c1b31180be 100644
--- a/addons/crm/crm_phonecall_view.xml
+++ b/addons/crm/crm_phonecall_view.xml
@@ -186,7 +186,7 @@
-
+
diff --git a/addons/crm/i18n/nl_BE.po b/addons/crm/i18n/nl_BE.po
index 697c7c4f083..a22d1f605a6 100644
--- a/addons/crm/i18n/nl_BE.po
+++ b/addons/crm/i18n/nl_BE.po
@@ -1,20 +1,20 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * crm
-# Els Van Vossel , 2012.
+# Els Van Vossel , 2012, 2013.
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
-"PO-Revision-Date: 2012-10-02 07:29+0000\n"
+"PO-Revision-Date: 2013-04-26 23:39+0000\n"
"Last-Translator: Els Van Vossel (Agaplan) \n"
"Language-Team: Els Van Vossel\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-03-16 05:09+0000\n"
-"X-Generator: Launchpad (build 16532)\n"
+"X-Launchpad-Export-Date: 2013-04-27 05:44+0000\n"
+"X-Generator: Launchpad (build 16580)\n"
"Language: nl\n"
#. module: crm
@@ -28,6 +28,8 @@ msgid ""
"Allows you to configure your incoming mail server, and create leads from "
"incoming emails."
msgstr ""
+"Hiermee kunt u de inkomende mailserver instellen en leads laten maken van "
+"binnenkomende e-mails."
#. module: crm
#: code:addons/crm/crm_lead.py:881
@@ -76,7 +78,7 @@ msgstr "Opportuniteiten kiezen"
#: model:res.groups,name:crm.group_fund_raising
#: field:sale.config.settings,group_fund_raising:0
msgid "Manage Fund Raising"
-msgstr ""
+msgstr "Fondsenwerving"
#. module: crm
#: view:crm.lead.report:0
@@ -96,7 +98,7 @@ msgstr "Fasenaam"
#: view:crm.lead.report:0
#: view:crm.phonecall.report:0
msgid "Salesperson"
-msgstr ""
+msgstr "Verkoper"
#. module: crm
#: model:ir.model,name:crm.model_crm_lead_report
@@ -113,18 +115,18 @@ msgstr "Dag"
#. module: crm
#: view:crm.lead:0
msgid "Company Name"
-msgstr ""
+msgstr "Bedrijfsnaam"
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor6
msgid "Training"
-msgstr ""
+msgstr "Training"
#. module: crm
#: model:ir.actions.act_window,name:crm.crm_lead_categ_action
#: model:ir.ui.menu,name:crm.menu_crm_lead_categ
msgid "Sales Tags"
-msgstr ""
+msgstr "Verkooplabels"
#. module: crm
#: view:crm.lead.report:0
@@ -137,6 +139,7 @@ msgstr "Verw. sluiting"
#: help:crm.phonecall,message_unread:0
msgid "If checked new messages require your attention."
msgstr ""
+"Als dit is ingeschakeld, zijn er nieuwe berichten die uw aandacht vragen."
#. module: crm
#: help:crm.lead.report,creation_day:0
@@ -182,6 +185,8 @@ msgid ""
"Holds the Chatter summary (number of messages, ...). This summary is "
"directly in html format in order to be inserted in kanban views."
msgstr ""
+"Bevat de Chatsamenvatting (aantal berichten, ...). Deze samenvatting is in "
+"html-formaat, zodat ze in de kanbanweergave kan worden gebruikt."
#. module: crm
#: code:addons/crm/crm_lead.py:624
@@ -189,7 +194,7 @@ msgstr ""
#: code:addons/crm/crm_phonecall.py:280
#, python-format
msgid "Warning!"
-msgstr ""
+msgstr "Waarschuwing"
#. module: crm
#: view:crm.lead:0
@@ -238,7 +243,7 @@ msgstr ""
#. module: crm
#: model:ir.actions.server,name:crm.action_email_reminder_lead
msgid "Reminder to User"
-msgstr ""
+msgstr "Herinnering voor gebruiker"
#. module: crm
#: field:crm.segmentation,segmentation_line:0
@@ -266,7 +271,7 @@ msgstr "Leadanalyse"
#: code:addons/crm/crm_lead.py:1010
#, python-format
msgid "%s a call for the %s."
-msgstr ""
+msgstr "%s een gesprek voor %s."
#. module: crm
#: model:ir.actions.act_window,name:crm.crm_case_resource_type_act
@@ -328,6 +333,16 @@ msgid ""
"
\n"
" "
msgstr ""
+"
\n"
+" Klik als u een nieuwe klantensegmentering wilt instellen.\n"
+"
\n"
+" Maak specifiieke categorieën en ken deze toe aan uw\n"
+" contactpersonen voor een betere interactie. Via\n"
+" segmentering kunt u categorieën toekennen aan "
+"contactpersonen\n"
+" volgens de door u ingestelde criteria.\n"
+"
\n"
+" "
#. module: crm
#: field:crm.opportunity2phonecall,contact_name:0
@@ -341,6 +356,7 @@ msgstr "Contactpersoon"
msgid ""
"When escalating to this team override the salesman with the team leader."
msgstr ""
+"Bij escaleren naar dit team, wordt de verkoper vervangen door de teamleider."
#. module: crm
#: model:process.transition,name:crm.process_transition_opportunitymeeting0
@@ -380,12 +396,25 @@ msgid ""
" \n"
" "
msgstr ""
+"
\n"
+" Klik om een opportuniteit voor deze klant te maken.\n"
+"
\n"
+" Met opportuniteiten kunt u uw verkooppijplijn en uw "
+"mogelijke verkopen opvolgen,\n"
+" voor een beter zicht op te verwachten inkomsten.\n"
+"
\n"
+" U kunt vergaderingen en telefoongesprekken plannen\n"
+" voor opportuniteiten. U kunt van een opportuniteit een "
+"offerte maken, documenten\n"
+" toevoegen, discussies volgen, en nog veel meer.\n"
+"
\n"
+" "
#. module: crm
#: model:crm.case.stage,name:crm.stage_lead7
#: view:crm.lead:0
msgid "Dead"
-msgstr ""
+msgstr "Dood"
#. module: crm
#: field:crm.case.section,message_unread:0
@@ -393,7 +422,7 @@ msgstr ""
#: field:crm.lead,message_unread:0
#: field:crm.phonecall,message_unread:0
msgid "Unread Messages"
-msgstr ""
+msgstr "Ongelezen berichten"
#. module: crm
#: view:crm.segmentation:0
@@ -407,7 +436,7 @@ msgstr "Segmentering"
#: selection:crm.lead2opportunity.partner.mass,action:0
#: selection:crm.partner.binding,action:0
msgid "Link to an existing customer"
-msgstr ""
+msgstr "Koppelen aan een bestaande relatie"
#. module: crm
#: field:crm.lead,write_date:0
@@ -425,8 +454,8 @@ msgid ""
"This percentage depicts the default/average probability of the Case for this "
"stage to be a success"
msgstr ""
-"Dit percentage drukt de standaard/gemiddelde slaagkans uit voor de zaak in "
-"deze fase."
+"Dit percentage drukt de standaard/gemiddelde slagingskans uit voor de zaak "
+"in deze fase."
#. module: crm
#: view:crm.lead:0
@@ -454,6 +483,7 @@ msgstr ""
#: view:crm.lead:0
msgid "Leads that are assigned to one of the sale teams I manage, or to me"
msgstr ""
+"Leads toegewezen aan een van de verkoopteams onder mijn beheer, of aan mij"
#. module: crm
#: field:crm.lead,partner_address_email:0
@@ -472,6 +502,15 @@ msgid ""
" \n"
" "
msgstr ""
+"
\n"
+" Klik als u een nieuw verkoopteam wilt maken.\n"
+"
\n"
+" Met verkoopteams organiseert u meerdere verkopers of "
+"afdelingen in\n"
+" aparte teams. Elk team werkt op zijn eigen\n"
+" opportuniteitenlijst.\n"
+"
\n"
+" "
#. module: crm
#: model:process.transition,note:crm.process_transition_opportunitymeeting0
@@ -485,7 +524,7 @@ msgstr "Gewone of telefonische vergadering over opportuniteit"
#: view:crm.phonecall.report:0
#: field:crm.phonecall.report,state:0
msgid "Status"
-msgstr ""
+msgstr "Status"
#. module: crm
#: view:crm.lead2opportunity.partner:0
@@ -495,7 +534,7 @@ msgstr "Opportuniteit maken"
#. module: crm
#: view:sale.config.settings:0
msgid "Configure"
-msgstr ""
+msgstr "Instellen"
#. module: crm
#: view:crm.lead:0
@@ -555,18 +594,26 @@ msgid ""
" If the call needs to be done then the status is set "
"to 'Not Held'."
msgstr ""
+"De status wordt op 'Uit te voeren' gezet als er een zaak wordt gemaakt. "
+" \n"
+"Als de zaak lopende is, wordt de status op 'Open' gezet. "
+" \n"
+"Als het gesprek is gevoerd, wordt de status op 'Uitgevoerd' gezet. "
+" \n"
+"Als het gesprek nog moet worden uitgevoerd, gaat de status naar 'Niet "
+"uitgevoerd'."
#. module: crm
#: field:crm.case.section,message_summary:0
#: field:crm.lead,message_summary:0
#: field:crm.phonecall,message_summary:0
msgid "Summary"
-msgstr "Samenvatting"
+msgstr "Overzicht"
#. module: crm
#: view:crm.merge.opportunity:0
msgid "Merge"
-msgstr ""
+msgstr "Samenvoegen"
#. module: crm
#: model:email.template,subject:crm.email_template_opportunity_mail
@@ -589,6 +636,8 @@ msgid ""
"Reminder on Lead: [[object.id ]] [[object.partner_id and 'of ' "
"+object.partner_id.name or '']]"
msgstr ""
+"Herinnering voor Lead: [[object.id ]] [[object.partner_id and 'van ' "
+"+object.partner_id.name or '']]"
#. module: crm
#: view:crm.segmentation:0
@@ -608,7 +657,7 @@ msgstr "De code van het verkoopteam moet uniek zijn"
#. module: crm
#: help:crm.lead,email_from:0
msgid "Email address of the contact"
-msgstr ""
+msgstr "E-mailadres van de contactpersoon"
#. module: crm
#: selection:crm.case.stage,state:0
@@ -629,6 +678,13 @@ msgid ""
" \n"
" "
msgstr ""
+"
\n"
+" Klik als u een nieuwe categorie wilt maken.\n"
+"
\n"
+" Maak specifieke telefooncategorieën om beter de gesprekstypen\n"
+" op te volgen in het systeem.\n"
+"
\n"
+" "
#. module: crm
#: help:crm.case.section,reply_to:0
@@ -734,12 +790,12 @@ msgstr "Televisie"
#. module: crm
#: model:ir.actions.act_window,name:crm.action_crm_send_mass_convert
msgid "Convert to opportunities"
-msgstr ""
+msgstr "Omzetten naar opportuniteit"
#. module: crm
#: model:ir.model,name:crm.model_sale_config_settings
msgid "sale.config.settings"
-msgstr ""
+msgstr "sale.config.settings"
#. module: crm
#: view:crm.segmentation:0
@@ -749,7 +805,7 @@ msgstr "Verwerking stoppen"
#. module: crm
#: field:crm.case.section,alias_id:0
msgid "Alias"
-msgstr ""
+msgstr "Alias"
#. module: crm
#: view:crm.phonecall:0
@@ -761,6 +817,8 @@ msgstr "Telefoongesprekken zoeken"
msgid ""
"Leads/Opportunities that are assigned to one of the sale teams I manage"
msgstr ""
+"Leads/Opportuniteiten die zijn toegewezen aan een van de verkoopteams onder "
+"mijn beheer"
#. module: crm
#: field:calendar.attendee,categ_id:0
@@ -781,7 +839,7 @@ msgstr "Van %s : %s"
#. module: crm
#: view:crm.lead2opportunity.partner.mass:0
msgid "Convert to Opportunities"
-msgstr ""
+msgstr "Omzetten naar opportuniteit"
#. module: crm
#: view:crm.lead2opportunity.partner:0
@@ -790,7 +848,7 @@ msgstr ""
#: view:crm.opportunity2phonecall:0
#: view:crm.phonecall2phonecall:0
msgid "or"
-msgstr ""
+msgstr "of"
#. module: crm
#: field:crm.lead.report,create_date:0
@@ -809,6 +867,8 @@ msgid ""
"Link between stages and sales teams. When set, this limitate the current "
"stage to the selected sales teams."
msgstr ""
+"Koppeling tussen fasen en verkoopteams. Deze maken de huidige fase enkel "
+"toegankelijk voor de gekozen verkoopteams."
#. module: crm
#: view:crm.case.stage:0
@@ -847,7 +907,7 @@ msgstr "Relatiecategorie"
#. module: crm
#: field:crm.lead,probability:0
msgid "Success Rate (%)"
-msgstr ""
+msgstr "Slagingskans (%)"
#. module: crm
#: field:crm.segmentation,sales_purchase_active:0
@@ -889,7 +949,7 @@ msgstr "Maart"
#. module: crm
#: view:crm.lead:0
msgid "Send Email"
-msgstr ""
+msgstr "E-mail verzenden"
#. module: crm
#: code:addons/crm/wizard/crm_lead_to_opportunity.py:89
@@ -919,6 +979,8 @@ msgid ""
"Opportunities that are assigned to either me or one of the sale teams I "
"manage"
msgstr ""
+"Opportuniteiten die zijn toegewezen aan een van de verkoopteams onder mijn "
+"beheer of aan mij"
#. module: crm
#: help:crm.case.section,resource_calendar_id:0
@@ -984,6 +1046,9 @@ msgid ""
"Allows you to track your customers/suppliers claims and grievances.\n"
" This installs the module crm_claim."
msgstr ""
+"Hiermee kunt u klachten en problemen van klanten/ aan leveranciers "
+"opvolgen.\n"
+" Hiermee installeert u de module crm_claim."
#. module: crm
#: model:crm.case.stage,name:crm.stage_lead6
@@ -1058,7 +1123,7 @@ msgstr "Fase"
#. module: crm
#: view:crm.phonecall.report:0
msgid "Phone Calls that are assigned to me"
-msgstr ""
+msgstr "Aan mij toegewezen telefoongesprekken"
#. module: crm
#: field:crm.lead,user_login:0
@@ -1086,11 +1151,13 @@ msgid ""
"Allows you to communicate with Customer, process Customer query, and "
"provide better help and support. This installs the module crm_helpdesk."
msgstr ""
+"Hiermee kunt u met de klant communiceren, vragen beantwoorden en betere "
+"ondersteuning verlenen. Hiermee installeert u de module crm_helpdesk."
#. module: crm
#: view:crm.lead:0
msgid "Delete"
-msgstr ""
+msgstr "Verwijderen"
#. module: crm
#: model:mail.message.subtype,description:crm.mt_lead_create
@@ -1100,7 +1167,7 @@ msgstr ""
#. module: crm
#: view:crm.lead:0
msgid "í"
-msgstr ""
+msgstr "í"
#. module: crm
#: selection:crm.lead.report,creation_month:0
@@ -1141,12 +1208,12 @@ msgstr ""
#. module: crm
#: view:crm.lead:0
msgid "oe_kanban_text_red"
-msgstr ""
+msgstr "oe_kanban_text_red"
#. module: crm
#: model:ir.ui.menu,name:crm.menu_crm_payment_mode_act
msgid "Payment Modes"
-msgstr ""
+msgstr "Betalingswijzen"
#. module: crm
#: field:crm.lead.report,opening_date:0
@@ -1193,6 +1260,9 @@ msgid ""
"This field is used to distinguish stages related to Leads from stages "
"related to Opportunities, or to specify stages available for both types."
msgstr ""
+"Dit veld dient om een onderscheid te maken tussen de fasen voor leads en die "
+"voor opportuniteiten, of om fasen in te stellen die voor beide typen van "
+"toepassing zijn."
#. module: crm
#: model:mail.message.subtype,name:crm.mt_lead_create
@@ -1234,7 +1304,7 @@ msgstr "onbekend"
#: field:crm.lead,message_is_follower:0
#: field:crm.phonecall,message_is_follower:0
msgid "Is a Follower"
-msgstr ""
+msgstr "Is een volger"
#. module: crm
#: field:crm.opportunity2phonecall,date:0
@@ -1247,7 +1317,7 @@ msgstr "Datum"
#. module: crm
#: model:crm.case.section,name:crm.crm_case_section_4
msgid "Online Support"
-msgstr ""
+msgstr "Onlineondersteuning"
#. module: crm
#: view:crm.lead.report:0
@@ -1273,6 +1343,10 @@ msgid ""
"set to 'Done'. If the case needs to be reviewed then the Status is set to "
"'Pending'."
msgstr ""
+"De status wordt op 'Voorlopig' gezet als de zaak wordt gemaakt. Als de zaak "
+"lopende is, wordt de status op 'Open' gezet. Als de zaak is behandeld, wordt "
+"de status op 'Gereed' gezet. Als de zaak moet worden bekeken, wordt de "
+"status op 'Wachtend' gezet."
#. module: crm
#: model:crm.case.section,name:crm.crm_case_section_1
@@ -1306,7 +1380,7 @@ msgstr "Segmenteringsomschrijving"
#. module: crm
#: view:crm.lead:0
msgid "Lead Description"
-msgstr ""
+msgstr "Leadomschrijving"
#. module: crm
#: code:addons/crm/crm_lead.py:565
@@ -1317,7 +1391,7 @@ msgstr "Samengevoegde opportuniteiten"
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor7
msgid "Consulting"
-msgstr ""
+msgstr "Consulting"
#. module: crm
#: field:crm.case.section,code:0
@@ -1327,7 +1401,7 @@ msgstr "Code"
#. module: crm
#: view:sale.config.settings:0
msgid "Features"
-msgstr ""
+msgstr "Opties"
#. module: crm
#: field:crm.case.section,child_ids:0
@@ -1342,7 +1416,7 @@ msgstr "Telefoongesprekken in status Uit te voeren en Open."
#. module: crm
#: field:crm.lead2opportunity.partner.mass,user_ids:0
msgid "Salesmen"
-msgstr ""
+msgstr "Verkopers"
#. module: crm
#: view:crm.lead:0
@@ -1362,12 +1436,12 @@ msgstr "Annuleren"
#. module: crm
#: view:crm.lead:0
msgid "Opportunities Assigned to Me or My Team(s)"
-msgstr ""
+msgstr "Opportuniteiten voor mij of mijn team(s)"
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor4
msgid "Information"
-msgstr ""
+msgstr "Informatie"
#. module: crm
#: view:crm.lead.report:0
@@ -1389,7 +1463,7 @@ msgstr ""
#: field:crm.lead2opportunity.partner.mass,action:0
#: field:crm.partner.binding,action:0
msgid "Related Customer"
-msgstr ""
+msgstr "Gekoppelde klant"
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor8
@@ -1406,12 +1480,12 @@ msgstr "Lead/opportuniteit"
#: model:ir.actions.act_window,name:crm.action_merge_opportunities
#: model:ir.actions.act_window,name:crm.merge_opportunity_act
msgid "Merge leads/opportunities"
-msgstr ""
+msgstr "Leads/opportuniteiten samenvoegen"
#. module: crm
#: help:crm.case.stage,sequence:0
msgid "Used to order stages. Lower is better."
-msgstr ""
+msgstr "WOrdt gebruikt om fasen te rangschikken. Lager is beter."
#. module: crm
#: model:ir.actions.act_window,name:crm.crm_phonecall_categ_action
@@ -1426,7 +1500,7 @@ msgstr "Leads/opportuniteiten in status Open."
#. module: crm
#: model:ir.model,name:crm.model_res_users
msgid "Users"
-msgstr ""
+msgstr "Gebruikers"
#. module: crm
#: model:mail.message.subtype,name:crm.mt_lead_stage
@@ -1484,7 +1558,7 @@ msgstr "Naam"
#. module: crm
#: view:crm.lead.report:0
msgid "Leads/Opportunities that are assigned to me"
-msgstr ""
+msgstr "Leads/opportuniteiten die aan mij zijn toegekend"
#. module: crm
#: field:crm.lead.report,date_closed:0
@@ -1502,12 +1576,12 @@ msgstr "Mijn zaken"
#: help:crm.lead,message_ids:0
#: help:crm.phonecall,message_ids:0
msgid "Messages and communication history"
-msgstr ""
+msgstr "Berichten en communicatiehistoriek"
#. module: crm
#: view:crm.lead:0
msgid "Show Countries"
-msgstr ""
+msgstr "Landen tonen"
#. module: crm
#: view:crm.lead:0
@@ -1531,7 +1605,7 @@ msgstr "Prospect omzetten naar relatie"
#. module: crm
#: model:ir.model,name:crm.model_crm_payment_mode
msgid "CRM Payment Mode"
-msgstr ""
+msgstr "CRM betalingswijze"
#. module: crm
#: view:crm.lead.report:0
@@ -1554,7 +1628,7 @@ msgstr "Groeperen op..."
#. module: crm
#: view:crm.merge.opportunity:0
msgid "Merge Leads/Opportunities"
-msgstr ""
+msgstr "Leads/opportuniteiten samenvoegen"
#. module: crm
#: field:crm.case.section,parent_id:0
@@ -1566,7 +1640,7 @@ msgstr "Hoofdteam"
#: selection:crm.lead2opportunity.partner.mass,action:0
#: selection:crm.partner.binding,action:0
msgid "Do not link to a customer"
-msgstr ""
+msgstr "Geen relatie koppelen"
#. module: crm
#: field:crm.lead,date_action:0
@@ -1580,11 +1654,14 @@ msgid ""
"stage. For example, if a stage is related to the status 'Close', when your "
"document reaches this stage, it is automatically closed."
msgstr ""
+"De status van uw document wordt automatisch gewijzigd in de functie van de "
+"gekozen fase. Als een fase gekoppeld is aan de status 'Gesloten', dan wordt "
+"het document automatisch gesloten als het in deze status komt."
#. module: crm
#: view:crm.lead2opportunity.partner.mass:0
msgid "Assign opportunities to"
-msgstr ""
+msgstr "Opportuniteiten toewijzen aan"
#. module: crm
#: model:crm.case.categ,name:crm.categ_phone1
@@ -1600,12 +1677,12 @@ msgstr "Maand van gesprek"
#: code:addons/crm/crm_phonecall.py:290
#, python-format
msgid "Partner has been created."
-msgstr ""
+msgstr "Relatie is gemaakt."
#. module: crm
#: field:sale.config.settings,module_crm_claim:0
msgid "Manage Customer Claims"
-msgstr ""
+msgstr "Klachten van klanten opvolgen"
#. module: crm
#: model:ir.actions.act_window,help:crm.action_report_crm_lead
@@ -1618,7 +1695,7 @@ msgstr ""
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor3
msgid "Services"
-msgstr ""
+msgstr "Diensten"
#. module: crm
#: selection:crm.lead,priority:0
@@ -1657,7 +1734,7 @@ msgstr "Antwoord aan"
#. module: crm
#: view:crm.lead:0
msgid "Display"
-msgstr ""
+msgstr "Weergeven"
#. module: crm
#: view:board.board:0
@@ -1693,12 +1770,12 @@ msgstr "Extra informatie"
#. module: crm
#: view:crm.lead:0
msgid "Fund Raising"
-msgstr ""
+msgstr "Fondsenwerving"
#. module: crm
#: view:crm.lead:0
msgid "Edit..."
-msgstr ""
+msgstr "Bewerken…"
#. module: crm
#: model:crm.case.resource.type,name:crm.type_lead5
@@ -1730,13 +1807,15 @@ msgstr "Lead naar opportuniteit / relatie"
#: help:crm.lead,partner_id:0
msgid "Linked partner (optional). Usually created when converting the lead."
msgstr ""
+"Gekoppelde relatie (optioneel). Doorgaans gemaakt bij het omzetten van de "
+"lead."
#. module: crm
#: field:crm.lead,payment_mode:0
#: view:crm.payment.mode:0
#: model:ir.actions.act_window,name:crm.action_crm_payment_mode
msgid "Payment Mode"
-msgstr ""
+msgstr "Betalingswijze"
#. module: crm
#: model:ir.model,name:crm.model_crm_lead2opportunity_partner_mass
@@ -1746,19 +1825,19 @@ msgstr "Vele leads naar opportuniteit / relatie"
#. module: crm
#: view:sale.config.settings:0
msgid "On Mail Server"
-msgstr ""
+msgstr "Op mailserver"
#. module: crm
#: model:ir.actions.act_window,name:crm.open_board_statistical_dash
#: model:ir.ui.menu,name:crm.menu_board_statistics_dash
msgid "CRM"
-msgstr ""
+msgstr "CRM"
#. module: crm
#: model:ir.actions.act_window,name:crm.crm_segmentation_tree-act
#: model:ir.ui.menu,name:crm.menu_crm_segmentation-act
msgid "Contacts Segmentation"
-msgstr ""
+msgstr "Contactpersoonsegmentering"
#. module: crm
#: model:process.node,note:crm.process_node_meeting0
@@ -1773,7 +1852,7 @@ msgstr "Televerkoop"
#. module: crm
#: view:crm.lead:0
msgid "Leads Assigned to Me or My Team(s)"
-msgstr ""
+msgstr "Leads voor mij of mijn team(s)"
#. module: crm
#: model:ir.model,name:crm.model_crm_segmentation_line
@@ -1817,7 +1896,7 @@ msgstr "Lead / klant"
#. module: crm
#: model:crm.case.section,name:crm.crm_case_section_2
msgid "Support Department"
-msgstr ""
+msgstr "Supportafdeling"
#. module: crm
#: view:crm.lead.report:0
@@ -1881,13 +1960,13 @@ msgstr ""
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor5
msgid "Design"
-msgstr ""
+msgstr "Ontwerp"
#. module: crm
#: selection:crm.lead2opportunity.partner,name:0
#: selection:crm.lead2opportunity.partner.mass,name:0
msgid "Merge with existing opportunities"
-msgstr ""
+msgstr "Samenvoegen met bestaande opportuniteiten"
#. module: crm
#: view:crm.phonecall.report:0
@@ -1912,6 +1991,8 @@ msgid ""
"The name of the future partner company that will be created while converting "
"the lead into opportunity"
msgstr ""
+"De naam van de toekomstige relatie die zal worden gemaakt als van de lead "
+"een opportuniteit wordt gemaakt."
#. module: crm
#: field:crm.opportunity2phonecall,note:0
@@ -1945,7 +2026,7 @@ msgstr "Openstaande opportuniteiten"
#. module: crm
#: model:crm.case.resource.type,name:crm.type_lead2
msgid "Email Campaign - Services"
-msgstr ""
+msgstr "E-mailcampagne - Diensten"
#. module: crm
#: selection:crm.case.stage,state:0
@@ -2018,7 +2099,7 @@ msgstr ""
#: selection:crm.lead2opportunity.partner.mass,action:0
#: selection:crm.partner.binding,action:0
msgid "Create a new customer"
-msgstr ""
+msgstr "Een nieuwe klant maken"
#. module: crm
#: field:crm.lead.report,deadline_day:0
@@ -2028,7 +2109,7 @@ msgstr "Verw. sluiting"
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor2
msgid "Software"
-msgstr ""
+msgstr "Software"
#. module: crm
#: field:crm.case.section,change_responsible:0
@@ -2063,12 +2144,12 @@ msgstr "Plaats"
#. module: crm
#: selection:crm.case.stage,type:0
msgid "Both"
-msgstr ""
+msgstr "Beide"
#. module: crm
#: view:crm.phonecall:0
msgid "Call Done"
-msgstr ""
+msgstr "Gesprek uitgevoerd"
#. module: crm
#: view:crm.phonecall:0
@@ -2079,12 +2160,12 @@ msgstr "Verantwoordelijke"
#. module: crm
#: model:crm.case.section,name:crm.crm_case_section_3
msgid "Direct Marketing"
-msgstr ""
+msgstr "Direct Marketing"
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor1
msgid "Product"
-msgstr ""
+msgstr "Product"
#. module: crm
#: field:crm.lead.report,creation_year:0
@@ -2094,7 +2175,7 @@ msgstr "Creatiejaar"
#. module: crm
#: view:crm.lead2opportunity.partner.mass:0
msgid "Conversion Options"
-msgstr ""
+msgstr "Conversieopties"
#. module: crm
#: view:crm.case.section:0
@@ -2106,7 +2187,7 @@ msgstr ""
#. module: crm
#: view:crm.lead:0
msgid "Address"
-msgstr ""
+msgstr "Adres"
#. module: crm
#: help:crm.case.section,alias_id:0
@@ -2114,6 +2195,8 @@ msgid ""
"The email address associated with this team. New emails received will "
"automatically create new leads assigned to the team."
msgstr ""
+"Het e-mailadres van het team. Nieuwe e-mails worden automatisch als lead "
+"gemaakt en toegekend aan dit team."
#. module: crm
#: view:crm.lead:0
@@ -2172,7 +2255,7 @@ msgstr "Verder verwerken"
#: selection:crm.lead2opportunity.partner.mass,name:0
#: model:ir.actions.act_window,name:crm.action_crm_lead2opportunity_partner
msgid "Convert to opportunity"
-msgstr ""
+msgstr "Omzetten naar opportuniteit"
#. module: crm
#: field:crm.opportunity2phonecall,user_id:0
@@ -2214,6 +2297,8 @@ msgid ""
"This stage is not visible, for example in status bar or kanban view, when "
"there are no records in that stage to display."
msgstr ""
+"Deze fase is niet zichtbaar, vb. in statusbalk of kanbanweergave, als er "
+"zich geen records in deze fase bevinden."
#. module: crm
#: field:crm.lead.report,nbr:0
@@ -2229,12 +2314,12 @@ msgstr "Verkoopteam aan wie de zaak toebehoort"
#. module: crm
#: model:crm.case.resource.type,name:crm.type_lead6
msgid "Banner Ads"
-msgstr ""
+msgstr "Kopadvertenties"
#. module: crm
#: field:crm.merge.opportunity,opportunity_ids:0
msgid "Leads/Opportunities"
-msgstr ""
+msgstr "Leads/opportuniteiten"
#. module: crm
#: field:crm.lead,fax:0
@@ -2279,17 +2364,17 @@ msgstr "Objectnaam"
#. module: crm
#: view:crm.phonecall:0
msgid "Phone Calls Assigned to Me or My Team(s)"
-msgstr ""
+msgstr "Telefoongesprekken voor mij of mijn team(s)"
#. module: crm
#: view:crm.lead:0
msgid "Reset"
-msgstr ""
+msgstr "Herstellen"
#. module: crm
#: view:sale.config.settings:0
msgid "After-Sale Services"
-msgstr ""
+msgstr "Klantenservice"
#. module: crm
#: field:crm.case.section,message_ids:0
@@ -2344,7 +2429,7 @@ msgstr ""
#. module: crm
#: field:crm.case.stage,state:0
msgid "Related Status"
-msgstr ""
+msgstr "Gekoppelde status"
#. module: crm
#: field:crm.phonecall,name:0
@@ -2365,7 +2450,7 @@ msgstr "Een gesprek plannen/noteren"
#. module: crm
#: view:crm.merge.opportunity:0
msgid "Select Leads/Opportunities"
-msgstr ""
+msgstr "Leads/opportuniteiten kiezen"
#. module: crm
#: selection:crm.phonecall,state:0
@@ -2390,7 +2475,7 @@ msgstr "Bevestigen"
#. module: crm
#: view:crm.lead:0
msgid "Unread messages"
-msgstr ""
+msgstr "Ongelezen berichten"
#. module: crm
#: field:crm.phonecall.report,section_id:0
@@ -2407,7 +2492,7 @@ msgstr "Optionele expressie"
#: field:crm.lead,message_follower_ids:0
#: field:crm.phonecall,message_follower_ids:0
msgid "Followers"
-msgstr ""
+msgstr "Volgers"
#. module: crm
#: model:ir.actions.act_window,help:crm.crm_case_category_act_leads_all
@@ -2430,7 +2515,7 @@ msgstr ""
#. module: crm
#: field:sale.config.settings,fetchmail_lead:0
msgid "Create leads from incoming mails"
-msgstr ""
+msgstr "Leads maken van binnenkomende mails"
#. module: crm
#: view:crm.lead:0
@@ -2481,7 +2566,7 @@ msgstr "Opportuniteiten maken van leads"
#. module: crm
#: model:crm.case.resource.type,name:crm.type_lead3
msgid "Email Campaign - Products"
-msgstr ""
+msgstr "E-mailcampagne - Producten"
#. module: crm
#: model:ir.actions.act_window,help:crm.crm_case_categ_phone_incoming0
@@ -2509,7 +2594,7 @@ msgstr "Allereerste contact met een nieuwe prospect"
#. module: crm
#: view:res.partner:0
msgid "Calls"
-msgstr ""
+msgstr "Gesprekken"
#. module: crm
#: field:crm.case.stage,on_change:0
@@ -2519,7 +2604,7 @@ msgstr "Kans automatisch wijzigen"
#. module: crm
#: view:crm.phonecall.report:0
msgid "My Phone Calls"
-msgstr ""
+msgstr "Mijn telefoongesprekken"
#. module: crm
#: model:crm.case.stage,name:crm.stage_lead3
@@ -2530,7 +2615,7 @@ msgstr "Kwalificatie"
#: field:crm.lead2opportunity.partner,name:0
#: field:crm.lead2opportunity.partner.mass,name:0
msgid "Conversion Action"
-msgstr ""
+msgstr "Conversieactie"
#. module: crm
#: model:ir.actions.act_window,help:crm.crm_lead_categ_action
@@ -2606,7 +2691,7 @@ msgstr "Verw. sluitingsjaar"
#. module: crm
#: model:ir.actions.client,name:crm.action_client_crm_menu
msgid "Open Sale Menu"
-msgstr ""
+msgstr "Het verkoopmenu openen"
#. module: crm
#: field:crm.lead,date_open:0
@@ -2629,12 +2714,12 @@ msgstr "Een gesprek plannen/noteren"
#. module: crm
#: field:crm.lead,planned_cost:0
msgid "Planned Costs"
-msgstr ""
+msgstr "Geplande kosten"
#. module: crm
#: help:crm.lead,date_deadline:0
msgid "Estimate of the date on which the opportunity will be won."
-msgstr ""
+msgstr "Verwachte datum waarop de opportuniteit kan worden gerealiseerd."
#. module: crm
#: help:crm.lead,email_cc:0
@@ -2698,7 +2783,7 @@ msgstr "Straat 2"
#. module: crm
#: field:sale.config.settings,module_crm_helpdesk:0
msgid "Manage Helpdesk and Support"
-msgstr ""
+msgstr "Helpdesk en ondersteuning"
#. module: crm
#: field:crm.lead.report,delay_open:0
@@ -2785,7 +2870,7 @@ msgstr "Gesprek noteren"
#. module: crm
#: help:sale.config.settings,group_fund_raising:0
msgid "Allows you to trace and manage your activities for fund raising."
-msgstr ""
+msgstr "Hiermee kunt u activiteiten voor fondsenwerving beheren"
#. module: crm
#: field:crm.meeting,phonecall_id:0
@@ -2797,6 +2882,8 @@ msgstr "Telefoongesprek"
#: view:crm.phonecall.report:0
msgid "Phone calls that are assigned to one of the sale teams I manage"
msgstr ""
+"Telefoongesprekken die zijn toegewezen aan een van de verkoopteams onder "
+"mijn beheer"
#. module: crm
#: view:crm.lead:0
@@ -2806,7 +2893,7 @@ msgstr "Creatiedatum"
#. module: crm
#: view:crm.lead:0
msgid "at"
-msgstr ""
+msgstr "bij"
#. module: crm
#: model:crm.case.stage,name:crm.stage_lead1
@@ -2875,7 +2962,7 @@ msgstr ""
#. module: crm
#: view:crm.lead:0
msgid "Internal Notes"
-msgstr ""
+msgstr "Interne notities"
#. module: crm
#: view:crm.lead:0
@@ -2895,7 +2982,7 @@ msgstr "Straat"
#. module: crm
#: field:crm.lead,referred:0
msgid "Referred By"
-msgstr ""
+msgstr "Doorverwezen via"
#. module: crm
#: view:crm.phonecall:0
@@ -2940,6 +3027,7 @@ msgstr "Verloren"
#, python-format
msgid "Closed/Cancelled leads cannot be converted into opportunities."
msgstr ""
+"Gesloten/geannuleerde leads kunnen niet in een opportuniteit worden omgezet"
#. module: crm
#: view:crm.lead:0
@@ -4199,3 +4287,6 @@ msgstr ""
#~ msgid "Recurrent"
#~ msgstr "Recurrent"
+
+#~ msgid "Conditions on Case Fields"
+#~ msgstr "Voorwaardevelden"
diff --git a/addons/crm/report/crm_lead_report_view.xml b/addons/crm/report/crm_lead_report_view.xml
index 1009e1c376d..759063b3c1d 100644
--- a/addons/crm/report/crm_lead_report_view.xml
+++ b/addons/crm/report/crm_lead_report_view.xml
@@ -82,7 +82,7 @@
groups="base.group_multi_salesteams"/>
-
+
diff --git a/addons/crm/report/crm_phonecall_report_view.xml b/addons/crm/report/crm_phonecall_report_view.xml
index c27d876ddd7..070d574e0cd 100644
--- a/addons/crm/report/crm_phonecall_report_view.xml
+++ b/addons/crm/report/crm_phonecall_report_view.xml
@@ -64,7 +64,7 @@
groups="base.group_multi_salesteams"/>
-
+
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 52648c8e347..b09c201df2b 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -35,3 +35,5 @@ access_crm_lead_partner_manager,crm.lead.partner.manager,model_crm_lead,base.gro
access_crm_phonecall_partner_manager,crm.phonecall.partner.manager,model_crm_phonecall,base.group_partner_manager,1,1,1,1
access_crm_payment_mode_user,crm.payment.mode,model_crm_payment_mode,base.group_sale_salesman,1,0,0,0
access_crm_payment_mode,crm.payment.mode,model_crm_payment_mode,base.group_sale_manager,1,1,1,1
+access_base_partner_merge_line_manager,base_partner_merge_line.manager,model_base_partner_merge_line,base.group_system,1,1,1,1
+access_base_partner_merge_manager,base_partner_merge.manager,model_base_partner_merge_automatic_wizard,base.group_system,1,1,1,1
diff --git a/addons/crm/validate_email.py b/addons/crm/validate_email.py
new file mode 100644
index 00000000000..38de5743fca
--- /dev/null
+++ b/addons/crm/validate_email.py
@@ -0,0 +1,123 @@
+# RFC 2822 - style email validation for Python
+# (c) 2012 Syrus Akbary
+# Extended from (c) 2011 Noel Bush
+# for support of mx and user check
+# This code is made available to you under the GNU LGPL v3.
+#
+# This module provides a single method, valid_email_address(),
+# which returns True or False to indicate whether a given address
+# is valid according to the 'addr-spec' part of the specification
+# given in RFC 2822. Ideally, we would like to find this
+# in some other library, already thoroughly tested and well-
+# maintained. The standard Python library email.utils
+# contains a parse_addr() function, but it is not sufficient
+# to detect many malformed addresses.
+#
+# This implementation aims to be faithful to the RFC, with the
+# exception of a circular definition (see comments below), and
+# with the omission of the pattern components marked as "obsolete".
+
+import re
+import smtplib
+import socket
+
+try:
+ import DNS
+ ServerError = DNS.ServerError
+except:
+ DNS = None
+ class ServerError(Exception): pass
+# All we are really doing is comparing the input string to one
+# gigantic regular expression. But building that regexp, and
+# ensuring its correctness, is made much easier by assembling it
+# from the "tokens" defined by the RFC. Each of these tokens is
+# tested in the accompanying unit test file.
+#
+# The section of RFC 2822 from which each pattern component is
+# derived is given in an accompanying comment.
+#
+# (To make things simple, every string below is given as 'raw',
+# even when it's not strictly necessary. This way we don't forget
+# when it is necessary.)
+#
+WSP = r'[ \t]' # see 2.2.2. Structured Header Field Bodies
+CRLF = r'(?:\r\n)' # see 2.2.3. Long Header Fields
+NO_WS_CTL = r'\x01-\x08\x0b\x0c\x0f-\x1f\x7f' # see 3.2.1. Primitive Tokens
+QUOTED_PAIR = r'(?:\\.)' # see 3.2.2. Quoted characters
+FWS = r'(?:(?:' + WSP + r'*' + CRLF + r')?' + \
+ WSP + r'+)' # see 3.2.3. Folding white space and comments
+CTEXT = r'[' + NO_WS_CTL + \
+ r'\x21-\x27\x2a-\x5b\x5d-\x7e]' # see 3.2.3
+CCONTENT = r'(?:' + CTEXT + r'|' + \
+ QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here
+ # as well, but that would be circular.)
+COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + \
+ r')*' + FWS + r'?\)' # see 3.2.3
+CFWS = r'(?:' + FWS + r'?' + COMMENT + ')*(?:' + \
+ FWS + '?' + COMMENT + '|' + FWS + ')' # see 3.2.3
+ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4. Atom
+ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + r'?' # see 3.2.4
+DOT_ATOM_TEXT = ATEXT + r'+(?:\.' + ATEXT + r'+)*' # see 3.2.4
+DOT_ATOM = CFWS + r'?' + DOT_ATOM_TEXT + CFWS + r'?' # see 3.2.4
+QTEXT = r'[' + NO_WS_CTL + \
+ r'\x21\x23-\x5b\x5d-\x7e]' # see 3.2.5. Quoted strings
+QCONTENT = r'(?:' + QTEXT + r'|' + \
+ QUOTED_PAIR + r')' # see 3.2.5
+QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + \
+ r'?' + QCONTENT + r')*' + FWS + \
+ r'?' + r'"' + CFWS + r'?'
+LOCAL_PART = r'(?:' + DOT_ATOM + r'|' + \
+ QUOTED_STRING + r')' # see 3.4.1. Addr-spec specification
+DTEXT = r'[' + NO_WS_CTL + r'\x21-\x5a\x5e-\x7e]' # see 3.4.1
+DCONTENT = r'(?:' + DTEXT + r'|' + \
+ QUOTED_PAIR + r')' # see 3.4.1
+DOMAIN_LITERAL = CFWS + r'?' + r'\[' + \
+ r'(?:' + FWS + r'?' + DCONTENT + \
+ r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1
+DOMAIN = r'(?:' + DOT_ATOM + r'|' + \
+ DOMAIN_LITERAL + r')' # see 3.4.1
+ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN # see 3.4.1
+
+# A valid address will match exactly the 3.4.1 addr-spec.
+VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$'
+
+def validate_email(email, check_mx=False,verify=False):
+
+ """Indicate whether the given string is a valid email address
+ according to the 'addr-spec' portion of RFC 2822 (see section
+ 3.4.1). Parts of the spec that are marked obsolete are *not*
+ included in this test, and certain arcane constructions that
+ depend on circular definitions in the spec may not pass, but in
+ general this should correctly identify any email address likely
+ to be in use as of 2011."""
+ try:
+ assert re.match(VALID_ADDRESS_REGEXP, email) is not None
+ check_mx |= verify
+ if check_mx:
+ if not DNS: raise Exception('For check the mx records or check if the email exists you must have installed pyDNS python package')
+ DNS.DiscoverNameServers()
+ hostname = email[email.find('@')+1:]
+ mx_hosts = DNS.mxlookup(hostname)
+ for mx in mx_hosts:
+ try:
+ smtp = smtplib.SMTP()
+ smtp.connect(mx[1])
+ if not verify: return True
+ status, _ = smtp.helo()
+ if status != 250: continue
+ smtp.mail('')
+ status, _ = smtp.rcpt(email)
+ if status != 250: return False
+ break
+ except smtplib.SMTPServerDisconnected: #Server not permits verify user
+ break
+ except smtplib.SMTPConnectError:
+ continue
+ except (AssertionError, ServerError):
+ return False
+ return True
+
+# import sys
+
+# sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__]
+# from validate_email_module import *
diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml
index 78e467a7ba8..e295bfdd250 100644
--- a/addons/crm_claim/crm_claim_view.xml
+++ b/addons/crm_claim/crm_claim_view.xml
@@ -201,7 +201,7 @@
-
+
diff --git a/addons/crm_claim/report/crm_claim_report_view.xml b/addons/crm_claim/report/crm_claim_report_view.xml
index febf9cafd73..530eb6061a3 100644
--- a/addons/crm_claim/report/crm_claim_report_view.xml
+++ b/addons/crm_claim/report/crm_claim_report_view.xml
@@ -65,7 +65,7 @@
-
+
diff --git a/addons/crm_helpdesk/crm_helpdesk_view.xml b/addons/crm_helpdesk/crm_helpdesk_view.xml
index 6769b42a7f8..6e3521be6f2 100644
--- a/addons/crm_helpdesk/crm_helpdesk_view.xml
+++ b/addons/crm_helpdesk/crm_helpdesk_view.xml
@@ -152,7 +152,7 @@
-
+
diff --git a/addons/crm_helpdesk/report/crm_helpdesk_report_view.xml b/addons/crm_helpdesk/report/crm_helpdesk_report_view.xml
index 795ae210d8d..6ce2c60093f 100644
--- a/addons/crm_helpdesk/report/crm_helpdesk_report_view.xml
+++ b/addons/crm_helpdesk/report/crm_helpdesk_report_view.xml
@@ -62,6 +62,7 @@
+
diff --git a/addons/crm_helpdesk/test/process/help-desk.yml b/addons/crm_helpdesk/test/process/help-desk.yml
index e2be4a64ca3..95536b1c8dd 100644
--- a/addons/crm_helpdesk/test/process/help-desk.yml
+++ b/addons/crm_helpdesk/test/process/help-desk.yml
@@ -4,8 +4,7 @@
Mail script will be fetched him request from mail server. so I process that mail after read EML file
-
!python {model: mail.thread}: |
- from openerp import addons
- request_file = open(addons.get_module_resource('crm_helpdesk','test', 'customer_question.eml'),'rb')
+ request_file = open(openerp.modules.module.get_module_resource('crm_helpdesk','test', 'customer_question.eml'),'rb')
request_message = request_file.read()
self.message_process(cr, uid, 'crm.helpdesk', request_message)
-
diff --git a/addons/edi/i18n/lt.po b/addons/edi/i18n/lt.po
new file mode 100644
index 00000000000..4c00a1dc6d6
--- /dev/null
+++ b/addons/edi/i18n/lt.po
@@ -0,0 +1,87 @@
+# Lithuanian translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2013-04-29 15:19+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Lithuanian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-04-30 05:29+0000\n"
+"X-Generator: Launchpad (build 16580)\n"
+
+#. module: edi
+#. openerp-web
+#: code:addons/edi/static/src/js/edi.js:67
+#, python-format
+msgid "Reason:"
+msgstr ""
+
+#. module: edi
+#. openerp-web
+#: code:addons/edi/static/src/js/edi.js:60
+#, python-format
+msgid "The document has been successfully imported!"
+msgstr ""
+
+#. module: edi
+#. openerp-web
+#: code:addons/edi/static/src/js/edi.js:65
+#, python-format
+msgid "Sorry, the document could not be imported."
+msgstr ""
+
+#. module: edi
+#: model:ir.model,name:edi.model_res_company
+msgid "Companies"
+msgstr ""
+
+#. module: edi
+#: model:ir.model,name:edi.model_res_currency
+msgid "Currency"
+msgstr ""
+
+#. module: edi
+#. openerp-web
+#: code:addons/edi/static/src/js/edi.js:71
+#, python-format
+msgid "Document Import Notification"
+msgstr ""
+
+#. module: edi
+#: code:addons/edi/models/edi.py:130
+#, python-format
+msgid "Missing application."
+msgstr ""
+
+#. module: edi
+#: code:addons/edi/models/edi.py:131
+#, python-format
+msgid ""
+"The document you are trying to import requires the OpenERP `%s` application. "
+"You can install it by connecting as the administrator and opening the "
+"configuration assistant."
+msgstr ""
+
+#. module: edi
+#: code:addons/edi/models/edi.py:47
+#, python-format
+msgid "'%s' is an invalid external ID"
+msgstr ""
+
+#. module: edi
+#: model:ir.model,name:edi.model_res_partner
+msgid "Partner"
+msgstr ""
+
+#. module: edi
+#: model:ir.model,name:edi.model_edi_edi
+msgid "EDI Subsystem"
+msgstr ""
diff --git a/addons/email_template/i18n/lt.po b/addons/email_template/i18n/lt.po
new file mode 100644
index 00000000000..20c2c5e0e5d
--- /dev/null
+++ b/addons/email_template/i18n/lt.po
@@ -0,0 +1,488 @@
+# Lithuanian translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2013-04-29 15:19+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Lithuanian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-04-30 05:29+0000\n"
+"X-Generator: Launchpad (build 16580)\n"
+
+#. module: email_template
+#: field:email.template,email_from:0
+#: field:email_template.preview,email_from:0
+msgid "From"
+msgstr ""
+
+#. module: email_template
+#: field:mail.compose.message,template_id:0
+msgid "Template"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,ref_ir_value:0
+#: help:email_template.preview,ref_ir_value:0
+msgid "Sidebar button to open the sidebar action"
+msgstr ""
+
+#. module: email_template
+#: field:res.partner,opt_out:0
+msgid "Opt-Out"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,email_to:0
+#: field:email_template.preview,email_to:0
+msgid "To (Emails)"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,mail_server_id:0
+#: field:email_template.preview,mail_server_id:0
+msgid "Outgoing Mail Server"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,ref_ir_act_window:0
+#: help:email_template.preview,ref_ir_act_window:0
+msgid ""
+"Sidebar action to make this template available on records of the related "
+"document model"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,model_object_field:0
+#: field:email_template.preview,model_object_field:0
+msgid "Field"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,email_from:0
+#: help:email_template.preview,email_from:0
+msgid "Sender address (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Remove context action"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,mail_server_id:0
+#: help:email_template.preview,mail_server_id:0
+msgid ""
+"Optional preferred server for outgoing mails. If not set, the highest "
+"priority one will be used."
+msgstr ""
+
+#. module: email_template
+#: field:email.template,report_name:0
+#: field:email_template.preview,report_name:0
+msgid "Report Filename"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Preview"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,reply_to:0
+#: field:email_template.preview,reply_to:0
+msgid "Reply-To"
+msgstr ""
+
+#. module: email_template
+#: view:mail.compose.message:0
+msgid "Use template"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,body_html:0
+#: field:email_template.preview,body_html:0
+msgid "Body"
+msgstr ""
+
+#. module: email_template
+#: code:addons/email_template/email_template.py:244
+#, python-format
+msgid "%s (copy)"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,user_signature:0
+#: help:email_template.preview,user_signature:0
+msgid ""
+"If checked, the user's signature will be appended to the text version of the "
+"message"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "SMTP Server"
+msgstr ""
+
+#. module: email_template
+#: view:mail.compose.message:0
+msgid "Save as new template"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,sub_object:0
+#: help:email_template.preview,sub_object:0
+msgid ""
+"When a relationship field is selected as first field, this field shows the "
+"document model the relationship goes to."
+msgstr ""
+
+#. module: email_template
+#: model:ir.model,name:email_template.model_email_template
+msgid "Email Templates"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,report_name:0
+#: help:email_template.preview,report_name:0
+msgid ""
+"Name to use for the generated report file (may contain placeholders)\n"
+"The extension can be omitted and will then come from the report type."
+msgstr ""
+
+#. module: email_template
+#: field:email.template,ref_ir_act_window:0
+#: field:email_template.preview,ref_ir_act_window:0
+msgid "Sidebar action"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,lang:0
+#: help:email_template.preview,lang:0
+msgid ""
+"Optional translation language (ISO code) to select when sending out an "
+"email. If not set, the english version will be used. This should usually be "
+"a placeholder expression that provides the appropriate language code, e.g. "
+"${object.partner_id.lang.code}."
+msgstr ""
+
+#. module: email_template
+#: field:email_template.preview,res_id:0
+msgid "Sample Document"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,model_object_field:0
+#: help:email_template.preview,model_object_field:0
+msgid ""
+"Select target field from the related document model.\n"
+"If it is a relationship field you will be able to select a target field at "
+"the destination of the relationship."
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Dynamic Value Builder"
+msgstr ""
+
+#. module: email_template
+#: model:ir.actions.act_window,name:email_template.wizard_email_template_preview
+msgid "Template Preview"
+msgstr ""
+
+#. module: email_template
+#: view:mail.compose.message:0
+msgid "Save as a new template"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid ""
+"Display an option on related documents to open a composition wizard with "
+"this template"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,email_cc:0
+#: help:email_template.preview,email_cc:0
+msgid "Carbon copy recipients (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,email_to:0
+#: help:email_template.preview,email_to:0
+msgid "Comma-separated recipient addresses (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Advanced"
+msgstr ""
+
+#. module: email_template
+#: view:email_template.preview:0
+msgid "Preview of"
+msgstr ""
+
+#. module: email_template
+#: view:email_template.preview:0
+msgid "Using sample document"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+#: model:ir.actions.act_window,name:email_template.action_email_template_tree_all
+#: model:ir.ui.menu,name:email_template.menu_email_templates
+msgid "Templates"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,name:0
+#: field:email_template.preview,name:0
+msgid "Name"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,lang:0
+#: field:email_template.preview,lang:0
+msgid "Language"
+msgstr ""
+
+#. module: email_template
+#: model:ir.model,name:email_template.model_email_template_preview
+msgid "Email Template Preview"
+msgstr ""
+
+#. module: email_template
+#: view:email_template.preview:0
+msgid "Email Preview"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid ""
+"Remove the contextual action to use this template on related documents"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,copyvalue:0
+#: field:email_template.preview,copyvalue:0
+msgid "Placeholder Expression"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,sub_object:0
+#: field:email_template.preview,sub_object:0
+msgid "Sub-model"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,subject:0
+#: help:email_template.preview,subject:0
+msgid "Subject (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,reply_to:0
+#: help:email_template.preview,reply_to:0
+msgid "Preferred response address (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,ref_ir_value:0
+#: field:email_template.preview,ref_ir_value:0
+msgid "Sidebar Button"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,report_template:0
+#: field:email_template.preview,report_template:0
+msgid "Optional report to print and attach"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,null_value:0
+#: help:email_template.preview,null_value:0
+msgid "Optional value to use if the target field is empty"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Model"
+msgstr ""
+
+#. module: email_template
+#: model:ir.model,name:email_template.model_mail_compose_message
+msgid "Email composition wizard"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Add context action"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,model_id:0
+#: help:email_template.preview,model_id:0
+msgid "The kind of document with with this template can be used"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,email_recipients:0
+#: field:email_template.preview,email_recipients:0
+msgid "To (Partners)"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,auto_delete:0
+#: field:email_template.preview,auto_delete:0
+msgid "Auto Delete"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,copyvalue:0
+#: help:email_template.preview,copyvalue:0
+msgid ""
+"Final placeholder expression, to be copy-pasted in the desired template "
+"field."
+msgstr ""
+
+#. module: email_template
+#: field:email.template,model:0
+#: field:email_template.preview,model:0
+msgid "Related Document Model"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Addressing"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,email_recipients:0
+#: help:email_template.preview,email_recipients:0
+msgid ""
+"Comma-separated ids of recipient partners (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,attachment_ids:0
+#: field:email_template.preview,attachment_ids:0
+msgid "Attachments"
+msgstr ""
+
+#. module: email_template
+#: code:addons/email_template/email_template.py:231
+#, python-format
+msgid "Deletion of the action record failed."
+msgstr ""
+
+#. module: email_template
+#: field:email.template,email_cc:0
+#: field:email_template.preview,email_cc:0
+msgid "Cc"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,model_id:0
+#: field:email_template.preview,model_id:0
+msgid "Applies to"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,sub_model_object_field:0
+#: field:email_template.preview,sub_model_object_field:0
+msgid "Sub-field"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Email Details"
+msgstr ""
+
+#. module: email_template
+#: code:addons/email_template/email_template.py:196
+#, python-format
+msgid "Send Mail (%s)"
+msgstr ""
+
+#. module: email_template
+#: help:res.partner,opt_out:0
+msgid ""
+"If checked, this partner will not receive any automated email notifications, "
+"such as the availability of invoices."
+msgstr ""
+
+#. module: email_template
+#: help:email.template,auto_delete:0
+#: help:email_template.preview,auto_delete:0
+msgid "Permanently delete this email after sending it, to save space"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Group by..."
+msgstr ""
+
+#. module: email_template
+#: help:email.template,sub_model_object_field:0
+#: help:email_template.preview,sub_model_object_field:0
+msgid ""
+"When a relationship field is selected as first field, this field lets you "
+"select the target field within the destination document model (sub-model)."
+msgstr ""
+
+#. module: email_template
+#: code:addons/email_template/email_template.py:231
+#, python-format
+msgid "Warning"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,user_signature:0
+#: field:email_template.preview,user_signature:0
+msgid "Add Signature"
+msgstr ""
+
+#. module: email_template
+#: model:ir.model,name:email_template.model_res_partner
+msgid "Partner"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,null_value:0
+#: field:email_template.preview,null_value:0
+msgid "Default Value"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,attachment_ids:0
+#: help:email_template.preview,attachment_ids:0
+msgid ""
+"You may attach files to this template, to be added to all emails created "
+"from this template"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,body_html:0
+#: help:email_template.preview,body_html:0
+msgid "Rich-text/HTML version of the message (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Contents"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,subject:0
+#: field:email_template.preview,subject:0
+msgid "Subject"
+msgstr ""
diff --git a/addons/email_template/i18n/nl_BE.po b/addons/email_template/i18n/nl_BE.po
new file mode 100644
index 00000000000..5e7ddeec587
--- /dev/null
+++ b/addons/email_template/i18n/nl_BE.po
@@ -0,0 +1,489 @@
+# Dutch (Belgium) translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2013-04-26 16:28+0000\n"
+"Last-Translator: Els Van Vossel (Agaplan) \n"
+"Language-Team: Dutch (Belgium) \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-04-27 05:44+0000\n"
+"X-Generator: Launchpad (build 16580)\n"
+
+#. module: email_template
+#: field:email.template,email_from:0
+#: field:email_template.preview,email_from:0
+msgid "From"
+msgstr "Van"
+
+#. module: email_template
+#: field:mail.compose.message,template_id:0
+msgid "Template"
+msgstr "Sjabloon"
+
+#. module: email_template
+#: help:email.template,ref_ir_value:0
+#: help:email_template.preview,ref_ir_value:0
+msgid "Sidebar button to open the sidebar action"
+msgstr ""
+
+#. module: email_template
+#: field:res.partner,opt_out:0
+msgid "Opt-Out"
+msgstr "Uitschrijven"
+
+#. module: email_template
+#: field:email.template,email_to:0
+#: field:email_template.preview,email_to:0
+msgid "To (Emails)"
+msgstr "Naar (E-mails)"
+
+#. module: email_template
+#: field:email.template,mail_server_id:0
+#: field:email_template.preview,mail_server_id:0
+msgid "Outgoing Mail Server"
+msgstr "Uitgaande mailserver"
+
+#. module: email_template
+#: help:email.template,ref_ir_act_window:0
+#: help:email_template.preview,ref_ir_act_window:0
+msgid ""
+"Sidebar action to make this template available on records of the related "
+"document model"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,model_object_field:0
+#: field:email_template.preview,model_object_field:0
+msgid "Field"
+msgstr "Veld"
+
+#. module: email_template
+#: help:email.template,email_from:0
+#: help:email_template.preview,email_from:0
+msgid "Sender address (placeholders may be used here)"
+msgstr ""
+"Adres van de afzender (variabele aanduidingen kunnen hier worden gebruikt)"
+
+#. module: email_template
+#: view:email.template:0
+msgid "Remove context action"
+msgstr "Contextactie verwijderen"
+
+#. module: email_template
+#: help:email.template,mail_server_id:0
+#: help:email_template.preview,mail_server_id:0
+msgid ""
+"Optional preferred server for outgoing mails. If not set, the highest "
+"priority one will be used."
+msgstr ""
+
+#. module: email_template
+#: field:email.template,report_name:0
+#: field:email_template.preview,report_name:0
+msgid "Report Filename"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Preview"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,reply_to:0
+#: field:email_template.preview,reply_to:0
+msgid "Reply-To"
+msgstr ""
+
+#. module: email_template
+#: view:mail.compose.message:0
+msgid "Use template"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,body_html:0
+#: field:email_template.preview,body_html:0
+msgid "Body"
+msgstr ""
+
+#. module: email_template
+#: code:addons/email_template/email_template.py:244
+#, python-format
+msgid "%s (copy)"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,user_signature:0
+#: help:email_template.preview,user_signature:0
+msgid ""
+"If checked, the user's signature will be appended to the text version of the "
+"message"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "SMTP Server"
+msgstr ""
+
+#. module: email_template
+#: view:mail.compose.message:0
+msgid "Save as new template"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,sub_object:0
+#: help:email_template.preview,sub_object:0
+msgid ""
+"When a relationship field is selected as first field, this field shows the "
+"document model the relationship goes to."
+msgstr ""
+
+#. module: email_template
+#: model:ir.model,name:email_template.model_email_template
+msgid "Email Templates"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,report_name:0
+#: help:email_template.preview,report_name:0
+msgid ""
+"Name to use for the generated report file (may contain placeholders)\n"
+"The extension can be omitted and will then come from the report type."
+msgstr ""
+
+#. module: email_template
+#: field:email.template,ref_ir_act_window:0
+#: field:email_template.preview,ref_ir_act_window:0
+msgid "Sidebar action"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,lang:0
+#: help:email_template.preview,lang:0
+msgid ""
+"Optional translation language (ISO code) to select when sending out an "
+"email. If not set, the english version will be used. This should usually be "
+"a placeholder expression that provides the appropriate language code, e.g. "
+"${object.partner_id.lang.code}."
+msgstr ""
+
+#. module: email_template
+#: field:email_template.preview,res_id:0
+msgid "Sample Document"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,model_object_field:0
+#: help:email_template.preview,model_object_field:0
+msgid ""
+"Select target field from the related document model.\n"
+"If it is a relationship field you will be able to select a target field at "
+"the destination of the relationship."
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Dynamic Value Builder"
+msgstr ""
+
+#. module: email_template
+#: model:ir.actions.act_window,name:email_template.wizard_email_template_preview
+msgid "Template Preview"
+msgstr ""
+
+#. module: email_template
+#: view:mail.compose.message:0
+msgid "Save as a new template"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid ""
+"Display an option on related documents to open a composition wizard with "
+"this template"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,email_cc:0
+#: help:email_template.preview,email_cc:0
+msgid "Carbon copy recipients (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,email_to:0
+#: help:email_template.preview,email_to:0
+msgid "Comma-separated recipient addresses (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Advanced"
+msgstr ""
+
+#. module: email_template
+#: view:email_template.preview:0
+msgid "Preview of"
+msgstr ""
+
+#. module: email_template
+#: view:email_template.preview:0
+msgid "Using sample document"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+#: model:ir.actions.act_window,name:email_template.action_email_template_tree_all
+#: model:ir.ui.menu,name:email_template.menu_email_templates
+msgid "Templates"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,name:0
+#: field:email_template.preview,name:0
+msgid "Name"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,lang:0
+#: field:email_template.preview,lang:0
+msgid "Language"
+msgstr ""
+
+#. module: email_template
+#: model:ir.model,name:email_template.model_email_template_preview
+msgid "Email Template Preview"
+msgstr ""
+
+#. module: email_template
+#: view:email_template.preview:0
+msgid "Email Preview"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid ""
+"Remove the contextual action to use this template on related documents"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,copyvalue:0
+#: field:email_template.preview,copyvalue:0
+msgid "Placeholder Expression"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,sub_object:0
+#: field:email_template.preview,sub_object:0
+msgid "Sub-model"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,subject:0
+#: help:email_template.preview,subject:0
+msgid "Subject (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,reply_to:0
+#: help:email_template.preview,reply_to:0
+msgid "Preferred response address (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,ref_ir_value:0
+#: field:email_template.preview,ref_ir_value:0
+msgid "Sidebar Button"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,report_template:0
+#: field:email_template.preview,report_template:0
+msgid "Optional report to print and attach"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,null_value:0
+#: help:email_template.preview,null_value:0
+msgid "Optional value to use if the target field is empty"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Model"
+msgstr ""
+
+#. module: email_template
+#: model:ir.model,name:email_template.model_mail_compose_message
+msgid "Email composition wizard"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Add context action"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,model_id:0
+#: help:email_template.preview,model_id:0
+msgid "The kind of document with with this template can be used"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,email_recipients:0
+#: field:email_template.preview,email_recipients:0
+msgid "To (Partners)"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,auto_delete:0
+#: field:email_template.preview,auto_delete:0
+msgid "Auto Delete"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,copyvalue:0
+#: help:email_template.preview,copyvalue:0
+msgid ""
+"Final placeholder expression, to be copy-pasted in the desired template "
+"field."
+msgstr ""
+
+#. module: email_template
+#: field:email.template,model:0
+#: field:email_template.preview,model:0
+msgid "Related Document Model"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Addressing"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,email_recipients:0
+#: help:email_template.preview,email_recipients:0
+msgid ""
+"Comma-separated ids of recipient partners (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,attachment_ids:0
+#: field:email_template.preview,attachment_ids:0
+msgid "Attachments"
+msgstr ""
+
+#. module: email_template
+#: code:addons/email_template/email_template.py:231
+#, python-format
+msgid "Deletion of the action record failed."
+msgstr ""
+
+#. module: email_template
+#: field:email.template,email_cc:0
+#: field:email_template.preview,email_cc:0
+msgid "Cc"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,model_id:0
+#: field:email_template.preview,model_id:0
+msgid "Applies to"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,sub_model_object_field:0
+#: field:email_template.preview,sub_model_object_field:0
+msgid "Sub-field"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Email Details"
+msgstr ""
+
+#. module: email_template
+#: code:addons/email_template/email_template.py:196
+#, python-format
+msgid "Send Mail (%s)"
+msgstr ""
+
+#. module: email_template
+#: help:res.partner,opt_out:0
+msgid ""
+"If checked, this partner will not receive any automated email notifications, "
+"such as the availability of invoices."
+msgstr ""
+
+#. module: email_template
+#: help:email.template,auto_delete:0
+#: help:email_template.preview,auto_delete:0
+msgid "Permanently delete this email after sending it, to save space"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Group by..."
+msgstr ""
+
+#. module: email_template
+#: help:email.template,sub_model_object_field:0
+#: help:email_template.preview,sub_model_object_field:0
+msgid ""
+"When a relationship field is selected as first field, this field lets you "
+"select the target field within the destination document model (sub-model)."
+msgstr ""
+
+#. module: email_template
+#: code:addons/email_template/email_template.py:231
+#, python-format
+msgid "Warning"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,user_signature:0
+#: field:email_template.preview,user_signature:0
+msgid "Add Signature"
+msgstr ""
+
+#. module: email_template
+#: model:ir.model,name:email_template.model_res_partner
+msgid "Partner"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,null_value:0
+#: field:email_template.preview,null_value:0
+msgid "Default Value"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,attachment_ids:0
+#: help:email_template.preview,attachment_ids:0
+msgid ""
+"You may attach files to this template, to be added to all emails created "
+"from this template"
+msgstr ""
+
+#. module: email_template
+#: help:email.template,body_html:0
+#: help:email_template.preview,body_html:0
+msgid "Rich-text/HTML version of the message (placeholders may be used here)"
+msgstr ""
+
+#. module: email_template
+#: view:email.template:0
+msgid "Contents"
+msgstr ""
+
+#. module: email_template
+#: field:email.template,subject:0
+#: field:email_template.preview,subject:0
+msgid "Subject"
+msgstr ""
diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py
index c422a2d4948..73daa64f841 100644
--- a/addons/hr_expense/hr_expense.py
+++ b/addons/hr_expense/hr_expense.py
@@ -52,6 +52,9 @@ class hr_expense_expense(osv.osv):
res[expense.id] = total
return res
+ def _get_expense_from_line(self, cr, uid, ids, context=None):
+ return [line.expense_id.id for line in self.pool.get('hr.expense.line').browse(cr, uid, ids, context=context)]
+
def _get_currency(self, cr, uid, context=None):
user = self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0]
if user.company_id:
@@ -84,7 +87,10 @@ class hr_expense_expense(osv.osv):
'account_move_id': fields.many2one('account.move', 'Ledger Posting'),
'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ),
'note': fields.text('Note'),
- 'amount': fields.function(_amount, string='Total Amount', digits_compute=dp.get_precision('Account')),
+ 'amount': fields.function(_amount, string='Total Amount', digits_compute=dp.get_precision('Account'),
+ store={
+ 'hr.expense.line': (_get_expense_from_line, ['unit_amount','unit_quantity'], 10)
+ }),
'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'department_id':fields.many2one('hr.department','Department', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'company_id': fields.many2one('res.company', 'Company', required=True),
diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml
index eab24b06717..10abe95d892 100644
--- a/addons/hr_expense/hr_expense_view.xml
+++ b/addons/hr_expense/hr_expense_view.xml
@@ -32,7 +32,7 @@
-
+
diff --git a/addons/hr_payroll_account/i18n/lt.po b/addons/hr_payroll_account/i18n/lt.po
new file mode 100644
index 00000000000..c29df14b44e
--- /dev/null
+++ b/addons/hr_payroll_account/i18n/lt.po
@@ -0,0 +1,126 @@
+# Lithuanian translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2013-04-29 15:21+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Lithuanian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-04-30 05:29+0000\n"
+"X-Generator: Launchpad (build 16580)\n"
+
+#. module: hr_payroll_account
+#: field:hr.salary.rule,account_credit:0
+msgid "Credit Account"
+msgstr ""
+
+#. module: hr_payroll_account
+#: code:addons/hr_payroll_account/hr_payroll_account.py:103
+#, python-format
+msgid "Payslip of %s"
+msgstr ""
+
+#. module: hr_payroll_account
+#: code:addons/hr_payroll_account/hr_payroll_account.py:156
+#, python-format
+msgid ""
+"The Expense Journal \"%s\" has not properly configured the Credit Account!"
+msgstr ""
+
+#. module: hr_payroll_account
+#: field:hr.payslip,move_id:0
+msgid "Accounting Entry"
+msgstr ""
+
+#. module: hr_payroll_account
+#: code:addons/hr_payroll_account/hr_payroll_account.py:172
+#, python-format
+msgid ""
+"The Expense Journal \"%s\" has not properly configured the Debit Account!"
+msgstr ""
+
+#. module: hr_payroll_account
+#: field:hr.salary.rule,account_tax_id:0
+msgid "Tax Code"
+msgstr ""
+
+#. module: hr_payroll_account
+#: field:hr.payslip,period_id:0
+msgid "Force Period"
+msgstr ""
+
+#. module: hr_payroll_account
+#: help:hr.payslip,period_id:0
+msgid "Keep empty to use the period of the validation(Payslip) date."
+msgstr ""
+
+#. module: hr_payroll_account
+#: model:ir.model,name:hr_payroll_account.model_hr_contract
+msgid "Contract"
+msgstr ""
+
+#. module: hr_payroll_account
+#: field:hr.contract,analytic_account_id:0
+#: field:hr.salary.rule,analytic_account_id:0
+msgid "Analytic Account"
+msgstr ""
+
+#. module: hr_payroll_account
+#: field:hr.salary.rule,account_debit:0
+msgid "Debit Account"
+msgstr ""
+
+#. module: hr_payroll_account
+#: model:ir.model,name:hr_payroll_account.model_hr_payslip_run
+msgid "Payslip Batches"
+msgstr ""
+
+#. module: hr_payroll_account
+#: model:ir.model,name:hr_payroll_account.model_hr_payslip_employees
+msgid "Generate payslips for all selected employees"
+msgstr ""
+
+#. module: hr_payroll_account
+#: code:addons/hr_payroll_account/hr_payroll_account.py:156
+#: code:addons/hr_payroll_account/hr_payroll_account.py:172
+#, python-format
+msgid "Configuration Error!"
+msgstr ""
+
+#. module: hr_payroll_account
+#: model:ir.model,name:hr_payroll_account.model_hr_salary_rule
+msgid "hr.salary.rule"
+msgstr ""
+
+#. module: hr_payroll_account
+#: view:hr.contract:0
+#: view:hr.salary.rule:0
+msgid "Accounting"
+msgstr ""
+
+#. module: hr_payroll_account
+#: model:ir.model,name:hr_payroll_account.model_hr_payslip
+msgid "Pay Slip"
+msgstr ""
+
+#. module: hr_payroll_account
+#: code:addons/hr_payroll_account/hr_payroll_account.py:158
+#: code:addons/hr_payroll_account/hr_payroll_account.py:174
+#, python-format
+msgid "Adjustment Entry"
+msgstr ""
+
+#. module: hr_payroll_account
+#: field:hr.contract,journal_id:0
+#: field:hr.payslip,journal_id:0
+#: field:hr.payslip.run,journal_id:0
+msgid "Salary Journal"
+msgstr ""
diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py
index b0b60db6bd5..e41d80fb348 100644
--- a/addons/hr_recruitment/hr_recruitment.py
+++ b/addons/hr_recruitment/hr_recruitment.py
@@ -510,8 +510,9 @@ class hr_job(osv.osv):
def _auto_init(self, cr, context=None):
"""Installation hook to create aliases for all jobs and avoid constraint errors."""
- self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(hr_job,self)._auto_init,
+ res = self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(hr_job,self)._auto_init,
self._columns['alias_id'], 'name', alias_prefix='job+', alias_defaults={'job_id': 'id'}, context=context)
+ return res
def create(self, cr, uid, vals, context=None):
mail_alias = self.pool.get('mail.alias')
diff --git a/addons/hr_recruitment/test/recruitment_process.yml b/addons/hr_recruitment/test/recruitment_process.yml
index f2d4e4736fa..335edc71892 100644
--- a/addons/hr_recruitment/test/recruitment_process.yml
+++ b/addons/hr_recruitment/test/recruitment_process.yml
@@ -4,8 +4,7 @@
An applicant is interested in the job position. So he sends a resume by email.
-
!python {model: mail.thread}: |
- from openerp import addons
- request_file = open(addons.get_module_resource('hr_recruitment','test', 'resume.eml'),'rb')
+ request_file = open(openerp.modules.module.get_module_resource('hr_recruitment','test', 'resume.eml'),'rb')
request_message = request_file.read()
self.message_process(cr, uid, 'hr.applicant', request_message)
-
diff --git a/addons/im/__init__.py b/addons/im/__init__.py
new file mode 100644
index 00000000000..23c6ad13350
--- /dev/null
+++ b/addons/im/__init__.py
@@ -0,0 +1,2 @@
+
+import im
diff --git a/addons/im/__openerp__.py b/addons/im/__openerp__.py
new file mode 100644
index 00000000000..a76dd7c8cc2
--- /dev/null
+++ b/addons/im/__openerp__.py
@@ -0,0 +1,27 @@
+{
+ 'name' : 'Instant Messaging',
+ 'version': '1.0',
+ 'summary': 'Live Chat, Talks with Others',
+ 'sequence': '18',
+ 'category': 'Tools',
+ 'complexity': 'easy',
+ 'description':
+ """
+Instant Messaging
+=================
+
+Allows users to chat with each other in real time. Find other users easily and
+chat in real time. It support several chats in parallel.
+ """,
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'security/im_security.xml',
+ ],
+ 'depends' : ['base'],
+ 'js': ['static/src/js/*.js'],
+ 'css': ['static/src/css/*.css'],
+ 'qweb': ['static/src/xml/*.xml'],
+ 'installable': True,
+ 'auto_install': False,
+ 'application': True,
+}
diff --git a/addons/im/im.py b/addons/im/im.py
new file mode 100644
index 00000000000..0de9ea8c79b
--- /dev/null
+++ b/addons/im/im.py
@@ -0,0 +1,351 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import openerp
+import openerp.tools.config
+import openerp.modules.registry
+from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
+import datetime
+from openerp.osv import osv, fields
+import time
+import logging
+import json
+import select
+
+_logger = logging.getLogger(__name__)
+
+def listen_channel(cr, channel_name, handle_message, check_stop=(lambda: False), check_stop_timer=60.):
+ """
+ Begin a loop, listening on a PostgreSQL channel. This method does never terminate by default, you need to provide a check_stop
+ callback to do so. This method also assume that all notifications will include a message formated using JSON (see the
+ corresponding notify_channel() method).
+
+ :param db_name: database name
+ :param channel_name: the name of the PostgreSQL channel to listen
+ :param handle_message: function that will be called when a message is received. It takes one argument, the message
+ attached to the notification.
+ :type handle_message: function (one argument)
+ :param check_stop: function that will be called periodically (see the check_stop_timer argument). If it returns True
+ this function will stop to watch the channel.
+ :type check_stop: function (no arguments)
+ :param check_stop_timer: The maximum amount of time between calls to check_stop_timer (can be shorter if messages
+ are received).
+ """
+ try:
+ conn = cr._cnx
+ cr.execute("listen " + channel_name + ";")
+ cr.commit();
+ stopping = False
+ while not stopping:
+ if check_stop():
+ stopping = True
+ break
+ if select.select([conn], [], [], check_stop_timer) == ([],[],[]):
+ pass
+ else:
+ conn.poll()
+ while conn.notifies:
+ message = json.loads(conn.notifies.pop().payload)
+ handle_message(message)
+ finally:
+ try:
+ cr.execute("unlisten " + channel_name + ";")
+ cr.commit()
+ except:
+ pass # can't do anything if that fails
+
+def notify_channel(cr, channel_name, message):
+ """
+ Send a message through a PostgreSQL channel. The message will be formatted using JSON. This method will
+ commit the given transaction because the notify command in Postgresql seems to work correctly when executed in
+ a separate transaction (despite what is written in the documentation).
+
+ :param cr: The cursor.
+ :param channel_name: The name of the PostgreSQL channel.
+ :param message: The message, must be JSON-compatible data.
+ """
+ cr.commit()
+ cr.execute("notify " + channel_name + ", %s", [json.dumps(message)])
+ cr.commit()
+
+POLL_TIMER = 30
+DISCONNECTION_TIMER = POLL_TIMER + 5
+WATCHER_ERROR_DELAY = 10
+
+if openerp.evented:
+ import gevent
+ import gevent.event
+
+ class ImWatcher(object):
+ watchers = {}
+
+ @staticmethod
+ def get_watcher(db_name):
+ if not ImWatcher.watchers.get(db_name):
+ ImWatcher(db_name)
+ return ImWatcher.watchers[db_name]
+
+ def __init__(self, db_name):
+ self.db_name = db_name
+ ImWatcher.watchers[db_name] = self
+ self.waiting = 0
+ self.wait_id = 0
+ self.users = {}
+ self.users_watch = {}
+ gevent.spawn(self.loop)
+
+ def loop(self):
+ _logger.info("Begin watching on channel im_channel for database " + self.db_name)
+ stop = False
+ while not stop:
+ try:
+ registry = openerp.modules.registry.RegistryManager.get(self.db_name)
+ with registry.cursor() as cr:
+ listen_channel(cr, "im_channel", self.handle_message, self.check_stop)
+ stop = True
+ except:
+ # if something crash, we wait some time then try again
+ _logger.exception("Exception during watcher activity")
+ time.sleep(WATCHER_ERROR_DELAY)
+ _logger.info("End watching on channel im_channel for database " + self.db_name)
+ del ImWatcher.watchers[self.db_name]
+
+ def handle_message(self, message):
+ if message["type"] == "message":
+ for waiter in self.users.get(message["receiver"], {}).values():
+ waiter.set()
+ else: #type status
+ for waiter in self.users_watch.get(message["user"], {}).values():
+ waiter.set()
+
+ def check_stop(self):
+ return self.waiting == 0
+
+ def _get_wait_id(self):
+ self.wait_id += 1
+ return self.wait_id
+
+ def stop(self, user_id, watch_users, timeout=None):
+ wait_id = self._get_wait_id()
+ event = gevent.event.Event()
+ self.waiting += 1
+ self.users.setdefault(user_id, {})[wait_id] = event
+ for watch in watch_users:
+ self.users_watch.setdefault(watch, {})[wait_id] = event
+ try:
+ event.wait(timeout)
+ finally:
+ for watch in watch_users:
+ del self.users_watch[watch][wait_id]
+ if len(self.users_watch[watch]) == 0:
+ del self.users_watch[watch]
+ del self.users[user_id][wait_id]
+ if len(self.users[user_id]) == 0:
+ del self.users[user_id]
+ self.waiting -= 1
+
+
+class LongPollingController(openerp.addons.web.http.Controller):
+ _cp_path = '/longpolling/im'
+
+ @openerp.addons.web.http.jsonrequest
+ def poll(self, req, last=None, users_watch=None, db=None, uid=None, password=None, uuid=None):
+ assert_uuid(uuid)
+ if not openerp.evented:
+ raise Exception("Not usable in a server not running gevent")
+ if db is not None:
+ req.session._db = db
+ req.session._uid = uid
+ req.session._password = password
+ req.session.model('im.user').im_connect(uuid=uuid, context=req.context)
+ my_id = req.session.model('im.user').get_by_user_id(uuid or req.session._uid, req.context)["id"]
+ num = 0
+ while True:
+ res = req.session.model('im.message').get_messages(last, users_watch, uuid=uuid, context=req.context)
+ if num >= 1 or len(res["res"]) > 0:
+ return res
+ last = res["last"]
+ num += 1
+ ImWatcher.get_watcher(res["dbname"]).stop(my_id, users_watch or [], POLL_TIMER)
+
+ @openerp.addons.web.http.jsonrequest
+ def activated(self, req):
+ return not not openerp.evented
+
+ @openerp.addons.web.http.jsonrequest
+ def gen_uuid(self, req):
+ import uuid
+ return "%s" % uuid.uuid1()
+
+def assert_uuid(uuid):
+ if not isinstance(uuid, (str, unicode, type(None))):
+ raise Exception("%s is not a uuid" % uuid)
+
+
+class im_message(osv.osv):
+ _name = 'im.message'
+
+ _order = "date desc"
+
+ _columns = {
+ 'message': fields.char(string="Message", size=200, required=True),
+ 'from_id': fields.many2one("im.user", "From", required= True, ondelete='cascade'),
+ 'to_id': fields.many2one("im.user", "To", required=True, select=True, ondelete='cascade'),
+ 'date': fields.datetime("Date", required=True, select=True),
+ }
+
+ _defaults = {
+ 'date': lambda *args: datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
+ }
+
+ def get_messages(self, cr, uid, last=None, users_watch=None, uuid=None, context=None):
+ assert_uuid(uuid)
+ users_watch = users_watch or []
+
+ # complex stuff to determine the last message to show
+ users = self.pool.get("im.user")
+ my_id = users.get_by_user_id(cr, uid, uuid or uid, context=context)["id"]
+ c_user = users.browse(cr, openerp.SUPERUSER_ID, my_id, context=context)
+ if last:
+ if c_user.im_last_received < last:
+ users.write(cr, openerp.SUPERUSER_ID, my_id, {'im_last_received': last}, context=context)
+ else:
+ last = c_user.im_last_received or -1
+
+ # how fun it is to always need to reorder results from read
+ mess_ids = self.search(cr, openerp.SUPERUSER_ID, [['id', '>', last], ['to_id', '=', my_id]], order="id", context=context)
+ mess = self.read(cr, openerp.SUPERUSER_ID, mess_ids, ["id", "message", "from_id", "date"], context=context)
+ index = {}
+ for i in xrange(len(mess)):
+ index[mess[i]["id"]] = mess[i]
+ mess = []
+ for i in mess_ids:
+ mess.append(index[i])
+
+ if len(mess) > 0:
+ last = mess[-1]["id"]
+ users_status = users.read(cr, openerp.SUPERUSER_ID, users_watch, ["im_status"], context=context)
+ return {"res": mess, "last": last, "dbname": cr.dbname, "users_status": users_status}
+
+ def post(self, cr, uid, message, to_user_id, uuid=None, context=None):
+ assert_uuid(uuid)
+ my_id = self.pool.get('im.user').get_by_user_id(cr, uid, uuid or uid)["id"]
+ self.create(cr, openerp.SUPERUSER_ID, {"message": message, 'from_id': my_id, 'to_id': to_user_id}, context=context)
+ notify_channel(cr, "im_channel", {'type': 'message', 'receiver': to_user_id})
+ return False
+
+class im_user(osv.osv):
+ _name = "im.user"
+
+ def _im_status(self, cr, uid, ids, something, something_else, context=None):
+ res = {}
+ current = datetime.datetime.now()
+ delta = datetime.timedelta(0, DISCONNECTION_TIMER)
+ data = self.read(cr, openerp.SUPERUSER_ID, ids, ["im_last_status_update", "im_last_status"], context=context)
+ for obj in data:
+ last_update = datetime.datetime.strptime(obj["im_last_status_update"], DEFAULT_SERVER_DATETIME_FORMAT)
+ res[obj["id"]] = obj["im_last_status"] and (last_update + delta) > current
+ return res
+
+ def search_users(self, cr, uid, domain, fields, limit, context=None):
+ # do not user openerp.SUPERUSER_ID, reserved to normal users
+ found = self.pool.get('res.users').search(cr, uid, domain, limit=limit, context=context)
+ found = self.get_by_user_ids(cr, uid, found, context=context)
+ return self.read(cr, uid, found, fields, context=context)
+
+ def im_connect(self, cr, uid, uuid=None, context=None):
+ assert_uuid(uuid)
+ return self._im_change_status(cr, uid, True, uuid, context)
+
+ def im_disconnect(self, cr, uid, uuid=None, context=None):
+ assert_uuid(uuid)
+ return self._im_change_status(cr, uid, False, uuid, context)
+
+ def _im_change_status(self, cr, uid, new_one, uuid=None, context=None):
+ assert_uuid(uuid)
+ id = self.get_by_user_id(cr, uid, uuid or uid, context=context)["id"]
+ current_status = self.read(cr, openerp.SUPERUSER_ID, id, ["im_status"], context=None)["im_status"]
+ self.write(cr, openerp.SUPERUSER_ID, id, {"im_last_status": new_one,
+ "im_last_status_update": datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
+ if current_status != new_one:
+ notify_channel(cr, "im_channel", {'type': 'status', 'user': id})
+ return True
+
+ def get_by_user_id(self, cr, uid, id, context=None):
+ ids = self.get_by_user_ids(cr, uid, [id], context=context)
+ return ids[0]
+
+ def get_by_user_ids(self, cr, uid, ids, context=None):
+ user_ids = [x for x in ids if isinstance(x, int)]
+ uuids = [x for x in ids if isinstance(x, (str, unicode))]
+ users = self.search(cr, openerp.SUPERUSER_ID, ["|", ["user", "in", user_ids], ["uuid", "in", uuids]], context=None)
+ records = self.read(cr, openerp.SUPERUSER_ID, users, ["user", "uuid"], context=None)
+ inside = {}
+ for i in records:
+ if i["user"]:
+ inside[i["user"][0]] = True
+ elif ["uuid"]:
+ inside[i["uuid"]] = True
+ not_inside = {}
+ for i in ids:
+ if not (i in inside):
+ not_inside[i] = True
+ for to_create in not_inside.keys():
+ if isinstance(to_create, int):
+ created = self.create(cr, openerp.SUPERUSER_ID, {"user": to_create}, context=context)
+ records.append({"id": created, "user": [to_create, ""]})
+ else:
+ created = self.create(cr, openerp.SUPERUSER_ID, {"uuid": to_create}, context=context)
+ records.append({"id": created, "uuid": to_create})
+ return records
+
+ def assign_name(self, cr, uid, uuid, name, context=None):
+ assert_uuid(uuid)
+ id = self.get_by_user_id(cr, uid, uuid or uid, context=context)["id"]
+ self.write(cr, openerp.SUPERUSER_ID, id, {"assigned_name": name}, context=context)
+ return True
+
+ def _get_name(self, cr, uid, ids, name, arg, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = record.assigned_name
+ if record.user:
+ res[record.id] = record.user.name
+ continue
+ return res
+
+ _columns = {
+ 'name': fields.function(_get_name, type='char', size=200, string="Name", store=True, readonly=True),
+ 'assigned_name': fields.char(string="Assigned Name", size=200, required=False),
+ 'image': fields.related('user', 'image_small', type='binary', string="Image", readonly=True),
+ 'user': fields.many2one("res.users", string="User", select=True, ondelete='cascade'),
+ 'uuid': fields.char(string="UUID", size=50, select=True),
+ 'im_last_received': fields.integer(string="Instant Messaging Last Received Message"),
+ 'im_last_status': fields.boolean(strint="Instant Messaging Last Status"),
+ 'im_last_status_update': fields.datetime(string="Instant Messaging Last Status Update"),
+ 'im_status': fields.function(_im_status, string="Instant Messaging Status", type='boolean'),
+ }
+
+ _defaults = {
+ 'im_last_received': -1,
+ 'im_last_status': False,
+ 'im_last_status_update': lambda *args: datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
+ }
diff --git a/addons/im/security/im_security.xml b/addons/im/security/im_security.xml
new file mode 100644
index 00000000000..10297ac0669
--- /dev/null
+++ b/addons/im/security/im_security.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ Can only read messages that you sent or messages sent to you
+
+
+ ["|", ('to_id.user', '=', user.id), ('from_id.user', '=', user.id)]
+
+
+
+
+
+
+
+ Can only modify your user
+
+
+ [('user', '=', user.id)]
+
+
+
+
+
+
+
diff --git a/addons/im/security/ir.model.access.csv b/addons/im/security/ir.model.access.csv
new file mode 100644
index 00000000000..ed639353e21
--- /dev/null
+++ b/addons/im/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_im_message,im.message,model_im_message,base.group_user,1,0,1,0
+access_im_user,im.user,model_im_user,base.group_user,1,1,1,0
\ No newline at end of file
diff --git a/addons/im/static/src/audio/Ting.mp3 b/addons/im/static/src/audio/Ting.mp3
new file mode 100644
index 00000000000..6fd090a89ce
Binary files /dev/null and b/addons/im/static/src/audio/Ting.mp3 differ
diff --git a/addons/im/static/src/audio/Ting.ogg b/addons/im/static/src/audio/Ting.ogg
new file mode 100644
index 00000000000..8d17ea85bd3
Binary files /dev/null and b/addons/im/static/src/audio/Ting.ogg differ
diff --git a/addons/im/static/src/audio/purr.mp3 b/addons/im/static/src/audio/purr.mp3
new file mode 100644
index 00000000000..849d303c3b1
Binary files /dev/null and b/addons/im/static/src/audio/purr.mp3 differ
diff --git a/addons/im/static/src/audio/purr.ogg b/addons/im/static/src/audio/purr.ogg
new file mode 100644
index 00000000000..2ddc77537f1
Binary files /dev/null and b/addons/im/static/src/audio/purr.ogg differ
diff --git a/addons/im/static/src/css/im.css b/addons/im/static/src/css/im.css
new file mode 100644
index 00000000000..2f3e7dd58bb
--- /dev/null
+++ b/addons/im/static/src/css/im.css
@@ -0,0 +1,258 @@
+
+.openerp .oe_im {
+ position: fixed;
+ background-color: #E8EBEF;
+ width: 220px;
+ border-left: 1px solid #AEB9BD;
+}
+
+/* button */
+
+.openerp .oe_topbar_imbutton {
+ cursor: pointer;
+}
+
+/* search stuff */
+.openerp .oe_im_frame_header {
+ position: relative;
+ background: #dedede;
+ background: -moz-linear-gradient(#fcfcfc, #dedede);
+ background: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede));
+ border-bottom: 1px solid border-color !important;
+ padding: 5px;
+}
+.openerp .oe_im_frame_header .oe_im_searchbox {
+ width: 168px;
+ padding: 1px 21px 1px 19px;
+ font-size: 13px;
+ -moz-border-radius: 13px;
+ -webkit-border-radius: 13px;
+ border-radius: 13px;
+}
+.openerp .oe_im_frame_header .oe_im_search_icon {
+ position: absolute;
+ color: #888;
+ top: 2px;
+ left: 9px;
+ font-size: 28px;
+ font-family: "entypoRegular" !important;
+ font-weight: 300 !important;
+}
+.openerp .oe_im_frame_header .oe_im_search_clear {
+ display: none;
+ position: absolute;
+ right: 11px;
+ top: 4px;
+ font-size: 26px;
+ color: #b6b6b6;
+ cursor: pointer;
+}
+.openerp .oe_im_frame_header .oe_im_search_clear:hover {
+ color: #888;
+}
+
+/* users */
+
+.openerp .oe_im_users {
+ padding-bottom: 38px;
+}
+.openerp .oe_im_user {
+ position: relative;
+ padding: 2px 6px;
+ cursor: pointer;
+ font-size: 13px;
+ margin-bottom: 3px;
+}
+.openerp .oe_im_user:hover {
+ background: lightGrey;
+}
+.openerp .oe_im_user_clip {
+ display: inline-block;
+ width: 26px;
+ height: 26px;
+ margin-right: 4px;
+ -moz-box-shadow: 0 0 2px 1px rgba(0,0,0,0.25);
+ -webkit-box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25);
+ box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25);
+}
+.openerp .oe_im_user_avatar {
+ width: 26px;
+ height: auto;
+}
+.openerp .oe_im_user_name {
+ width: 162px;
+ line-height: 26px;
+ padding-right: 15px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ position: relative;
+}
+
+.openerp .oe_im_user_online {
+ display: none;
+ position: absolute;
+ top: 9.5px;
+ right: 11px;
+ width: 11px;
+ height: 11px;
+ vertical-align: middle;
+ border: 0;
+}
+
+/* conversations */
+
+.openerp .oe_im_chatview {
+ position: fixed;
+ overflow: hidden;
+ bottom: 6px;
+ margin-right: 6px;
+ background: rgba(60, 60, 60, 0.8);
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: 0 0 3px rgba(0,0,0,0.3), 0 2px 4px rgba(0,0,0,0.3);
+ -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
+ width: 240px;
+}
+.openerp .oe_im_chatview .oe_im_chatview_disconnected {
+ display:none;
+ z-index: 100;
+ width: 100%;
+ background: #E8EBEF;
+ padding: 5px;
+ font-size: 11px;
+ color: #999;
+ line-height: 14px;
+ height: 28px;
+ overflow: hidden;
+}
+.openerp .oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_disconnected {
+ display: block;
+}
+.openerp .oe_im_chatview .oe_im_chatview_header {
+ padding: 3px 6px 2px;
+ background: #DEDEDE;
+ background: -moz-linear-gradient(#FCFCFC, #DEDEDE);
+ background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE));
+ -moz-border-radius: 3px 3px 0 0;
+ -webkit-border-radius: 3px 3px 0 0;
+ border-radius: 3px 3px 0 0;
+ border-bottom: 1px solid #AEB9BD;
+ cursor: pointer;
+}
+.openerp .oe_im_chatview .oe_im_chatview_close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+ font-size: 18px;
+ line-height: 16px;
+ float: right;
+ font-weight: bold;
+ color: black;
+ text-shadow: 0 1px 0 white;
+ opacity: 0.2;
+}
+.openerp .oe_im_chatview .oe_im_chatview_content {
+ overflow: auto;
+ height: 287px;
+}
+.openerp .oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_content {
+ height: 249px;
+}
+.openerp .oe_im_chatview .oe_im_chatview_footer {
+ position: relative;
+ padding: 3px;
+ border-top: 1px solid #AEB9BD;
+ background: #DEDEDE;
+ background: -moz-linear-gradient(#FCFCFC, #DEDEDE);
+ background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE));
+ -moz-border-radius: 0 0 3px 3px;
+ -webkit-border-radius: 0 0 3px 3px;
+ border-radius: 0 0 3px 3px;
+}
+.openerp .oe_im_chatview .oe_im_chatview_input {
+ width: 222px;
+ font-family: Lato, Helvetica, sans-serif;
+ font-size: 13px;
+ color: #333;
+ padding: 1px 5px;
+ border: 1px solid #AEB9BD;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: inset 0 1px 4px rgba(0,0,0,0.2);
+ -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2);
+ box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2);
+}
+.openerp .oe_im_chatview .oe_im_chatview_bubble {
+ background: white;
+ position: relative;
+ padding: 3px;
+ margin: 3px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+.openerp .oe_im_chatview .oe_im_chatview_clip {
+ position: relative;
+ float: left;
+ width: 26px;
+ height: 26px;
+ margin-right: 4px;
+ -moz-box-shadow: 0 0 2px 1px rgba(0,0,0,0.25);
+ -webkit-box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25);
+ box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25);
+}
+.openerp .oe_im_chatview .oe_im_chatview_avatar {
+ float: left;
+ width: 26px;
+ height: auto;
+ clip: rect(0, 26px, 26px, 0);
+ max-width: 100%;
+ width: auto 9;
+ height: auto;
+ vertical-align: middle;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+}
+.openerp .oe_im_chatview .oe_im_chatview_time {
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ margin: 3px;
+ text-align: right;
+ line-height: 13px;
+ font-size: 11px;
+ color: #999;
+ width: 60px;
+ overflow: hidden;
+}
+.openerp .oe_im_chatview .oe_im_chatview_from {
+ margin: 0 0 2px 0;
+ line-height: 14px;
+ font-weight: bold;
+ font-size: 12px;
+ width: 140px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ color: #3A87AD;
+}
+.openerp .oe_im_chatview .oe_im_chatview_bubble_list {
+}
+.openerp .oe_im_chatview .oe_im_chatview_bubble_item {
+ margin: 0 0 2px 30px;
+ line-height: 14px;
+ word-wrap: break-word;
+}
+
+.openerp .oe_im_chatview_online {
+ display: none;
+ margin-top: -4px;
+ width: 11px;
+ height: 11px;
+}
diff --git a/addons/im/static/src/img/avatar/avatar.jpeg b/addons/im/static/src/img/avatar/avatar.jpeg
new file mode 100644
index 00000000000..7168794022e
Binary files /dev/null and b/addons/im/static/src/img/avatar/avatar.jpeg differ
diff --git a/addons/im/static/src/img/button-gloss.png b/addons/im/static/src/img/button-gloss.png
new file mode 100755
index 00000000000..6f3957702fe
Binary files /dev/null and b/addons/im/static/src/img/button-gloss.png differ
diff --git a/addons/im/static/src/img/glyphicons-halflings-white.png b/addons/im/static/src/img/glyphicons-halflings-white.png
new file mode 100755
index 00000000000..3bf6484a29d
Binary files /dev/null and b/addons/im/static/src/img/glyphicons-halflings-white.png differ
diff --git a/addons/im/static/src/img/glyphicons-halflings.png b/addons/im/static/src/img/glyphicons-halflings.png
new file mode 100755
index 00000000000..a9969993201
Binary files /dev/null and b/addons/im/static/src/img/glyphicons-halflings.png differ
diff --git a/addons/im/static/src/img/green.png b/addons/im/static/src/img/green.png
new file mode 100644
index 00000000000..01fb373c251
Binary files /dev/null and b/addons/im/static/src/img/green.png differ
diff --git a/addons/im/static/src/img/icon.png b/addons/im/static/src/img/icon.png
new file mode 100644
index 00000000000..07d2503ce4e
Binary files /dev/null and b/addons/im/static/src/img/icon.png differ
diff --git a/addons/im/static/src/img/logo.png b/addons/im/static/src/img/logo.png
new file mode 100644
index 00000000000..aca5f4c60d8
Binary files /dev/null and b/addons/im/static/src/img/logo.png differ
diff --git a/addons/im/static/src/img/wood.png b/addons/im/static/src/img/wood.png
new file mode 100644
index 00000000000..22f2450d3ad
Binary files /dev/null and b/addons/im/static/src/img/wood.png differ
diff --git a/addons/im/static/src/js/im.js b/addons/im/static/src/js/im.js
new file mode 100644
index 00000000000..b21a339cb84
--- /dev/null
+++ b/addons/im/static/src/js/im.js
@@ -0,0 +1,463 @@
+
+openerp.im = function(instance) {
+
+ var USERS_LIMIT = 20;
+ var ERROR_DELAY = 5000;
+
+ var _t = instance.web._t,
+ _lt = instance.web._lt;
+ var QWeb = instance.web.qweb;
+
+ instance.web.UserMenu.include({
+ do_update: function(){
+ var self = this;
+ this.update_promise.then(function() {
+ var im = new instance.im.InstantMessaging(self);
+ im.appendTo(instance.client.$el);
+ var button = new instance.im.ImTopButton(this);
+ button.on("clicked", im, im.switch_display);
+ button.appendTo(instance.webclient.$el.find('.oe_systray'));
+ });
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ instance.im.ImTopButton = instance.web.Widget.extend({
+ template:'ImTopButton',
+ events: {
+ "click": "clicked",
+ },
+ clicked: function() {
+ this.trigger("clicked");
+ },
+ });
+
+ instance.im.InstantMessaging = instance.web.Widget.extend({
+ template: "InstantMessaging",
+ events: {
+ "keydown .oe_im_searchbox": "input_change",
+ "keyup .oe_im_searchbox": "input_change",
+ "change .oe_im_searchbox": "input_change",
+ },
+ init: function(parent) {
+ this._super(parent);
+ this.shown = false;
+ this.set("right_offset", 0);
+ this.set("current_search", "");
+ this.users = [];
+ this.c_manager = new instance.im.ConversationManager(this);
+ this.on("change:right_offset", this.c_manager, _.bind(function() {
+ this.c_manager.set("right_offset", this.get("right_offset"));
+ }, this));
+ this.user_search_dm = new instance.web.DropMisordered();
+ },
+ start: function() {
+ this.$el.css("right", -this.$el.outerWidth());
+ $(window).scroll(_.bind(this.calc_box, this));
+ $(window).resize(_.bind(this.calc_box, this));
+ this.calc_box();
+
+ this.on("change:current_search", this, this.search_changed);
+ this.search_changed();
+
+ var self = this;
+
+ return this.c_manager.start_polling();
+ },
+ calc_box: function() {
+ var $topbar = instance.client.$(".oe_topbar");
+ var top = $topbar.offset().top + $topbar.height();
+ top = Math.max(top - $(window).scrollTop(), 0);
+ this.$el.css("top", top);
+ this.$el.css("bottom", 0);
+ },
+ input_change: function() {
+ this.set("current_search", this.$(".oe_im_searchbox").val());
+ },
+ search_changed: function(e) {
+ var users = new instance.web.Model("im.user");
+ var self = this;
+ return this.user_search_dm.add(users.call("search_users",
+ [[["name", "ilike", this.get("current_search")], ["id", "<>", instance.session.uid]],
+ ["name", "user", "uuid", "im_status"], USERS_LIMIT], {context:new instance.web.CompoundContext()})).then(function(result) {
+ self.c_manager.add_to_user_cache(result);
+ self.$(".oe_im_input").val("");
+ var old_users = self.users;
+ self.users = [];
+ _.each(result, function(user) {
+ var widget = new instance.im.UserWidget(self, self.c_manager.get_user(user.id));
+ widget.appendTo(self.$(".oe_im_users"));
+ widget.on("activate_user", self, self.activate_user);
+ self.users.push(widget);
+ });
+ _.each(old_users, function(user) {
+ user.destroy();
+ });
+ });
+ },
+ switch_display: function() {
+ var fct = _.bind(function(place) {
+ this.set("right_offset", place + this.$el.outerWidth());
+ }, this);
+ var opt = {
+ step: fct,
+ };
+ if (this.shown) {
+ this.$el.animate({
+ right: -this.$el.outerWidth(),
+ }, opt);
+ } else {
+ if (! this.c_manager.get_activated()) {
+ this.do_warn("Instant Messaging is not activated on this server.", "");
+ return;
+ }
+ this.$el.animate({
+ right: 0,
+ }, opt);
+ }
+ this.shown = ! this.shown;
+ },
+ activate_user: function(user) {
+ this.c_manager.activate_user(user, true);
+ },
+ });
+
+ instance.im.UserWidget = instance.web.Widget.extend({
+ "template": "UserWidget",
+ events: {
+ "click": "activate_user",
+ },
+ init: function(parent, user) {
+ this._super(parent);
+ this.user = user;
+ this.user.add_watcher();
+ },
+ start: function() {
+ var change_status = function() {
+ this.$(".oe_im_user_online").toggle(this.user.get("im_status") === true);
+ };
+ this.user.on("change:im_status", this, change_status);
+ change_status.call(this);
+ },
+ activate_user: function() {
+ this.trigger("activate_user", this.user);
+ },
+ destroy: function() {
+ this.user.remove_watcher();
+ this._super();
+ },
+ });
+
+ instance.im.ImUser = instance.web.Class.extend(instance.web.PropertiesMixin, {
+ init: function(parent, user_rec) {
+ instance.web.PropertiesMixin.init.call(this, parent);
+ user_rec.image_url = instance.session.url("/im/static/src/img/avatar/avatar.jpeg");
+ if (user_rec.user)
+ user_rec.image_url = instance.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: user_rec.user[0]});
+ this.set(user_rec);
+ this.set("watcher_count", 0);
+ this.on("change:watcher_count", this, function() {
+ if (this.get("watcher_count") === 0)
+ this.destroy();
+ });
+ },
+ destroy: function() {
+ this.trigger("destroyed");
+ instance.web.PropertiesMixin.destroy.call(this);
+ },
+ add_watcher: function() {
+ this.set("watcher_count", this.get("watcher_count") + 1);
+ },
+ remove_watcher: function() {
+ this.set("watcher_count", this.get("watcher_count") - 1);
+ },
+ });
+
+ instance.im.ConversationManager = instance.web.Controller.extend({
+ init: function(parent) {
+ this._super(parent);
+ this.set("right_offset", 0);
+ this.conversations = [];
+ this.users = {};
+ this.on("change:right_offset", this, this.calc_positions);
+ this.set("window_focus", true);
+ this.set("waiting_messages", 0);
+ this.focus_hdl = _.bind(function() {
+ this.set("window_focus", true);
+ }, this);
+ $(window).bind("focus", this.focus_hdl);
+ this.blur_hdl = _.bind(function() {
+ this.set("window_focus", false);
+ }, this);
+ $(window).bind("blur", this.blur_hdl);
+ this.on("change:window_focus", this, this.window_focus_change);
+ this.window_focus_change();
+ this.on("change:waiting_messages", this, this.messages_change);
+ this.messages_change();
+ this.create_ting();
+ this.activated = false;
+ this.users_cache = {};
+ this.last = null;
+ this.unload_event_handler = _.bind(this.unload, this);
+ },
+ start_polling: function() {
+ var self = this;
+ return new instance.web.Model("im.user").call("get_by_user_id", [instance.session.uid]).then(function(my_id) {
+ self.my_id = my_id["id"];
+ return self.ensure_users([self.my_id]).then(function() {
+ var me = self.users_cache[self.my_id];
+ delete self.users_cache[self.my_id];
+ self.me = me;
+ self.rpc("/longpolling/im/activated", {}, {shadow: true}).then(function(activated) {
+ if (activated) {
+ self.activated = true;
+ $(window).on("unload", self.unload_event_handler);
+ self.poll();
+ }
+ }, function(a, e) {
+ e.preventDefault();
+ });
+ });
+ });
+ },
+ unload: function() {
+ return new instance.web.Model("im.user").call("im_disconnect", [], {context: new instance.web.CompoundContext()});
+ },
+ ensure_users: function(user_ids) {
+ var no_cache = {};
+ _.each(user_ids, function(el) {
+ if (! this.users_cache[el])
+ no_cache[el] = el;
+ }, this);
+ var self = this;
+ if (_.size(no_cache) === 0)
+ return $.when();
+ else
+ return new instance.web.Model("im.user").call("read", [_.values(no_cache), ["name", "user", "uuid", "im_status"]],
+ {context: new instance.web.CompoundContext()}).then(function(users) {
+ self.add_to_user_cache(users);
+ });
+ },
+ add_to_user_cache: function(user_recs) {
+ _.each(user_recs, function(user_rec) {
+ if (! this.users_cache[user_rec.id]) {
+ var user = new instance.im.ImUser(this, user_rec);
+ this.users_cache[user_rec.id] = user;
+ user.on("destroyed", this, function() {
+ delete this.users_cache[user_rec.id];
+ });
+ }
+ }, this);
+ },
+ get_user: function(user_id) {
+ return this.users_cache[user_id];
+ },
+ poll: function() {
+ var self = this;
+ var user_ids = _.map(this.users_cache, function(el) {
+ return el.get("id");
+ });
+ this.rpc("/longpolling/im/poll", {
+ last: this.last,
+ users_watch: user_ids,
+ context: instance.web.pyeval.eval('context', {}),
+ }, {shadow: true}).then(function(result) {
+ _.each(result.users_status, function(el) {
+ if (self.get_user(el.id))
+ self.get_user(el.id).set(el);
+ });
+ self.last = result.last;
+ var user_ids = _.pluck(_.pluck(result.res, "from_id"), 0);
+ self.ensure_users(user_ids).then(function() {
+ _.each(result.res, function(mes) {
+ var user = self.get_user(mes.from_id[0]);
+ self.received_message(mes, user);
+ });
+ self.poll();
+ });
+ }, function(unused, e) {
+ e.preventDefault();
+ setTimeout(_.bind(self.poll, self), ERROR_DELAY);
+ });
+ },
+ get_activated: function() {
+ return this.activated;
+ },
+ create_ting: function() {
+ var kitten = jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined;
+ this.ting = new Audio(instance.webclient.session.origin + "/im/static/src/audio/" + (kitten ? "purr" : "Ting") +
+ (new Audio().canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3"));
+ },
+ window_focus_change: function() {
+ if (this.get("window_focus")) {
+ this.set("waiting_messages", 0);
+ }
+ },
+ messages_change: function() {
+ if (! instance.webclient.set_title_part)
+ return;
+ instance.webclient.set_title_part("aa_im_messages", this.get("waiting_messages") === 0 ? undefined :
+ _.str.sprintf(_t("%d Messages"), this.get("waiting_messages")));
+ },
+ activate_user: function(user, focus) {
+ var conv = this.users[user.get('id')];
+ if (! conv) {
+ conv = new instance.im.Conversation(this, user, this.me);
+ conv.appendTo(instance.client.$el);
+ conv.on("destroyed", this, function() {
+ this.conversations = _.without(this.conversations, conv);
+ delete this.users[conv.user.get('id')];
+ this.calc_positions();
+ });
+ this.conversations.push(conv);
+ this.users[user.get('id')] = conv;
+ this.calc_positions();
+ }
+ if (focus)
+ conv.focus();
+ return conv;
+ },
+ received_message: function(message, user) {
+ if (! this.get("window_focus")) {
+ this.set("waiting_messages", this.get("waiting_messages") + 1);
+ this.ting.play();
+ this.create_ting();
+ }
+ var conv = this.activate_user(user);
+ conv.received_message(message);
+ },
+ calc_positions: function() {
+ var current = this.get("right_offset");
+ _.each(_.range(this.conversations.length), function(i) {
+ this.conversations[i].set("right_position", current);
+ current += this.conversations[i].$el.outerWidth(true);
+ }, this);
+ },
+ destroy: function() {
+ $(window).off("unload", this.unload_event_handler);
+ $(window).unbind("blur", this.blur_hdl);
+ $(window).unbind("focus", this.focus_hdl);
+ this._super();
+ },
+ });
+
+ instance.im.Conversation = instance.web.Widget.extend({
+ "template": "Conversation",
+ events: {
+ "keydown input": "send_message",
+ "click .oe_im_chatview_close": "destroy",
+ "click .oe_im_chatview_header": "show_hide",
+ },
+ init: function(parent, user, me) {
+ this._super(parent);
+ this.me = me;
+ this.user = user;
+ this.user.add_watcher();
+ this.set("right_position", 0);
+ this.shown = true;
+ this.set("pending", 0);
+ },
+ start: function() {
+ var change_status = function() {
+ this.$el.toggleClass("oe_im_chatview_disconnected_status", this.user.get("im_status") === false);
+ this.$(".oe_im_chatview_online").toggle(this.user.get("im_status") === true);
+ this._go_bottom();
+ };
+ this.user.on("change:im_status", this, change_status);
+ change_status.call(this);
+
+ this.on("change:right_position", this, this.calc_pos);
+ this.full_height = this.$el.height();
+ this.calc_pos();
+ this.on("change:pending", this, _.bind(function() {
+ if (this.get("pending") === 0) {
+ this.$(".oe_im_chatview_nbr_messages").text("");
+ } else {
+ this.$(".oe_im_chatview_nbr_messages").text("(" + this.get("pending") + ")");
+ }
+ }, this));
+ },
+ show_hide: function() {
+ if (this.shown) {
+ this.$el.animate({
+ height: this.$(".oe_im_chatview_header").outerHeight(),
+ });
+ } else {
+ this.$el.animate({
+ height: this.full_height,
+ });
+ }
+ this.shown = ! this.shown;
+ if (this.shown) {
+ this.set("pending", 0);
+ }
+ },
+ calc_pos: function() {
+ this.$el.css("right", this.get("right_position"));
+ },
+ received_message: function(message) {
+ if (this.shown) {
+ this.set("pending", 0);
+ } else {
+ this.set("pending", this.get("pending") + 1);
+ }
+ this._add_bubble(this.user, message.message, message.date);
+ },
+ send_message: function(e) {
+ if(e && e.which !== 13) {
+ return;
+ }
+ var mes = this.$("input").val();
+ this.$("input").val("");
+ var send_it = _.bind(function() {
+ var model = new instance.web.Model("im.message");
+ return model.call("post", [mes, this.user.get('id')],
+ {context: new instance.web.CompoundContext()});
+ }, this);
+ var tries = 0;
+ send_it().then(_.bind(function() {
+ this._add_bubble(this.me, mes, instance.web.datetime_to_str(new Date()));
+ }, this), function(error, e) {
+ e.preventDefault();
+ tries += 1;
+ if (tries < 3)
+ return send_it();
+ });
+ },
+ _add_bubble: function(user, item, date) {
+ var items = [item];
+ if (user === this.last_user) {
+ this.last_bubble.remove();
+ items = this.last_items.concat(items);
+ }
+ this.last_user = user;
+ this.last_items = items;
+ date = instance.web.str_to_datetime(date);
+ var now = new Date();
+ var diff = now - date;
+ if (diff > (1000 * 60 * 60 * 24)) {
+ date = $.timeago(date);
+ } else {
+ date = date.toString(Date.CultureInfo.formatPatterns.shortTime);
+ }
+
+ this.last_bubble = $(QWeb.render("Conversation.bubble", {"items": items, "user": user, "time": date}));
+ $(this.$(".oe_im_chatview_content").children()[0]).append(this.last_bubble);
+ this._go_bottom();
+ },
+ _go_bottom: function() {
+ this.$(".oe_im_chatview_content").scrollTop($(this.$(".oe_im_chatview_content").children()[0]).height());
+ },
+ focus: function() {
+ this.$(".oe_im_chatview_input").focus();
+ if (! this.shown)
+ this.show_hide();
+ },
+ destroy: function() {
+ this.user.remove_watcher();
+ this.trigger("destroyed");
+ return this._super();
+ },
+ });
+
+}
\ No newline at end of file
diff --git a/addons/im/static/src/xml/im.xml b/addons/im/static/src/xml/im.xml
new file mode 100644
index 00000000000..795d1b3e698
--- /dev/null
+++ b/addons/im/static/src/xml/im.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+ ô
+
+ [
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/im_livechat/__init__.py b/addons/im_livechat/__init__.py
new file mode 100644
index 00000000000..2825a75179c
--- /dev/null
+++ b/addons/im_livechat/__init__.py
@@ -0,0 +1,2 @@
+
+import im_livechat
diff --git a/addons/im_livechat/__openerp__.py b/addons/im_livechat/__openerp__.py
new file mode 100644
index 00000000000..8b9ea3924d5
--- /dev/null
+++ b/addons/im_livechat/__openerp__.py
@@ -0,0 +1,29 @@
+{
+ 'name' : 'Live Support',
+ 'version': '1.0',
+ 'summary': 'Live Chat with Visitors/Customers',
+ 'category': 'Tools',
+ 'complexity': 'easy',
+ 'description':
+ """
+Live Chat Support
+=================
+
+Allow to drop instant messaging widgets on any web page that will communicate
+with the current server and dispatch visitors request amongst several live
+chat operators.
+
+ """,
+ 'data': [
+ "security/im_livechat_security.xml",
+ "security/ir.model.access.csv",
+ "im_livechat_view.xml",
+ ],
+ 'demo': [
+ "im_livechat_demo.xml",
+ ],
+ 'depends' : ["im", "mail", "portal_anonymous"],
+ 'installable': True,
+ 'auto_install': False,
+ 'application': True,
+}
diff --git a/addons/im_livechat/im_livechat.py b/addons/im_livechat/im_livechat.py
new file mode 100644
index 00000000000..1ffb422fe6a
--- /dev/null
+++ b/addons/im_livechat/im_livechat.py
@@ -0,0 +1,245 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import openerp
+import openerp.addons.im.im as im
+import json
+import random
+import jinja2
+from openerp.osv import osv, fields
+from openerp import tools
+
+env = jinja2.Environment(
+ loader=jinja2.PackageLoader('openerp.addons.im_livechat', "."),
+ autoescape=False
+)
+env.filters["json"] = json.dumps
+
+class LiveChatController(openerp.addons.web.http.Controller):
+ _cp_path = '/im_livechat'
+
+ @openerp.addons.web.http.httprequest
+ def loader(self, req, **kwargs):
+ p = json.loads(kwargs["p"])
+ db = p["db"]
+ channel = p["channel"]
+ user_name = p.get("user_name", None)
+ req.session._db = db
+ req.session._uid = None
+ req.session._login = "anonymous"
+ req.session._password = "anonymous"
+ info = req.session.model('im_livechat.channel').get_info_for_chat_src(channel)
+ info["db"] = db
+ info["channel"] = channel
+ info["userName"] = user_name
+ return req.make_response(env.get_template("loader.js").render(info),
+ headers=[('Content-Type', "text/javascript")])
+
+ @openerp.addons.web.http.httprequest
+ def web_page(self, req, **kwargs):
+ p = json.loads(kwargs["p"])
+ db = p["db"]
+ channel = p["channel"]
+ req.session._db = db
+ req.session._uid = None
+ req.session._login = "anonymous"
+ req.session._password = "anonymous"
+ script = req.session.model('im_livechat.channel').read(channel, ["script"])["script"]
+ info = req.session.model('im_livechat.channel').get_info_for_chat_src(channel)
+ info["script"] = script
+ return req.make_response(env.get_template("web_page.html").render(info),
+ headers=[('Content-Type', "text/html")])
+
+ @openerp.addons.web.http.jsonrequest
+ def available(self, req, db, channel):
+ req.session._db = db
+ req.session._uid = None
+ req.session._login = "anonymous"
+ req.session._password = "anonymous"
+ return req.session.model('im_livechat.channel').get_available_user(channel) > 0
+
+class im_livechat_channel(osv.osv):
+ _name = 'im_livechat.channel'
+
+ def _get_default_image(self, cr, uid, context=None):
+ image_path = openerp.modules.get_module_resource('im_livechat', 'static/src/img', 'default.png')
+ return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
+ def _get_image(self, cr, uid, ids, name, args, context=None):
+ result = dict.fromkeys(ids, False)
+ for obj in self.browse(cr, uid, ids, context=context):
+ result[obj.id] = tools.image_get_resized_images(obj.image)
+ return result
+ def _set_image(self, cr, uid, id, name, value, args, context=None):
+ return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
+
+
+ def _are_you_inside(self, cr, uid, ids, name, arg, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = False
+ for user in record.user_ids:
+ if user.id == uid:
+ res[record.id] = True
+ break
+ return res
+
+ def _script(self, cr, uid, ids, name, arg, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = env.get_template("include.html").render({
+ "url": self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url'),
+ "parameters": {"db":cr.dbname, "channel":record.id},
+ })
+ return res
+
+ def _web_page(self, cr, uid, ids, name, arg, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') + \
+ "/im_livechat/web_page?p=" + json.dumps({"db":cr.dbname, "channel":record.id})
+ return res
+
+ _columns = {
+ 'name': fields.char(string="Channel Name", size=200, required=True),
+ 'user_ids': fields.many2many('res.users', 'im_livechat_channel_im_user', 'channel_id', 'user_id', string="Users"),
+ 'are_you_inside': fields.function(_are_you_inside, type='boolean', string='Are you inside the matrix?', store=False),
+ 'script': fields.function(_script, type='text', string='Script', store=False),
+ 'web_page': fields.function(_web_page, type='url', string='Web Page', store=False, size="200"),
+ 'button_text': fields.char(string="Text of the Button", size=200),
+ 'input_placeholder': fields.char(string="Chat Input Placeholder", size=200),
+ 'default_message': fields.char(string="Welcome Message", size=200, help="This is an automated 'welcome' message that your visitor will see when they initiate a new chat session."),
+ # image: all image fields are base64 encoded and PIL-supported
+ 'image': fields.binary("Photo",
+ help="This field holds the image used as photo for the group, limited to 1024x1024px."),
+ 'image_medium': fields.function(_get_image, fnct_inv=_set_image,
+ string="Medium-sized photo", type="binary", multi="_get_image",
+ store={
+ 'im_livechat.channel': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
+ },
+ help="Medium-sized photo of the group. It is automatically "\
+ "resized as a 128x128px image, with aspect ratio preserved. "\
+ "Use this field in form views or some kanban views."),
+ 'image_small': fields.function(_get_image, fnct_inv=_set_image,
+ string="Small-sized photo", type="binary", multi="_get_image",
+ store={
+ 'im_livechat.channel': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
+ },
+ help="Small-sized photo of the group. It is automatically "\
+ "resized as a 64x64px image, with aspect ratio preserved. "\
+ "Use this field anywhere a small image is required."),
+ }
+
+ def _default_user_ids(self, cr, uid, context=None):
+ return [(6, 0, [uid])]
+
+ _defaults = {
+ 'button_text': "Have a Question? Chat with us.",
+ 'input_placeholder': "How may I help you?",
+ 'default_message': '',
+ 'user_ids': _default_user_ids,
+ 'image': _get_default_image,
+ }
+
+ def get_available_user(self, cr, uid, channel_id, context=None):
+ channel = self.browse(cr, openerp.SUPERUSER_ID, channel_id, context=context)
+ users = []
+ for user in channel.user_ids:
+ iuid = self.pool.get("im.user").get_by_user_id(cr, uid, user.id, context=context)["id"]
+ imuser = self.pool.get("im.user").browse(cr, uid, iuid, context=context)
+ if imuser.im_status:
+ users.append(imuser)
+ if len(users) == 0:
+ return False
+ return random.choice(users).id
+
+ def test_channel(self, cr, uid, channel, context=None):
+ if not channel:
+ return {}
+ return {
+ 'url': self.browse(cr, uid, channel[0], context=context or {}).web_page,
+ 'type': 'ir.actions.act_url'
+ }
+
+ def get_info_for_chat_src(self, cr, uid, channel, context=None):
+ url = self.pool.get('ir.config_parameter').get_param(cr, openerp.SUPERUSER_ID, 'web.base.url')
+ chan = self.browse(cr, uid, channel, context=context)
+ return {
+ "url": url,
+ 'buttonText': chan.button_text,
+ 'inputPlaceholder': chan.input_placeholder,
+ 'defaultMessage': chan.default_message,
+ "channelName": chan.name,
+ }
+
+ def join(self, cr, uid, ids, context=None):
+ self.write(cr, uid, ids, {'user_ids': [(4, uid)]})
+ return True
+
+ def quit(self, cr, uid, ids, context=None):
+ self.write(cr, uid, ids, {'user_ids': [(3, uid)]})
+ return True
+
+
+class im_message(osv.osv):
+ _inherit = 'im.message'
+
+ def _support_member(self, cr, uid, ids, name, arg, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = False
+ if record.to_id.user and record.from_id.user:
+ continue
+ elif record.to_id.user:
+ res[record.id] = record.to_id.user.id
+ elif record.from_id.user:
+ res[record.id] = record.from_id.user.id
+ return res
+
+ def _customer(self, cr, uid, ids, name, arg, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = False
+ if record.to_id.uuid and record.from_id.uuid:
+ continue
+ elif record.to_id.uuid:
+ res[record.id] = record.to_id.id
+ elif record.from_id.uuid:
+ res[record.id] = record.from_id.id
+ return res
+
+ def _direction(self, cr, uid, ids, name, arg, context=None):
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ res[record.id] = False
+ if not not record.to_id.user and not not record.from_id.user:
+ continue
+ elif not not record.to_id.user:
+ res[record.id] = "c2s"
+ elif not not record.from_id.user:
+ res[record.id] = "s2c"
+ return res
+
+ _columns = {
+ 'support_member_id': fields.function(_support_member, type='many2one', relation='res.users', string='Support Member', store=True, select=True),
+ 'customer_id': fields.function(_customer, type='many2one', relation='im.user', string='Customer', store=True, select=True),
+ 'direction': fields.function(_direction, type="selection", selection=[("s2c", "Support Member to Customer"), ("c2s", "Customer to Support Member")],
+ string='Direction', store=False),
+ }
diff --git a/addons/im_livechat/im_livechat_demo.xml b/addons/im_livechat/im_livechat_demo.xml
new file mode 100644
index 00000000000..f199179fb50
--- /dev/null
+++ b/addons/im_livechat/im_livechat_demo.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ YourWebsite.com
+ Hello, how may I help you?
+
+
+
+
+
+
+
+
diff --git a/addons/im_livechat/im_livechat_view.xml b/addons/im_livechat/im_livechat_view.xml
new file mode 100644
index 00000000000..f684fd561fc
--- /dev/null
+++ b/addons/im_livechat/im_livechat_view.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+ Live Chat Channels
+ im_livechat.channel
+ kanban,form
+
+
+ Click to define a new live chat channel.
+
+ You can create channels for each website on which you want
+ to integrate the live chat widget, allowing you website
+ visitors to talk in real time with your operators.
+
+ Each channel has it's own URL that you can send by email to
+ your customers in order to start chatting with you.
+
+
+
+
+
+
+
+
+
+ support_channel.form
+ im_livechat.channel
+
+
+
+
+
+
+ History
+ im.message
+ list
+ ["|", ('to_id.user', '=', None), ('from_id.user', '=', None)]
+
+
+
+
+ im.message.tree
+ im.message
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/im_livechat/include.html b/addons/im_livechat/include.html
new file mode 100644
index 00000000000..55de6212361
--- /dev/null
+++ b/addons/im_livechat/include.html
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/addons/im_livechat/loader.js b/addons/im_livechat/loader.js
new file mode 100644
index 00000000000..669591b410f
--- /dev/null
+++ b/addons/im_livechat/loader.js
@@ -0,0 +1,24 @@
+
+require.config({
+ context: "oelivesupport",
+ baseUrl: {{url | json}} + "/im_livechat/static/ext/static/js",
+ shim: {
+ underscore: {
+ init: function() {
+ return _.noConflict();
+ },
+ },
+ "jquery.achtung": {
+ deps: ['jquery'],
+ },
+ },
+})(["livesupport", "jquery"], function(livesupport, jQuery) {
+ jQuery.noConflict();
+ livesupport.main({{url | json}}, {{db | json}}, "anonymous", "anonymous", {{channel | json}}, {
+ buttonText: {{buttonText | json}},
+ inputPlaceholder: {{inputPlaceholder | json}},
+ defaultMessage: {{(defaultMessage or None) | json}},
+ auto: window.oe_im_livechat_auto || false,
+ userName: {{userName | json}} || undefined,
+ });
+});
diff --git a/addons/im_livechat/security/im_livechat_security.xml b/addons/im_livechat/security/im_livechat_security.xml
new file mode 100644
index 00000000000..5ad020a8a78
--- /dev/null
+++ b/addons/im_livechat/security/im_livechat_security.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Live Support
+
+
+
+
+ User
+
+ The user will be able to join support channels.
+
+
+
+ Manager
+ The user will be able to delete support channels.
+
+
+
+
+
+
+ Live Support Managers can read messages from live support
+
+
+ ["|", ('to_id.user', '=', None), ('from_id.user', '=', None)]
+
+
+
+
+
+
+
+
diff --git a/addons/im_livechat/security/ir.model.access.csv b/addons/im_livechat/security/ir.model.access.csv
new file mode 100644
index 00000000000..6e17c1a127f
--- /dev/null
+++ b/addons/im_livechat/security/ir.model.access.csv
@@ -0,0 +1,6 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_ls_chann1,im_livechat.channel,model_im_livechat_channel,,1,0,0,0
+access_ls_chann2,im_livechat.channel,model_im_livechat_channel,group_im_livechat,1,1,1,0
+access_ls_chann3,im_livechat.channel,model_im_livechat_channel,group_im_livechat_manager,1,1,1,1
+access_ls_message,im_livechat.im.message,im.model_im_message,portal.group_anonymous,0,0,0,0
+access_im_user,im_livechat.im.user,im.model_im_user,portal.group_anonymous,1,0,0,0
\ No newline at end of file
diff --git a/addons/im_livechat/static/ext/Makefile b/addons/im_livechat/static/ext/Makefile
new file mode 100644
index 00000000000..b73ca4f2b89
--- /dev/null
+++ b/addons/im_livechat/static/ext/Makefile
@@ -0,0 +1,3 @@
+
+static/js/livesupport_templates.js: static/js/livesupport_templates.html
+ python static/js/to_jsonp.py static/js/livesupport_templates.html oe_livesupport_templates_callback > static/js/livesupport_templates.js
\ No newline at end of file
diff --git a/addons/im_livechat/static/ext/static/audio/Ting.mp3 b/addons/im_livechat/static/ext/static/audio/Ting.mp3
new file mode 100644
index 00000000000..6fd090a89ce
Binary files /dev/null and b/addons/im_livechat/static/ext/static/audio/Ting.mp3 differ
diff --git a/addons/im_livechat/static/ext/static/audio/Ting.ogg b/addons/im_livechat/static/ext/static/audio/Ting.ogg
new file mode 100644
index 00000000000..8d17ea85bd3
Binary files /dev/null and b/addons/im_livechat/static/ext/static/audio/Ting.ogg differ
diff --git a/addons/im_livechat/static/ext/static/css/livesupport.css b/addons/im_livechat/static/ext/static/css/livesupport.css
new file mode 100644
index 00000000000..bfe26b5e838
--- /dev/null
+++ b/addons/im_livechat/static/ext/static/css/livesupport.css
@@ -0,0 +1,190 @@
+
+
+
+.openerp_style { /* base style of openerp */
+ font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
+ color: #4c4c4c;
+ font-size: 13px;
+ background: white;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
+}
+
+/* button */
+
+.oe_chat_button {
+ position: fixed;
+ bottom: 0px;
+ right: 6px;
+ display: inline-block;
+ min-width: 100px;
+ background-color: rgba(60, 60, 60, 0.6);
+ font-family: 'Lucida Grande', 'Lucida Sans Unicode', Arial, Verdana, sans-serif;
+ font-size: 14px;
+ font-weight: bold;
+ padding: 10px;
+ color: white;
+ text-shadow: rgb(59, 76, 88) 1px 1px 0px;
+ border: 1px solid rgb(80, 80, 80);
+ border-bottom: 0px;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ cursor: pointer;
+}
+
+/* conversations */
+
+.oe_im_chatview {
+ position: fixed;
+ overflow: hidden;
+ bottom: 42px;
+ margin-right: 6px;
+ background: rgba(60, 60, 60, 0.8);
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: 0 0 3px rgba(0,0,0,0.3), 0 2px 4px rgba(0,0,0,0.3);
+ -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
+ width: 240px;
+}
+.oe_im_chatview .oe_im_chatview_disconnected {
+ display:none;
+ z-index: 100;
+ width: 100%;
+ background: #E8EBEF;
+ padding: 5px;
+ font-size: 11px;
+ color: #999;
+ line-height: 14px;
+ height: 28px;
+ overflow: hidden;
+}
+.oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_disconnected {
+ display: block;
+}
+.oe_im_chatview .oe_im_chatview_header {
+ padding: 3px 6px 2px;
+ background: #DEDEDE;
+ background: -moz-linear-gradient(#FCFCFC, #DEDEDE);
+ background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE));
+ -moz-border-radius: 3px 3px 0 0;
+ -webkit-border-radius: 3px 3px 0 0;
+ border-radius: 3px 3px 0 0;
+ border-bottom: 1px solid #AEB9BD;
+ cursor: pointer;
+}
+.oe_im_chatview .oe_im_chatview_close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+ font-size: 18px;
+ line-height: 16px;
+ float: right;
+ font-weight: bold;
+ color: black;
+ text-shadow: 0 1px 0 white;
+ opacity: 0.2;
+}
+.oe_im_chatview .oe_im_chatview_content {
+ overflow: auto;
+ height: 287px;
+ width: 240px;
+}
+.oe_im_chatview.oe_im_chatview_disconnected_status .oe_im_chatview_content {
+ height: 249px;
+}
+.oe_im_chatview .oe_im_chatview_footer {
+ position: relative;
+ padding: 3px;
+ border-top: 1px solid #AEB9BD;
+ background: #DEDEDE;
+ background: -moz-linear-gradient(#FCFCFC, #DEDEDE);
+ background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE));
+ -moz-border-radius: 0 0 3px 3px;
+ -webkit-border-radius: 0 0 3px 3px;
+ border-radius: 0 0 3px 3px;
+}
+.oe_im_chatview .oe_im_chatview_input {
+ width: 222px;
+ font-family: Lato, Helvetica, sans-serif;
+ font-size: 13px;
+ color: #333;
+ padding: 1px 5px;
+ border: 1px solid #AEB9BD;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: inset 0 1px 4px rgba(0,0,0,0.2);
+ -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2);
+ box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2);
+}
+.oe_im_chatview .oe_im_chatview_bubble {
+ background: white;
+ position: relative;
+ padding: 3px;
+ margin: 3px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+.oe_im_chatview .oe_im_chatview_clip {
+ position: relative;
+ float: left;
+ width: 26px;
+ height: 26px;
+ margin-right: 4px;
+ -moz-box-shadow: 0 0 2px 1px rgba(0,0,0,0.25);
+ -webkit-box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25);
+ box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.25);
+}
+.oe_im_chatview .oe_im_chatview_avatar {
+ float: left;
+ width: 26px;
+ height: auto;
+ clip: rect(0, 26px, 26px, 0);
+ max-width: 100%;
+ width: auto 9;
+ height: auto;
+ vertical-align: middle;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+}
+.oe_im_chatview .oe_im_chatview_time {
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ margin: 3px;
+ text-align: right;
+ line-height: 13px;
+ font-size: 11px;
+ color: #999;
+ width: 60px;
+ overflow: hidden;
+}
+.oe_im_chatview .oe_im_chatview_from {
+ margin: 0 0 2px 0;
+ line-height: 14px;
+ font-weight: bold;
+ font-size: 12px;
+ width: 140px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ color: #3A87AD;
+}
+.oe_im_chatview .oe_im_chatview_bubble_list {
+}
+.oe_im_chatview .oe_im_chatview_bubble_item {
+ margin: 0 0 2px 30px;
+ line-height: 14px;
+ word-wrap: break-word;
+}
+
+.oe_im_chatview_online {
+ display: none;
+ margin-top: -4px;
+ width: 11px;
+ height: 11px;
+}
diff --git a/addons/im_livechat/static/ext/static/img/avatar/avatar.jpeg b/addons/im_livechat/static/ext/static/img/avatar/avatar.jpeg
new file mode 100644
index 00000000000..7168794022e
Binary files /dev/null and b/addons/im_livechat/static/ext/static/img/avatar/avatar.jpeg differ
diff --git a/addons/im_livechat/static/ext/static/img/button-gloss.png b/addons/im_livechat/static/ext/static/img/button-gloss.png
new file mode 100755
index 00000000000..6f3957702fe
Binary files /dev/null and b/addons/im_livechat/static/ext/static/img/button-gloss.png differ
diff --git a/addons/im_livechat/static/ext/static/img/glyphicons-halflings-white.png b/addons/im_livechat/static/ext/static/img/glyphicons-halflings-white.png
new file mode 100755
index 00000000000..3bf6484a29d
Binary files /dev/null and b/addons/im_livechat/static/ext/static/img/glyphicons-halflings-white.png differ
diff --git a/addons/im_livechat/static/ext/static/img/glyphicons-halflings.png b/addons/im_livechat/static/ext/static/img/glyphicons-halflings.png
new file mode 100755
index 00000000000..a9969993201
Binary files /dev/null and b/addons/im_livechat/static/ext/static/img/glyphicons-halflings.png differ
diff --git a/addons/im_livechat/static/ext/static/img/green.png b/addons/im_livechat/static/ext/static/img/green.png
new file mode 100644
index 00000000000..01fb373c251
Binary files /dev/null and b/addons/im_livechat/static/ext/static/img/green.png differ
diff --git a/addons/im_livechat/static/ext/static/img/logo.png b/addons/im_livechat/static/ext/static/img/logo.png
new file mode 100644
index 00000000000..aca5f4c60d8
Binary files /dev/null and b/addons/im_livechat/static/ext/static/img/logo.png differ
diff --git a/addons/im_livechat/static/ext/static/img/wood.png b/addons/im_livechat/static/ext/static/img/wood.png
new file mode 100644
index 00000000000..22f2450d3ad
Binary files /dev/null and b/addons/im_livechat/static/ext/static/img/wood.png differ
diff --git a/addons/im_livechat/static/ext/static/js/jquery.achtung.css b/addons/im_livechat/static/ext/static/js/jquery.achtung.css
new file mode 100644
index 00000000000..820ae3e9194
--- /dev/null
+++ b/addons/im_livechat/static/ext/static/js/jquery.achtung.css
@@ -0,0 +1,306 @@
+/**
+ * achtung 0.3.0
+ *
+ * Growl-like notifications for jQuery
+ *
+ * Copyright (c) 2009 Josh Varner
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Portions of this file are from the jQuery UI CSS framework.
+ *
+ * @license http://www.opensource.org/licenses/mit-license.php
+ * @author Josh Varner
+ */
+
+/* IE 6 doesn't support position: fixed */
+* html #achtung-overlay {
+ position:absolute;
+}
+
+/* IE6 includes padding in width */
+* html .achtung {
+ width: 280px;
+}
+
+#achtung-overlay {
+ overflow: hidden;
+ position: fixed;
+ top: 15px;
+ right: 15px;
+ width: 280px;
+ z-index:50;
+}
+
+.achtung {
+ display:none;
+ margin-bottom: 8px;
+ padding: 15px 15px;
+ background-color: #000;
+ color: white;
+ width: 250px;
+ font-weight: bold;
+ position:relative;
+ overflow: hidden;
+ -moz-box-shadow: #aaa 1px 1px 2px;
+ -webkit-box-shadow: #aaa 1px 1px 2px;
+ box-shadow: #aaa 1px 1px 2px;
+ -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px;
+ /* Note that if using show/hide animations, IE will lose
+ this setting */
+ opacity: .85;
+ filter:Alpha(Opacity=85);
+}
+
+/**
+ * This section from jQuery UI CSS framework
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Can (and should) be removed if you are already loading the jQuery UI CSS
+ * to reduce payload size.
+ */
+.ui-icon { display: block; overflow: hidden; background-repeat: no-repeat; }
+.ui-icon { width: 16px; height: 16px; }
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+.achtung .achtung-message-icon {
+ margin-top: 0px;
+ margin-left: -.5em;
+ margin-right: .5em;
+ float: left;
+ zoom: 1;
+}
+
+.achtung .ui-icon.achtung-close-button {
+ float: right;
+ margin-right: -8px;
+ margin-top: -12px;
+ cursor: pointer;
+ color: white;
+ text-align: right;
+}
+
+.achtung .ui-icon.achtung-close-button:after {
+ content: "x"
+}
+
+/* Slightly darker for these colors (readability) */
+.achtungSuccess, .achtungFail, .achtungWait {
+ /* Note that if using show/hide animations, IE will lose
+ this setting */
+ opacity: .93; filter:Alpha(Opacity=93);
+}
+
+.achtungSuccess {
+ background-color: #4DB559;
+}
+
+.achtungFail {
+ background-color: #D64450;
+}
+
+.achtungWait {
+ background-color: #658093;
+}
+
+.achtungSuccess .ui-icon.achtung-close-button,
+.achtungFail .ui-icon.achtung-close-button {
+}
+
+.achtungSuccess .ui-icon.achtung-close-button-hover,
+.achtungFail .ui-icon.achtung-close-button-hover {
+}
+
+.achtung .wait-icon {
+}
+
+.achtung .achtung-message {
+ display: inline;
+}
diff --git a/addons/im_livechat/static/ext/static/js/jquery.achtung.js b/addons/im_livechat/static/ext/static/js/jquery.achtung.js
new file mode 100644
index 00000000000..1aa69469c1d
--- /dev/null
+++ b/addons/im_livechat/static/ext/static/js/jquery.achtung.js
@@ -0,0 +1,273 @@
+/**
+ * achtung 0.3.0
+ *
+ * Growl-like notifications for jQuery
+ *
+ * Copyright (c) 2009 Josh Varner
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @license http://www.opensource.org/licenses/mit-license.php
+ * @author Josh Varner
+ */
+
+/*globals jQuery,clearTimeout,document,navigator,setTimeout
+*/
+(function($) {
+
+/**
+ * This is based on the jQuery UI $.widget code. I would have just made this
+ * a $.widget but I didn't want the jQuery UI dependency.
+ */
+$.fn.achtung = function(options)
+{
+ var isMethodCall = (typeof options === 'string'),
+ args = Array.prototype.slice.call(arguments, 0),
+ name = 'achtung';
+
+ // handle initialization and non-getter methods
+ return this.each(function() {
+ var instance = $.data(this, name);
+
+ // prevent calls to internal methods
+ if (isMethodCall && options.substring(0, 1) === '_') {
+ return this;
+ }
+
+ // constructor
+ (!instance && !isMethodCall &&
+ $.data(this, name, new $.achtung(this))._init(args));
+
+ // method call
+ (instance && isMethodCall && $.isFunction(instance[options]) &&
+ instance[options].apply(instance, args.slice(1)));
+ });
+};
+
+$.achtung = function(element)
+{
+ var args = Array.prototype.slice.call(arguments, 0), $el;
+
+ if (!element || !element.nodeType) {
+ $el = $('');
+ return $el.achtung.apply($el, args);
+ }
+
+ this.$container = $(element);
+};
+
+
+/**
+ * Static members
+ **/
+$.extend($.achtung, {
+ version: '0.3.0',
+ $overlay: false,
+ defaults: {
+ timeout: 10,
+ disableClose: false,
+ icon: false,
+ className: '',
+ animateClassSwitch: false,
+ showEffects: {'opacity':'toggle','height':'toggle'},
+ hideEffects: {'opacity':'toggle','height':'toggle'},
+ showEffectDuration: 500,
+ hideEffectDuration: 700
+ }
+});
+
+/**
+ * Non-static members
+ **/
+$.extend($.achtung.prototype, {
+ $container: false,
+ closeTimer: false,
+ options: {},
+
+ _init: function(args)
+ {
+ var o, self = this;
+
+ args = $.isArray(args) ? args : [];
+
+
+ args.unshift($.achtung.defaults);
+ args.unshift({});
+
+ o = this.options = $.extend.apply($, args);
+
+ if (!$.achtung.$overlay) {
+ $.achtung.$overlay = $('').appendTo(document.body);
+ }
+
+ if (!o.disableClose) {
+ $('')
+ .click(function () { self.close(); })
+ .hover(function () { $(this).addClass('achtung-close-button-hover'); },
+ function () { $(this).removeClass('achtung-close-button-hover'); })
+ .prependTo(this.$container);
+ }
+
+ this.changeIcon(o.icon, true);
+
+ if (o.message) {
+ this.$container.append($('' + o.message + ''));
+ }
+
+ (o.className && this.$container.addClass(o.className));
+ (o.css && this.$container.css(o.css));
+
+ this.$container
+ .addClass('achtung')
+ .appendTo($.achtung.$overlay);
+
+ if (o.showEffects) {
+ this.$container.toggle();
+ } else {
+ this.$container.show();
+ }
+
+ if (o.timeout > 0) {
+ this.timeout(o.timeout);
+ }
+ },
+
+ timeout: function(timeout)
+ {
+ var self = this;
+
+ if (this.closeTimer) {
+ clearTimeout(this.closeTimer);
+ }
+
+ this.closeTimer = setTimeout(function() { self.close(); }, timeout * 1000);
+ this.options.timeout = timeout;
+ },
+
+ /**
+ * Change the CSS class associated with this message, using
+ * a transition if available (not availble in Safari/Webkit).
+ * If no transition is available, the switch is immediate.
+ *
+ * #LATER Check if this has been corrected in Webkit or jQuery UI
+ * #TODO Make transition time configurable
+ * @param newClass string Name of new class to associate
+ */
+ changeClass: function(newClass)
+ {
+ var self = this;
+
+ if (this.options.className === newClass) {
+ return;
+ }
+
+ this.$container.queue(function() {
+ if (!self.options.animateClassSwitch ||
+ /webkit/.test(navigator.userAgent.toLowerCase()) ||
+ !$.isFunction($.fn.switchClass)) {
+ self.$container.removeClass(self.options.className);
+ self.$container.addClass(newClass);
+ } else {
+ self.$container.switchClass(self.options.className, newClass, 500);
+ }
+
+ self.options.className = newClass;
+ self.$container.dequeue();
+ });
+ },
+
+ changeIcon: function(newIcon, force)
+ {
+ var self = this;
+
+ if ((force !== true || newIcon === false) && this.options.icon === newIcon) {
+ return;
+ }
+
+ if (force || this.options.icon === false) {
+ this.$container.prepend($(''));
+ this.options.icon = newIcon;
+ return;
+ } else if (newIcon === false) {
+ this.$container.find('.achtung-message-icon').remove();
+ this.options.icon = false;
+ return;
+ }
+
+ this.$container.queue(function() {
+ var $span = $('.achtung-message-icon', self.$container);
+
+ if (!self.options.animateClassSwitch ||
+ /webkit/.test(navigator.userAgent.toLowerCase()) ||
+ !$.isFunction($.fn.switchClass)) {
+ $span.removeClass(self.options.icon);
+ $span.addClass(newIcon);
+ } else {
+ $span.switchClass(self.options.icon, newIcon, 500);
+ }
+
+ self.options.icon = newIcon;
+ self.$container.dequeue();
+ });
+ },
+
+
+ changeMessage: function(newMessage)
+ {
+ this.$container.queue(function() {
+ $('.achtung-message', $(this)).html(newMessage);
+ $(this).dequeue();
+ });
+ },
+
+
+ update: function(options)
+ {
+ (options.className && this.changeClass(options.className));
+ (options.css && this.$container.css(options.css));
+ (typeof(options.icon) !== 'undefined' && this.changeIcon(options.icon));
+ (options.message && this.changeMessage(options.message));
+ (options.timeout && this.timeout(options.timeout));
+ },
+
+ close: function()
+ {
+ var o = this.options, $container = this.$container;
+
+ if (o.hideEffects) {
+ this.$container.animate(o.hideEffects, o.hideEffectDuration);
+ } else {
+ this.$container.hide();
+ }
+
+ $container.queue(function() {
+ $container.removeData('achtung');
+ $container.remove();
+
+ if ($.achtung.$overlay && $.achtung.$overlay.is(':empty')) {
+ $.achtung.$overlay.remove();
+ $.achtung.$overlay = false;
+ }
+
+ $container.dequeue();
+ });
+ }
+});
+
+})(jQuery);
\ No newline at end of file
diff --git a/addons/im_livechat/static/ext/static/js/jquery.js b/addons/im_livechat/static/ext/static/js/jquery.js
new file mode 100644
index 00000000000..ded03845983
--- /dev/null
+++ b/addons/im_livechat/static/ext/static/js/jquery.js
@@ -0,0 +1,9555 @@
+/*!
+ * jQuery JavaScript Library v1.9.0
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-1-14
+ */
+(function( window, undefined ) {
+"use strict";
+var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+ location = window.location,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // [[Class]] -> type pairs
+ class2type = {},
+
+ // List of deleted data cache ids, so we can reuse them
+ core_deletedIds = [],
+
+ core_version = "1.9.0",
+
+ // Save a reference to some core methods
+ core_concat = core_deletedIds.concat,
+ core_push = core_deletedIds.push,
+ core_slice = core_deletedIds.slice,
+ core_indexOf = core_deletedIds.indexOf,
+ core_toString = class2type.toString,
+ core_hasOwn = class2type.hasOwnProperty,
+ core_trim = core_version.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
+
+ // Used for splitting on whitespace
+ core_rnotwhite = /\S+/g,
+
+ // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ DOMContentLoaded = function() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ } else if ( document.readyState === "complete" ) {
+ // we're here because readyState === "complete" in oldIE
+ // which is good enough for us to call the dom ready!
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+
+jQuery.fn = jQuery.prototype = {
+ // The current version of jQuery being used
+ jquery: core_version,
+
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+
+ // scripts is true for back-compat
+ jQuery.merge( this, jQuery.parseHTML(
+ match[1],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+ // Properties of context are called as methods if possible
+ if ( jQuery.isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+ ret.context = this.context;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ if ( obj == null ) {
+ return String( obj );
+ }
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ core_toString.call(obj) ] || "object" :
+ typeof obj;
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !core_hasOwn.call(obj, "constructor") &&
+ !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || core_hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // keepScripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, keepScripts ) {
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ keepScripts = context;
+ context = false;
+ }
+ context = context || document;
+
+ var parsed = rsingleTag.exec( data ),
+ scripts = !keepScripts && [];
+
+ // Single tag
+ if ( parsed ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts );
+ if ( scripts ) {
+ jQuery( scripts ).remove();
+ }
+ return jQuery.merge( [], parsed.childNodes );
+ },
+
+ parseJSON: function( data ) {
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ if ( data === null ) {
+ return data;
+ }
+
+ if ( typeof data === "string" ) {
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ if ( data ) {
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+ }
+ }
+ }
+
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && jQuery.trim( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var value,
+ i = 0,
+ length = obj.length,
+ isArray = isArraylike( obj );
+
+ if ( args ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
+ function( text ) {
+ return text == null ?
+ "" :
+ core_trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ core_push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( core_indexOf ) {
+ return core_indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value,
+ i = 0,
+ length = elems.length,
+ isArray = isArraylike( elems ),
+ ret = [];
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return core_concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ length = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < length; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
+ }
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+ var length = obj.length,
+ type = jQuery.type( obj );
+
+ if ( jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.nodeType === 1 && length ) {
+ return true;
+ }
+
+ return type === "array" || type !== "function" &&
+ ( length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj );
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ return jQuery.inArray( fn, list ) > -1;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ](function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ }
+ });
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ]
+ deferred[ tuple[0] ] = function() {
+ deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ return this;
+ };
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+jQuery.support = (function() {
+
+ var support, all, a, select, opt, input, fragment, eventName, isSupported, i,
+ div = document.createElement("div");
+
+ // Setup
+ div.setAttribute( "className", "t" );
+ div.innerHTML = "
a";
+
+ // Support tests won't run in some limited or non-browser environments
+ all = div.getElementsByTagName("*");
+ a = div.getElementsByTagName("a")[ 0 ];
+ if ( !all || !a || !all.length ) {
+ return {};
+ }
+
+ // First batch of tests
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ a.style.cssText = "top:1px;float:left;opacity:.5";
+ support = {
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: div.firstChild.nodeType === 3,
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: a.getAttribute("href") === "/a",
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.5/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
+ checkOn: !!input.value,
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Tests for enctype support on a form (#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>",
+
+ // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+ boxModel: document.compatMode === "CSS1Compat",
+
+ // Will be defined later
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ boxSizingReliable: true,
+ pixelPosition: false
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Support: IE<9
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ // Check if we can trust getAttribute("value")
+ input = document.createElement("input");
+ input.setAttribute( "value", "" );
+ support.input = input.getAttribute( "value" ) === "";
+
+ // Check if an input maintains its value after becoming a radio
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "checked", "t" );
+ input.setAttribute( "name", "t" );
+
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( input );
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE<9
+ // Opera does not clone events (and typeof div.attachEvent === undefined).
+ // IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
+ if ( div.attachEvent ) {
+ div.attachEvent( "onclick", function() {
+ support.noCloneEvent = false;
+ });
+
+ div.cloneNode( true ).click();
+ }
+
+ // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)
+ // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php
+ for ( i in { submit: true, change: true, focusin: true }) {
+ div.setAttribute( eventName = "on" + i, "t" );
+
+ support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false;
+ }
+
+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, marginDiv, tds,
+ divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
+
+ body.appendChild( container ).appendChild( div );
+
+ // Support: IE8
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ div.innerHTML = "
t
";
+ tds = div.getElementsByTagName("td");
+ tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Support: IE8
+ // Check if empty table cells still have offsetWidth/Height
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check box-sizing and margin behavior
+ div.innerHTML = "";
+ div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+ support.boxSizing = ( div.offsetWidth === 4 );
+ support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+ // Use window.getComputedStyle because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. (#3333)
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = div.appendChild( document.createElement("div") );
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Support: IE<8
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ div.innerHTML = "";
+ div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Support: IE6
+ // Check if elements with layout shrink-wrap their children
+ div.style.display = "block";
+ div.innerHTML = "";
+ div.firstChild.style.width = "5px";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+ // Prevent IE 6 from affecting layout for positioned elements #11048
+ // Prevent IE from shrinking the body in IE 7 mode #12869
+ body.style.zoom = 1;
+ }
+
+ body.removeChild( container );
+
+ // Null elements to avoid leaks in IE
+ container = div = tds = marginDiv = null;
+ });
+
+ // Null elements to avoid leaks in IE
+ all = select = fragment = opt = a = input = null;
+
+ return support;
+})();
+
+var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+function internalData( elem, name, data, pvt /* Internal Use Only */ ){
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+}
+
+function internalRemoveData( elem, name, pvt /* For internal use only */ ){
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split(" ");
+ }
+ }
+ } else {
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = name.concat( jQuery.map( name, jQuery.camelCase ) );
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+}
+
+jQuery.extend({
+ cache: {},
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return internalData( elem, name, data, false );
+ },
+
+ removeData: function( elem, name ) {
+ return internalRemoveData( elem, name, false );
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return internalData( elem, name, data, true );
+ },
+
+ _removeData: function( elem, name ) {
+ return internalRemoveData( elem, name, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ // nodes accept data unless otherwise specified; rejection can be conditional
+ return !noData || noData !== true && elem.getAttribute("classid") === noData;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var attrs, name,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attrs = elem.attributes;
+ for ( ; i < attrs.length; i++ ) {
+ name = attrs[i].name;
+
+ if ( !name.indexOf( "data-" ) ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ // Try to fetch any internally stored data first
+ return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
+ }
+
+ this.each(function() {
+ jQuery.data( this, key, value );
+ });
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ hooks.cur = fn;
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery._removeData( elem, type + "queue" );
+ jQuery._removeData( elem, key );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var nodeHook, boolHook,
+ rclass = /[\t\r\n]/g,
+ rreturn = /\r/g,
+ rfocusable = /^(?:input|select|textarea|button|object)$/i,
+ rclickable = /^(?:a|area)$/i,
+ rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,
+ ruseDefault = /^(?:checked|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute,
+ getSetInput = jQuery.support.input;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classes, elem, cur, clazz, j,
+ i = 0,
+ len = this.length,
+ proceed = typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call( this, j, this.className ) );
+ });
+ }
+
+ if ( proceed ) {
+ // The disjunction here is for better compressibility (see removeClass)
+ classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ " "
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+ cur += clazz + " ";
+ }
+ }
+ elem.className = jQuery.trim( cur );
+
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classes, elem, cur, clazz, j,
+ i = 0,
+ len = this.length,
+ proceed = arguments.length === 0 || typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call( this, j, this.className ) );
+ });
+ }
+ if ( proceed ) {
+ classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ // This expression is here for better compressibility (see addClass)
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ ""
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ // Remove *all* instances
+ while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+ cur = cur.replace( " " + clazz + " ", " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( cur ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.match( core_rnotwhite ) || [];
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ // Toggle whole class name
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // If the element has a class name or if we're passed "false",
+ // then remove the whole classname (if there was one, the above saved it).
+ // Otherwise bring back whatever was previously saved (if anything),
+ // falling back to the empty string if nothing was stored.
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val,
+ self = jQuery(this);
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // oldIE doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attr: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+
+ } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ // In IE9+, Flash objects don't have .getAttribute (#12945)
+ // Support: IE9+
+ if ( typeof elem.getAttribute !== "undefined" ) {
+ ret = elem.getAttribute( name );
+ }
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var name, propName,
+ i = 0,
+ attrNames = value && value.match( core_rnotwhite );
+
+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( (name = attrNames[i++]) ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // Boolean attributes get special treatment (#10870)
+ if ( rboolean.test( name ) ) {
+ // Set corresponding property to false for boolean attributes
+ // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8
+ if ( !getSetAttribute && ruseDefault.test( name ) ) {
+ elem[ jQuery.camelCase( "default-" + name ) ] =
+ elem[ propName ] = false;
+ } else {
+ elem[ propName ] = false;
+ }
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ } else {
+ jQuery.attr( elem, name, "" );
+ }
+
+ elem.removeAttribute( getSetAttribute ? name : propName );
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to default in case type is set after value during creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ }
+ },
+
+ 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( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ var
+ // Use .prop to determine if this attribute is understood as boolean
+ prop = jQuery.prop( elem, name ),
+
+ // Fetch it accordingly
+ attr = typeof prop === "boolean" && elem.getAttribute( name ),
+ detail = typeof prop === "boolean" ?
+
+ getSetInput && getSetAttribute ?
+ attr != null :
+ // oldIE fabricates an empty string for missing boolean attributes
+ // and conflates checked/selected into attroperties
+ ruseDefault.test( name ) ?
+ elem[ jQuery.camelCase( "default-" + name ) ] :
+ !!attr :
+
+ // fetch an attribute node for properties not recognized as boolean
+ elem.getAttributeNode( name );
+
+ return detail && detail.value !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
+ // IE<8 needs the *property* name
+ elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
+
+ // Use defaultChecked and defaultSelected for oldIE
+ } else {
+ elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
+ }
+
+ return name;
+ }
+};
+
+// fix oldIE value attroperty
+if ( !getSetInput || !getSetAttribute ) {
+ jQuery.attrHooks.value = {
+ get: function( elem, name ) {
+ var ret = elem.getAttributeNode( name );
+ return jQuery.nodeName( elem, "input" ) ?
+
+ // Ignore the value *property* by using defaultValue
+ elem.defaultValue :
+
+ ret && ret.specified ? ret.value : undefined;
+ },
+ set: function( elem, value, name ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ // Does not return so that setAttribute is also used
+ elem.defaultValue = value;
+ } else {
+ // Use nodeHook if defined (#1954); otherwise setAttribute is fine
+ return nodeHook && nodeHook.set( elem, value, name );
+ }
+ }
+ };
+}
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret = elem.getAttributeNode( name );
+ return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ?
+ ret.value :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ elem.setAttributeNode(
+ (ret = elem.ownerDocument.createAttribute( name ))
+ );
+ }
+
+ ret.value = value += "";
+
+ // Break association with cloned elements by also using setAttribute (#9646)
+ return name === "value" || value === elem.getAttribute( name ) ?
+ value :
+ undefined;
+ }
+ };
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ nodeHook.set( elem, value === "" ? false : value, name );
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+}
+
+
+// Some attributes require a special call on IE
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret == null ? undefined : ret;
+ }
+ });
+ });
+
+ // href/src property should get the full normalized URL (#10299/#12915)
+ jQuery.each([ "href", "src" ], function( i, name ) {
+ jQuery.propHooks[ name ] = {
+ get: function( elem ) {
+ return elem.getAttribute( name, 4 );
+ }
+ };
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Note: IE uppercases css property names, but if we were to .toLowerCase()
+ // .cssText, that would destroy case senstitivity in URL's, like in "background"
+ return elem.style.cssText || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = value + "" );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+var rformElems = /^(?:input|select|textarea)$/i,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ elemData = elem.nodeType !== 3 && elem.nodeType !== 8 && jQuery._data( elem );
+
+ if ( !elemData ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !(events = elemData.events) ) {
+ events = elemData.events = {};
+ }
+ if ( !(eventHandle = elemData.handle) ) {
+ eventHandle = elemData.handle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = ( types || "" ).match( core_rnotwhite ) || [""];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !(handlers = events[ type ]) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( core_rnotwhite ) || [""];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery._removeData( elem, "events" );
+ }
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+
+ var i, cur, tmp, bubbleType, ontype, handle, special,
+ eventPath = [ elem || document ],
+ type = event.type || event,
+ namespaces = event.namespace ? event.namespace.split(".") : [];
+
+ cur = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf(".") >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf(":") < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ event.isTrigger = true;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === (elem.ownerDocument || document) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ try {
+ elem[ type ]();
+ } catch ( e ) {
+ // IE<9 dies on focus/blur to hidden element (#1486,#12518)
+ // only reproducible on winXP IE8 native, not IE9 in IE8 mode
+ }
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event );
+
+ var i, j, ret, matched, handleObj,
+ handlerQueue = [],
+ args = core_slice.call( arguments ),
+ handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+ // Triggered event must either 1) have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, matches, sel, handleObj,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ // Black-hole SVG