f2ad703af84daa1274df207d06c8cb1ced748533
[odoo/odoo.git] / addons / im_livechat / im_livechat.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import random
23 import openerp
24 import json
25 import openerp.addons.im_chat.im_chat
26 import datetime
27
28 import re
29 import json
30
31 from openerp.osv import osv, fields
32 from openerp import tools
33 from openerp import http
34 from openerp.http import request
35 from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
36
37
38 class im_livechat_channel(osv.Model):
39     _name = 'im_livechat.channel'
40
41     def _get_default_image(self, cr, uid, context=None):
42         image_path = openerp.modules.get_module_resource('im_livechat', 'static/src/img', 'default.png')
43         return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
44     def _get_image(self, cr, uid, ids, name, args, context=None):
45         result = dict.fromkeys(ids, False)
46         for obj in self.browse(cr, uid, ids, context=context):
47             result[obj.id] = tools.image_get_resized_images(obj.image)
48         return result
49     def _set_image(self, cr, uid, id, name, value, args, context=None):
50         return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
51
52     def _are_you_inside(self, cr, uid, ids, name, arg, context=None):
53         res = {}
54         for record in self.browse(cr, uid, ids, context=context):
55             res[record.id] = False
56             for user in record.user_ids:
57                 if user.id == uid:
58                     res[record.id] = True
59                     break
60         return res
61
62     def _script_external(self, cr, uid, ids, name, arg, context=None):
63         values = {
64             "url": self.pool.get('ir.config_parameter').get_param(cr, openerp.SUPERUSER_ID, 'web.base.url'),
65             "dbname":cr.dbname
66         }
67         res = {}
68         for record in self.browse(cr, uid, ids, context=context):
69             values["channel"] = record.id
70             res[record.id] = self.pool['ir.ui.view'].render(cr, uid, 'im_livechat.external_loader', values, context=context)
71         return res
72
73     def _script_internal(self, cr, uid, ids, name, arg, context=None):
74         values = {
75             "url": self.pool.get('ir.config_parameter').get_param(cr, openerp.SUPERUSER_ID, 'web.base.url'),
76             "dbname":cr.dbname
77         }
78         res = {}
79         for record in self.browse(cr, uid, ids, context=context):
80             values["channel"] = record.id
81             res[record.id] = self.pool['ir.ui.view'].render(cr, uid, 'im_livechat.internal_loader', values, context=context)
82         return res
83
84     def _web_page(self, cr, uid, ids, name, arg, context=None):
85         res = {}
86         for record in self.browse(cr, uid, ids, context=context):
87             res[record.id] = self.pool.get('ir.config_parameter').get_param(cr, openerp.SUPERUSER_ID, 'web.base.url') + \
88                 "/im_livechat/support/%s/%i" % (cr.dbname, record.id)
89         return res
90
91     def _compute_nbr_session(self, cr, uid, ids, name, arg, context=None):
92         res = {}
93         for record in self.browse(cr, uid, ids, context=context):
94             res[record.id] = len(record.session_ids)
95         return res
96
97
98     _columns = {
99         'name': fields.char(string="Channel Name", size=200, required=True),
100         'user_ids': fields.many2many('res.users', 'im_livechat_channel_im_user', 'channel_id', 'user_id', string="Users"),
101         'are_you_inside': fields.function(_are_you_inside, type='boolean', string='Are you inside the matrix?', store=False),
102         'script_internal': fields.function(_script_internal, type='text', string='Script (internal)', store=False),
103         'script_external': fields.function(_script_external, type='text', string='Script (external)', store=False),
104         'web_page': fields.function(_web_page, type='char', string='Web Page', store=False),
105         'button_text': fields.char(string="Text of the Button"),
106         'input_placeholder': fields.char(string="Chat Input Placeholder"),
107         'default_message': fields.char(string="Welcome Message", help="This is an automated 'welcome' message that your visitor will see when they initiate a new chat session."),
108         # image: all image fields are base64 encoded and PIL-supported
109         'image': fields.binary("Photo",
110             help="This field holds the image used as photo for the group, limited to 1024x1024px."),
111         'image_medium': fields.function(_get_image, fnct_inv=_set_image,
112             string="Medium-sized photo", type="binary", multi="_get_image",
113             store={
114                 'im_livechat.channel': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
115             },
116             help="Medium-sized photo of the group. It is automatically "\
117                  "resized as a 128x128px image, with aspect ratio preserved. "\
118                  "Use this field in form views or some kanban views."),
119         'image_small': fields.function(_get_image, fnct_inv=_set_image,
120             string="Small-sized photo", type="binary", multi="_get_image",
121             store={
122                 'im_livechat.channel': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
123             },
124             help="Small-sized photo of the group. It is automatically "\
125                  "resized as a 64x64px image, with aspect ratio preserved. "\
126                  "Use this field anywhere a small image is required."),
127         'session_ids' : fields.one2many('im_chat.session', 'channel_id', 'Sessions'),
128         'nbr_session' : fields.function(_compute_nbr_session, type='integer', string='Number of session', store=False),
129         'rule_ids': fields.one2many('im_livechat.channel.rule','channel_id','Rules'),
130     }
131
132     def _default_user_ids(self, cr, uid, context=None):
133         return [(6, 0, [uid])]
134
135     _defaults = {
136         'button_text': "Have a Question? Chat with us.",
137         'input_placeholder': "How may I help you?",
138         'default_message': '',
139         'user_ids': _default_user_ids,
140         'image': _get_default_image,
141     }
142
143     def get_available_users(self, cr, uid, channel_id, context=None):
144         """ get available user of a given channel """
145         channel = self.browse(cr, uid, channel_id, context=context)
146         users = []
147         for user_id in channel.user_ids:
148             if (user_id.im_status == 'online'):
149                 users.append(user_id)
150         return users
151
152     def get_channel_session(self, cr, uid, channel_id, anonymous_name, context=None):
153         """ return a session given a channel : create on with a registered user, or return false otherwise """
154         # get the avalable user of the channel
155         users = self.get_available_users(cr, uid, channel_id, context=context)
156         if len(users) == 0:
157             return False
158         user_id = random.choice(users).id
159         # create the session, and add the link with the given channel
160         Session = self.pool["im_chat.session"]
161         newid = Session.create(cr, uid, {'user_ids': [(4, user_id)], 'channel_id': channel_id, 'anonymous_name' : anonymous_name}, context=context)
162         return Session.session_info(cr, uid, [newid], context=context)
163
164     def test_channel(self, cr, uid, channel, context=None):
165         if not channel:
166             return {}
167         return {
168             'url': self.browse(cr, uid, channel[0], context=context or {}).web_page,
169             'type': 'ir.actions.act_url'
170         }
171
172     def get_info_for_chat_src(self, cr, uid, channel, context=None):
173         url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
174         chan = self.browse(cr, uid, channel, context=context)
175         return {
176             "url": url,
177             'buttonText': chan.button_text,
178             'inputPlaceholder': chan.input_placeholder,
179             'defaultMessage': chan.default_message,
180             "channelName": chan.name,
181         }
182
183     def join(self, cr, uid, ids, context=None):
184         self.write(cr, uid, ids, {'user_ids': [(4, uid)]})
185         return True
186
187     def quit(self, cr, uid, ids, context=None):
188         self.write(cr, uid, ids, {'user_ids': [(3, uid)]})
189         return True
190
191
192
193 class im_livechat_channel_rule(osv.Model):
194     _name = 'im_livechat.channel.rule'
195
196     _columns = {
197         'regex_url' : fields.char('URL Regex', help="Regular expression identifying the web page on which the rules will be applied."),
198         'action' : fields.selection([('display_button', 'Display the button'),('auto_popup','Auto popup'), ('hide_button', 'Hide the button')], 'Action', size=32, required=True,
199                                  help="Select 'Display the button' to simply display the chat button on the pages."\
200                                  " Select 'Auto popup' for to display the button, and automatically open the conversation window."\
201                                  " Select 'Hide the button' to hide the chat button on the pages."),
202         'auto_popup_timer' : fields.integer('Auto popup timer', help="Delay (in seconds) to automatically open the converssation window. Note : the selected action must be 'Auto popup', otherwise this parameter will not be take into account."),
203         'channel_id': fields.many2one('im_livechat.channel', 'Channel', help="The channel of the rule"),
204         'country_ids': fields.many2many('res.country', 'im_livechat_channel_country_rel', 'channel_id', 'country_id', 'Country', help="The actual rule will match only for this country. So if you set select 'Belgium' and 'France' and you set the action to 'Hide Buttun', this 2 country will not be see the support button for the specified URL. This feature requires GeoIP installed on your server."),
205         'sequence' : fields.integer('Matching order', help="Given the order to find a matching rule. If 2 rules are matching for the given url/country, the one with the lowest sequence will be chosen.")
206     }
207
208     _defaults = {
209         'auto_popup_timer': 0,
210         'action' : 'display_button',
211         'sequence' : 10,
212     }
213
214     _order = "sequence asc"
215
216     def match_rule(self, cr, uid, channel_id, url, country_id=False, context=None):
217         """ determine if a rule of the given channel match with the given url """
218         domain = [('channel_id', '=', channel_id)]
219         if country_id: # don't include the country in the research if geoIP is not installed
220             domain.append(('country_ids', 'in', country_id))
221         rule_ids = self.search(cr, uid, domain, context=context)
222         for rule in self.browse(cr, uid, rule_ids, context=context):
223             if re.search(rule.regex_url, url):
224                 return rule
225         return False
226
227
228 class im_chat_session(osv.Model):
229
230     _inherit = 'im_chat.session'
231
232     def _get_fullname(self, cr, uid, ids, fields, arg, context=None):
233         """ built the complete name of the session """
234         result = {}
235         sessions = self.browse(cr, uid, ids, context=context)
236         for session in sessions:
237             names = []
238             for user in session.user_ids:
239                 names.append(user.name)
240             if session.anonymous_name:
241                 names.append(session.anonymous_name)
242             result[session.id] = ', '.join(names)
243         return result
244
245     _columns = {
246         'anonymous_name' : fields.char('Anonymous Name'),
247         'create_date': fields.datetime('Create Date', required=True, select=True),
248         'channel_id': fields.many2one("im_livechat.channel", "Channel"),
249         'fullname' : fields.function(_get_fullname, type="char", string="Complete name"),
250         'feedback_rating': fields.selection([('10','Good'),('5','Ok'),('1','Bad')], 'Grade', help='Feedback from user (Bad, Ok or Good)'),
251         'feedback_reason': fields.text('Reason', help="Reason explaining the rating."),
252     }
253
254     def is_in_session(self, cr, uid, uuid, user_id, context=None):
255         """ return if the given user_id is in the session """
256         sids = self.search(cr, uid, [('uuid', '=', uuid)], context=context, limit=1)
257         for session in self.browse(cr, uid, sids, context=context):
258             if session.anonymous_name and user_id == openerp.SUPERUSER_ID:
259                 return True
260             else:
261                 return super(im_chat_session, self).is_in_session(cr, uid, uuid, user_id, context=context)
262         return False
263
264     def users_infos(self, cr, uid, ids, context=None):
265         """ add the anonymous user in the user of the session """
266         for session in self.browse(cr, uid, ids, context=context):
267             users_infos = super(im_chat_session, self).users_infos(cr, uid, ids, context=context)
268             if session.anonymous_name:
269                 users_infos.append({'id' : False, 'name' : session.anonymous_name, 'im_status' : 'online'})
270             return users_infos
271
272     def quit_user(self, cr, uid, uuid, context=None):
273         """ action of leaving a given session """
274         sids = self.search(cr, uid, [('uuid', '=', uuid)], context=context, limit=1)
275         for session in self.browse(cr, openerp.SUPERUSER_ID, sids, context=context):
276             if session.anonymous_name:
277                 # an identified user can leave an anonymous session if there is still another idenfied user in it
278                 if uid and uid in [u.id for u in session.user_ids] and len(session.user_ids) > 1:
279                     self.remove_user(cr, uid, session.id, context=context)
280                     return True
281                 return False
282             else:
283                 return super(im_chat_session, self).quit_user(cr, uid, session.id, context=context)
284
285 class LiveChatController(http.Controller):
286
287     @http.route('/im_livechat/support/<string:dbname>/<int:channel_id>', type='http', auth='none')
288     def support_page(self, dbname, channel_id, **kwargs):
289         registry, cr, uid, context = openerp.modules.registry.RegistryManager.get(dbname), request.cr, openerp.SUPERUSER_ID, request.context
290         info = registry.get('im_livechat.channel').get_info_for_chat_src(cr, uid, channel_id)
291         info["dbname"] = dbname
292         info["channel"] = channel_id
293         info["channel_name"] = registry.get('im_livechat.channel').read(cr, uid, channel_id, ['name'], context=context)["name"]
294         return request.render('im_livechat.support_page', info)
295
296     @http.route('/im_livechat/loader/<string:dbname>/<int:channel_id>', type='http', auth='none')
297     def loader(self, dbname, channel_id, **kwargs):
298         registry, cr, uid, context = openerp.modules.registry.RegistryManager.get(dbname), request.cr, openerp.SUPERUSER_ID, request.context
299         info = registry.get('im_livechat.channel').get_info_for_chat_src(cr, uid, channel_id)
300         info["dbname"] = dbname
301         info["channel"] = channel_id
302         info["username"] = kwargs.get("username", "Visitor")
303         # find the country from the request
304         country_id = False
305         country_code = request.session.geoip and request.session.geoip.get('country_name', False) or False
306         if country_code:
307             country_ids = registry.get('res.country').search(cr, uid, [('code', '=', country_code)], context=context)
308             if country_ids:
309                 country_id = country_ids[0]
310         # extract url
311         url = request.httprequest.headers['Referer'] or request.httprequest.base_url
312         # find the match rule for the given country and url
313         rule = registry.get('im_livechat.channel.rule').match_rule(cr, uid, channel_id, url, country_id, context=context)
314         if rule:
315             if rule.action == 'hide_button':
316                 # don't return the initialization script, since its blocked (in the country)
317                 return
318             rule_data = {
319                 'action' : rule.action,
320                 'auto_popup_timer' : rule.auto_popup_timer,
321                 'regex_url' : rule.regex_url,
322             }
323         info['rule'] = json.dumps(rule and rule_data or False)
324         return request.render('im_livechat.loader', info)
325
326     @http.route('/im_livechat/get_session', type="json", auth="none")
327     def get_session(self, channel_id, anonymous_name, **kwargs):
328         cr, uid, context, db = request.cr, request.uid or openerp.SUPERUSER_ID, request.context, request.db
329         reg = openerp.modules.registry.RegistryManager.get(db)
330         # if geoip, add the country name to the anonymous name
331         if request.session.geoip:
332             anonymous_name = anonymous_name + " ("+request.session.geoip.get('country_name', "")+")"
333         return reg.get("im_livechat.channel").get_channel_session(cr, uid, channel_id, anonymous_name, context=context)
334
335     @http.route('/im_livechat/available', type='json', auth="none")
336     def available(self, db, channel):
337         cr, uid, context, db = request.cr, request.uid or openerp.SUPERUSER_ID, request.context, request.db
338         reg = openerp.modules.registry.RegistryManager.get(db)
339         with reg.cursor() as cr:
340             return len(reg.get('im_livechat.channel').get_available_users(cr, uid, channel)) > 0
341
342
343     @http.route('/im_livechat/feedback', type='json', auth="none")
344     def feedback(self, uuid, rating, reason=None):
345         cr, uid, context, db = request.cr, request.uid or openerp.SUPERUSER_ID, request.context, request.db
346         registry = openerp.modules.registry.RegistryManager.get(db)
347         Session = registry['im_chat.session']
348         session_ids = Session.search(cr, uid, [('uuid','=',uuid)], context=context)
349         Session.write(cr, uid, session_ids, {'feedback_rating' : str(rating), 'feedback_reason' : reason}, context=context)
350
351