1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
25 import openerp.addons.im_chat.im_chat
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
38 class im_livechat_channel(osv.Model):
39 _name = 'im_livechat.channel'
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)
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)
52 def _are_you_inside(self, cr, uid, ids, name, arg, context=None):
54 for record in self.browse(cr, uid, ids, context=context):
55 res[record.id] = False
56 for user in record.user_ids:
62 def _script_external(self, cr, uid, ids, name, arg, context=None):
64 "url": self.pool.get('ir.config_parameter').get_param(cr, openerp.SUPERUSER_ID, 'web.base.url'),
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)
73 def _script_internal(self, cr, uid, ids, name, arg, context=None):
75 "url": self.pool.get('ir.config_parameter').get_param(cr, openerp.SUPERUSER_ID, 'web.base.url'),
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)
84 def _web_page(self, cr, uid, ids, name, arg, context=None):
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)
91 def _compute_nbr_session(self, cr, uid, ids, name, arg, context=None):
93 for record in self.browse(cr, uid, ids, context=context):
94 res[record.id] = len(record.session_ids)
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",
114 'im_livechat.channel': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
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",
122 'im_livechat.channel': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
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'),
132 def _default_user_ids(self, cr, uid, context=None):
133 return [(6, 0, [uid])]
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,
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)
147 for user_id in channel.user_ids:
148 if (user_id.im_status == 'online'):
149 users.append(user_id)
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)
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)
164 def test_channel(self, cr, uid, channel, context=None):
168 'url': self.browse(cr, uid, channel[0], context=context or {}).web_page,
169 'type': 'ir.actions.act_url'
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)
177 'buttonText': chan.button_text,
178 'inputPlaceholder': chan.input_placeholder,
179 'defaultMessage': chan.default_message,
180 "channelName": chan.name,
183 def join(self, cr, uid, ids, context=None):
184 self.write(cr, uid, ids, {'user_ids': [(4, uid)]})
187 def quit(self, cr, uid, ids, context=None):
188 self.write(cr, uid, ids, {'user_ids': [(3, uid)]})
193 class im_livechat_channel_rule(osv.Model):
194 _name = 'im_livechat.channel.rule'
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.")
209 'auto_popup_timer': 0,
210 'action' : 'display_button',
214 _order = "sequence asc"
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):
228 class im_chat_session(osv.Model):
230 _inherit = 'im_chat.session'
232 def _get_fullname(self, cr, uid, ids, fields, arg, context=None):
233 """ built the complete name of the session """
235 sessions = self.browse(cr, uid, ids, context=context)
236 for session in sessions:
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)
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."),
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:
261 return super(im_chat_session, self).is_in_session(cr, uid, uuid, user_id, context=context)
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'})
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)
283 return super(im_chat_session, self).quit_user(cr, uid, session.id, context=context)
285 class LiveChatController(http.Controller):
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)
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
305 country_code = request.session.geoip and request.session.geoip.get('country_name', False) or False
307 country_ids = registry.get('res.country').search(cr, uid, [('code', '=', country_code)], context=context)
309 country_id = country_ids[0]
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)
315 if rule.action == 'hide_button':
316 # don't return the initialization script, since its blocked (in the country)
319 'action' : rule.action,
320 'auto_popup_timer' : rule.auto_popup_timer,
321 'regex_url' : rule.regex_url,
323 info['rule'] = json.dumps(rule and rule_data or False)
324 return request.render('im_livechat.loader', info)
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)
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
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)