[IMP] barcodes: use builtin python sets when it makes sense
[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_blocked_country_rel', 'channel_id', 'country_id', 'Blocked Country', help="For this country list, the channel will not be available on the web page matched with the Regular Expression. This feature requires GeoIP installed on your server."),
205     }
206
207     _defaults = {
208         'auto_popup_timer': 0,
209         'action' : 'display_button',
210     }
211
212     def match_rule(self, cr, uid, channel_id, word, context=None):
213         """ determine if a rule of the given channel match with the given word """
214         rule_ids = self.search(cr, uid, [('channel_id', '=', channel_id)], context=context)
215         for rule in self.browse(cr, uid, rule_ids, context=context):
216             if re.search(rule.regex_url, word):
217                 return rule
218         return False
219
220
221 class im_chat_session(osv.Model):
222
223     _inherit = 'im_chat.session'
224
225     def _get_fullname(self, cr, uid, ids, fields, arg, context=None):
226         """ built the complete name of the session """
227         result = {}
228         sessions = self.browse(cr, uid, ids, context=context)
229         for session in sessions:
230             names = []
231             for user in session.user_ids:
232                 names.append(user.name)
233             if session.anonymous_name:
234                 names.append(session.anonymous_name)
235             result[session.id] = ', '.join(names)
236         return result
237
238     _columns = {
239         'anonymous_name' : fields.char('Anonymous Name'),
240         'create_date': fields.datetime('Create Date', required=True, select=True),
241         'channel_id': fields.many2one("im_livechat.channel", "Channel"),
242         'fullname' : fields.function(_get_fullname, type="char", string="Complete name"),
243         'feedback_rating': fields.selection([('10','Good'),('5','Ok'),('1','Bad')], 'Grade', help='Feedback from user (Bad, Ok or Good)'),
244         'feedback_reason': fields.text('Reason', help="Reason explaining the rating."),
245     }
246
247     def is_in_session(self, cr, uid, uuid, user_id, context=None):
248         """ return if the given user_id is in the session """
249         sids = self.search(cr, uid, [('uuid', '=', uuid)], context=context, limit=1)
250         for session in self.browse(cr, uid, sids, context=context):
251             if session.anonymous_name and user_id == openerp.SUPERUSER_ID:
252                 return True
253             else:
254                 return super(im_chat_session, self).is_in_session(cr, uid, uuid, user_id, context=context)
255         return False
256
257     def users_infos(self, cr, uid, ids, context=None):
258         """ add the anonymous user in the user of the session """
259         for session in self.browse(cr, uid, ids, context=context):
260             users_infos = super(im_chat_session, self).users_infos(cr, uid, ids, context=context)
261             if session.anonymous_name:
262                 users_infos.append({'id' : False, 'name' : session.anonymous_name, 'im_status' : 'online'})
263             return users_infos
264
265     def quit_user(self, cr, uid, uuid, context=None):
266         """ action of leaving a given session """
267         sids = self.search(cr, uid, [('uuid', '=', uuid)], context=context, limit=1)
268         for session in self.browse(cr, openerp.SUPERUSER_ID, sids, context=context):
269             if session.anonymous_name:
270                 # an identified user can leave an anonymous session if there is still another idenfied user in it
271                 if uid and uid in [u.id for u in session.user_ids] and len(session.user_ids) > 1:
272                     self.remove_user(cr, uid, session.id, context=context)
273                     return True
274                 return False
275             else:
276                 return super(im_chat_session, self).quit_user(cr, uid, session.id, context=context)
277
278 class LiveChatController(http.Controller):
279
280     @http.route('/im_livechat/support/<string:dbname>/<int:channel_id>', type='http', auth='none')
281     def support_page(self, dbname, channel_id, **kwargs):
282         registry, cr, uid, context = openerp.modules.registry.RegistryManager.get(dbname), request.cr, openerp.SUPERUSER_ID, request.context
283         info = registry.get('im_livechat.channel').get_info_for_chat_src(cr, uid, channel_id)
284         info["dbname"] = dbname
285         info["channel"] = channel_id
286         info["channel_name"] = registry.get('im_livechat.channel').read(cr, uid, channel_id, ['name'], context=context)["name"]
287         return request.render('im_livechat.support_page', info)
288
289     @http.route('/im_livechat/loader/<string:dbname>/<int:channel_id>', type='http', auth='none')
290     def loader(self, dbname, channel_id, **kwargs):
291         registry, cr, uid, context = openerp.modules.registry.RegistryManager.get(dbname), request.cr, openerp.SUPERUSER_ID, request.context
292         info = registry.get('im_livechat.channel').get_info_for_chat_src(cr, uid, channel_id)
293         info["dbname"] = dbname
294         info["channel"] = channel_id
295         info["username"] = kwargs.get("username", "Visitor")
296         url = request.httprequest.headers['Referer'] or request.httprequest.base_url
297         rule = registry.get('im_livechat.channel.rule').match_rule(cr, uid, channel_id, url, context=context)
298         if rule:
299             if request.session.geoip and request.session.geoip.get('country_name', "") in [c.code for c in rule.country_ids]:
300                 # don't return the initialization script, since its blocked in the country
301                 return
302             if rule.action == 'hide_button':
303                 return
304             rule_data = {
305                 'action' : rule.action,
306                 'auto_popup_timer' : rule.auto_popup_timer,
307                 'regex_url' : rule.regex_url,
308             }
309         info['rule'] = json.dumps(rule and rule_data or False)
310         return request.render('im_livechat.loader', info)
311
312     @http.route('/im_livechat/get_session', type="json", auth="none")
313     def get_session(self, channel_id, anonymous_name, **kwargs):
314         cr, uid, context, db = request.cr, request.uid or openerp.SUPERUSER_ID, request.context, request.db
315         reg = openerp.modules.registry.RegistryManager.get(db)
316         # if geoip, add the country name to the anonymous name
317         if request.session.geoip:
318             anonymous_name = anonymous_name + " ("+request.session.geoip.get('country_name', "")+")"
319         return reg.get("im_livechat.channel").get_channel_session(cr, uid, channel_id, anonymous_name, context=context)
320
321     @http.route('/im_livechat/available', type='json', auth="none")
322     def available(self, db, channel):
323         cr, uid, context, db = request.cr, request.uid or openerp.SUPERUSER_ID, request.context, request.db
324         reg = openerp.modules.registry.RegistryManager.get(db)
325         with reg.cursor() as cr:
326             return len(reg.get('im_livechat.channel').get_available_users(cr, uid, channel)) > 0
327
328
329     @http.route('/im_livechat/feedback', type='json', auth="none")
330     def feedback(self, uuid, rating, reason=None):
331         cr, uid, context, db = request.cr, request.uid or openerp.SUPERUSER_ID, request.context, request.db
332         registry = openerp.modules.registry.RegistryManager.get(db)
333         Session = registry['im_chat.session']
334         session_ids = Session.search(cr, uid, [('uuid','=',uuid)], context=context)
335         Session.write(cr, uid, session_ids, {'feedback_rating' : str(rating), 'feedback_reason' : reason}, context=context)
336
337