def convert_answer_to_comment(self): """ Tools to convert an answer (forum.post) to a comment (mail.message). The original post is unlinked and a new comment is posted on the question using the post create_uid as the comment's author. """ self.ensure_one() if not self.parent_id: return self.env['mail.message'] # karma-based action check: use the post field that computed own/all value if not self.can_comment_convert: raise KarmaError(_('%d karma required to convert an answer to a comment.') % self.karma_comment_convert) # post the message question = self.parent_id self_sudo = self.sudo() values = { 'author_id': self_sudo.create_uid.partner_id.id, # use sudo here because of access to res.users model 'email_from': self_sudo.create_uid.email_formatted, # use sudo here because of access to res.users model 'body': tools.html_sanitize(self.content, sanitize_attributes=True, strip_style=True, strip_classes=True), 'message_type': 'comment', 'subtype': 'mail.mt_comment', 'date': self.create_date, } new_message = question.with_context(mail_create_nosubscribe=True).message_post(**values) # unlink the original answer, using SUPERUSER_ID to avoid karma issues self.sudo().unlink() return new_message
def convert_comment_to_answer(self, message_id, default=None): """ Tool to convert a comment (mail.message) into an answer (forum.post). The original comment is unlinked and a new answer from the comment's author is created. Nothing is done if the comment's author already answered the question. """ comment = self.env['mail.message'].sudo().browse(message_id) post = self.browse(comment.res_id) if not comment.author_id or not comment.author_id.user_ids: # only comment posted by users can be converted return False # karma-based action check: must check the message's author to know if own / all karma_convert = comment.author_id.id == self.env.user.partner_id.id and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all can_convert = self.env.user.karma >= karma_convert if not can_convert: raise KarmaError('Not enough karma to convert a comment to an answer') # check the message's author has not already an answer question = post.parent_id if post.parent_id else post post_create_uid = comment.author_id.user_ids[0] if any(answer.create_uid.id == post_create_uid.id for answer in question.child_ids): return False # create the new post post_values = { 'forum_id': question.forum_id.id, 'content': comment.body, 'parent_id': question.id, } # done with the author user to have create_uid correctly set new_post = self.sudo(post_create_uid.id).create(post_values) # delete comment comment.unlink() return new_post
def get_mail_message_access(self, res_ids, operation, model_name=None): # XDO FIXME: to be correctly fixed with new get_mail_message_access and filter access rule if operation in ('write', 'unlink') and (not model_name or model_name == 'forum.post'): # Make sure only author or moderator can edit/delete messages for post in self.browse(res_ids): if not post.can_edit: raise KarmaError(_('%d karma required to edit a post.') % post.karma_edit) return super(Post, self).get_mail_message_access(res_ids, operation, model_name=model_name)
def unlink(self): if any(not post.can_unlink for post in self): raise KarmaError('Not enough karma to unlink a post') # if unlinking an answer with accepted answer: remove provided karma for post in self: if post.is_correct: post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1) self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1) return super(Post, self).unlink()
def unlink(self): for post in self: if not post.can_unlink: raise KarmaError(_('%d karma required to unlink a post.') % post.karma_unlink) # if unlinking an answer with accepted answer: remove provided karma for post in self: if post.is_correct: post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1) self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1) return super(Post, self).unlink()
def unlink_comment(self, message_id): user = self.env.user comment = self.env['mail.message'].sudo().browse(message_id) if not comment.model == 'forum.post' or not comment.res_id == self.id: return False # karma-based action check: must check the message's author to know if own or all karma_unlink = comment.author_id.id == user.partner_id.id and self.forum_id.karma_comment_unlink_own or self.forum_id.karma_comment_unlink_all can_unlink = user.karma >= karma_unlink if not can_unlink: raise KarmaError(_('%d karma required to unlink a comment.') % karma_unlink) return comment.unlink()
def create(self, vals): if 'content' in vals and vals.get('forum_id'): vals['content'] = self._update_content(vals['content'], vals['forum_id']) post = super(Post, self.with_context(mail_create_nolog=True)).create(vals) # deleted or closed questions if post.parent_id and (post.parent_id.state == 'close' or post.parent_id.active is False): raise UserError(_('Posting answer on a [Deleted] or [Closed] question is not possible.')) # karma-based access if not post.parent_id and not post.can_ask: raise KarmaError(_('%d karma required to create a new question.') % post.forum_id.karma_ask) elif post.parent_id and not post.can_answer: raise KarmaError(_('%d karma required to answer a question.') % post.forum_id.karma_answer) if not post.parent_id and not post.can_post: post.sudo().state = 'pending' # add karma for posting new questions if not post.parent_id and post.state == 'active': self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new) post.post_notification() return post
def _update_content(self, content, forum_id): forum = self.env['forum.forum'].browse(forum_id) if content and self.env.user.karma < forum.karma_dofollow: for match in re.findall(r'<a\s.*href=".*?">', content): match = re.escape(match) # replace parenthesis or special char in regex content = re.sub(match, match[:3] + 'rel="nofollow" ' + match[3:], content) if self.env.user.karma <= forum.karma_editor: filter_regexp = r'(<img.*?>)|(<a[^>]*?href[^>]*?>)|(<[a-z|A-Z]+[^>]*style\s*=\s*[\'"][^\'"]*\s*background[^:]*:[^url;]*url)' content_match = re.search(filter_regexp, content, re.I) if content_match: raise KarmaError(_('%d karma required to post an image or link.') % forum.karma_editor) return content
def flag(self): if not self.can_flag: raise KarmaError('Not enough karma to flag a post') if(self.state == 'flagged'): return {'error': 'post_already_flagged'} elif(self.state == 'active'): self.write({ 'state': 'flagged', 'flag_user_id': self.env.user.id, }) return self.can_moderate and {'success': 'post_flagged_moderator'} or {'success': 'post_flagged_non_moderator'} else: return {'error': 'post_non_flaggable'}
def validate(self): for post in self: if not post.can_moderate: raise KarmaError(_('%d karma required to validate a post.') % post.forum_id.karma_moderate) # if state == pending, no karma previously added for the new question if post.state == 'pending': post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_new) post.write({ 'state': 'active', 'active': True, 'moderator_id': self.env.user.id, }) post.post_notification() return True
def flag(self): if not self.can_flag: raise KarmaError(_('%d karma required to flag a post.') % self.forum_id.karma_flag) if(self.state == 'flagged'): return {'error': 'post_already_flagged'} elif(self.state == 'active'): self.write({ 'state': 'flagged', 'flag_user_id': self.env.user.id, }) return self.can_moderate and {'success': 'post_flagged_moderator'} or {'success': 'post_flagged_non_moderator'} else: return {'error': 'post_non_flaggable'}
def message_post(self, parent_id=False, subtype=None, **kwargs): """ Temporary workaround to avoid spam. If someone replies on a channel through the 'Presentation Published' email, it should be considered as a note as we don't want all channel followers to be notified of this answer. """ self.ensure_one() if kwargs.get('message_type') == 'comment' and not self.can_review: raise KarmaError(_('Not enough karma to review')) if parent_id: parent_message = self.env['mail.message'].sudo().browse(parent_id) if parent_message.subtype_id and parent_message.subtype_id == self.env.ref('website_slides.mt_channel_slide_published'): if kwargs.get('subtype_id'): kwargs['subtype_id'] = False subtype = 'mail.mt_note' return super(Channel, self).message_post(parent_id=parent_id, subtype=subtype, **kwargs)
def validate(self): if not self.can_moderate: raise KarmaError('Not enough karma to validate a post') # if state == pending, no karma previously added for the new question if self.state == 'pending': self.create_uid.sudo().add_karma(self.forum_id.karma_gen_question_new) self.write({ 'state': 'active', 'active': True, 'moderator_id': self.env.user.id, }) self.post_notification() return True
def mark_as_offensive(self, reason_id): if not self.can_moderate: raise KarmaError(_('%d karma required to mark a post as offensive.') % self.forum_id.karma_moderate) # remove some karma _logger.info('Downvoting user <%s> for posting spam/offensive contents', self.create_uid) self.create_uid.sudo().add_karma(self.forum_id.karma_gen_answer_flagged) self.write({ 'state': 'offensive', 'moderator_id': self.env.user.id, 'closed_date': datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT), 'closed_reason_id': reason_id, 'active': False, }) return True
def mark_as_offensive(self, reason_id): for post in self: if not post.can_moderate: raise KarmaError(_('%d karma required to mark a post as offensive.') % post.forum_id.karma_moderate) # remove some karma _logger.info('Downvoting user <%s> for posting spam/offensive contents', post.create_uid) post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_flagged) # TODO: potential bottleneck, could be done in batch post.write({ 'state': 'offensive', 'moderator_id': self.env.user.id, 'closed_date': fields.Datetime.now(), 'closed_reason_id': reason_id, 'active': False, }) return True
def write(self, vals): trusted_keys = ['active', 'is_correct', 'tag_ids'] # fields where security is checked manually if 'content' in vals: vals['content'] = self._update_content(vals['content'], self.forum_id.id) tag_ids = False if 'tag_ids' in vals: tag_ids = set(tag.get('id') for tag in self.resolve_2many_commands('tag_ids', vals['tag_ids'])) for post in self: if 'state' in vals: if vals['state'] in ['active', 'close']: if not post.can_close: raise KarmaError(_('%d karma required to close or reopen a post.') % post.karma_close) trusted_keys += ['state', 'closed_uid', 'closed_date', 'closed_reason_id'] elif vals['state'] == 'flagged': if not post.can_flag: raise KarmaError(_('%d karma required to flag a post.') % post.forum_id.karma_flag) trusted_keys += ['state', 'flag_user_id'] if 'active' in vals: if not post.can_unlink: raise KarmaError(_('%d karma required to delete or reactivate a post.') % post.karma_unlink) if 'is_correct' in vals: if not post.can_accept: raise KarmaError(_('%d karma required to accept or refuse an answer.') % post.karma_accept) # update karma except for self-acceptance mult = 1 if vals['is_correct'] else -1 if vals['is_correct'] != post.is_correct and post.create_uid.id != self._uid: post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * mult) self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accept * mult) if tag_ids: if set(post.tag_ids.ids) != tag_ids and self.env.user.karma < post.forum_id.karma_edit_retag: raise KarmaError(_('%d karma required to retag.') % post.forum_id.karma_edit_retag) if any(key not in trusted_keys for key in vals) and not post.can_edit: raise KarmaError(_('%d karma required to edit a post.') % post.karma_edit) res = super(Post, self).write(vals) # if post content modify, notify followers if 'content' in vals or 'name' in vals: for post in self: if post.parent_id: body, subtype = _('Answer Edited'), 'website_forum.mt_answer_edit' obj_id = post.parent_id else: body, subtype = _('Question Edited'), 'website_forum.mt_question_edit' obj_id = post obj_id.message_post(body=body, subtype=subtype) if 'active' in vals: answers = self.env['forum.post'].with_context(active_test=False).search([('parent_id', 'in', self.ids)]) if answers: answers.write({'active': vals['active']}) return res
def message_post(self, message_type='notification', **kwargs): if self.ids and message_type == 'comment': # user comments have a restriction on karma # add followers of comments on the parent post if self.parent_id: partner_ids = kwargs.get('partner_ids', []) comment_subtype = self.sudo().env.ref('mail.mt_comment') question_followers = self.env['mail.followers'].sudo().search([ ('res_model', '=', self._name), ('res_id', '=', self.parent_id.id), ('partner_id', '!=', False), ]).filtered(lambda fol: comment_subtype in fol.subtype_ids).mapped('partner_id') partner_ids += question_followers.ids kwargs['partner_ids'] = partner_ids self.ensure_one() if not self.can_comment: raise KarmaError(_('%d karma required to comment.') % self.karma_comment) if not kwargs.get('record_name') and self.parent_id: kwargs['record_name'] = self.parent_id.name return super(Post, self).message_post(message_type=message_type, **kwargs)
def flag(self): res = [] for post in self: if not post.can_flag: raise KarmaError(_('%d karma required to flag a post.') % post.forum_id.karma_flag) if post.state == 'flagged': res.append({'error': 'post_already_flagged'}) elif post.state == 'active': # TODO: potential performance bottleneck, can be batched post.write({ 'state': 'flagged', 'flag_user_id': self.env.user.id, }) res.append( post.can_moderate and {'success': 'post_flagged_moderator'} or {'success': 'post_flagged_non_moderator'} ) else: res.append({'error': 'post_non_flaggable'}) return res
def refuse(self): for post in self: if not post.can_moderate: raise KarmaError(_('%d karma required to refuse a post.') % post.forum_id.karma_moderate) post.moderator_id = self.env.user return True
def test_karma_error_http(self, **kwargs): raise KarmaError("This is a karma http test")
def _check_karma_rights(self, upvote=None): # karma check if upvote and not self.post_id.can_upvote: raise KarmaError('You don\'t have enough karma to upvote.') elif not upvote and not self.post_id.can_downvote: raise KarmaError('You don\'t have enough karma to downvote.')
def check_mail_message_access(self, res_ids, operation, model_name=None): if operation in ('write', 'unlink') and (not model_name or model_name == 'forum.post'): # Make sure only author or moderator can edit/delete messages if any(not post.can_edit for post in self.browse(res_ids)): raise KarmaError('Not enough karma to edit a post.') return super(Post, self).check_mail_message_access(res_ids, operation, model_name=model_name)
def _check_karma_rights(self, upvote=None): # karma check if upvote and not self.post_id.can_upvote: raise KarmaError(_('%d karma required to upvote.') % self.post_id.forum_id.karma_upvote) elif not upvote and not self.post_id.can_downvote: raise KarmaError(_('%d karma required to downvote.') % self.post_id.forum_id.karma_downvote)
def refuse(self): if not self.can_moderate: raise KarmaError(_('%d karma required to refuse a post.') % self.forum_id.karma_moderate) self.moderator_id = self.env.user return True
def create(self, vals): forum = self.env['forum.forum'].browse(vals.get('forum_id')) if self.env.user.karma < forum.karma_tag_create: raise KarmaError(_('%d karma required to create a new Tag.') % forum.karma_tag_create) return super(Tags, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(vals)
def refuse(self): if not self.can_moderate: raise KarmaError(_('Not enough karma to refuse a post')) self.moderator_id = self.env.user return True
def message_post(self, message_type='notification', **kwargs): self.ensure_one() if message_type == 'comment' and not self.channel_id.can_comment: # user comments have a restriction on karma raise KarmaError(_('Not enough karma to comment')) return super(Slide, self).message_post(message_type=message_type, **kwargs)
def test_karma_error_rpc(self, **kwargs): raise KarmaError("This is a karma rpc test")