plugin account, groupblog: user can erase all their microblogs at once

This commit is contained in:
souliane 2014-03-27 02:24:20 +01:00
commit f6673e0725
2 changed files with 216 additions and 17 deletions

View file

@ -34,6 +34,7 @@ PLUGIN_INFO = {
"type": "MISC",
"protocols": [],
"dependencies": [],
"recommendations": ['GROUPBLOG'],
"main": "MiscAccount",
"handler": "no",
"description": _(u"""SàT account creation""")
@ -133,6 +134,13 @@ class MiscAccount(object):
self.__account_cb_id = host.registerCallback(self._accountDialogCb, with_data=True)
self.__delete_account_id = host.registerCallback(self.__deleteAccountCb, with_data=True)
def deleteBlogCallback(posts, comments):
return lambda data, profile: self.__deleteBlogPostsCb(posts, comments, data, profile)
self.__delete_posts_comments_id = host.registerCallback(deleteBlogCallback(True, True), with_data=True)
self.__delete_posts_id = host.registerCallback(deleteBlogCallback(True, False), with_data=True)
self.__delete_comments_id = host.registerCallback(deleteBlogCallback(False, True), with_data=True)
def getConfig(self, name):
return self.host.memory.getConfig(CONFIG_SECTION, name) or default_conf[name]
@ -239,6 +247,7 @@ SàT team""" % {'login': login, 'password': password, 'jid': "%s@%s" % (login, s
"""
form_ui = xml_tools.XMLUI("form", "tabs", title=D_("Manage your XMPP account"), submit_id=self.__account_cb_id)
tab_container = form_ui.current_container
tab_container.addTab("update", D_("Change your password"), container=xml_tools.PairsContainer)
form_ui.addLabel(D_("Current password"))
form_ui.addPassword("current_passwd", value="")
@ -246,6 +255,16 @@ SàT team""" % {'login': login, 'password': password, 'jid': "%s@%s" % (login, s
form_ui.addPassword("new_passwd1", value="")
form_ui.addLabel(D_("New password (again)"))
form_ui.addPassword("new_passwd2", value="")
if 'GROUPBLOG' in self.host.plugins:
tab_container.addTab("delete_posts", D_("Delete your posts"), container=xml_tools.PairsContainer)
form_ui.addLabel(D_("Current password"))
form_ui.addPassword("delete_posts_passwd", value="")
form_ui.addLabel(D_("Delete all your posts and their comments"))
form_ui.addBool("delete_posts_checkbox", "false")
form_ui.addLabel(D_("Delete all your comments on other's posts"))
form_ui.addBool("delete_comments_checkbox", "false")
tab_container.addTab("delete", D_("Delete your account"), container=xml_tools.PairsContainer)
form_ui.addLabel(D_("Current password"))
form_ui.addPassword("delete_passwd", value="")
@ -261,7 +280,7 @@ SàT team""" % {'login': login, 'password': password, 'jid': "%s@%s" % (login, s
password = self.host.memory.getParamA("Password", "Connection", profile_key=profile)
def error_ui():
error_ui = xml_tools.XMLUI("popup", title="Error")
error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
error_ui.addText(D_("Passwords don't match!"))
return defer.succeed({'xmlui': error_ui.toXml()})
@ -273,6 +292,18 @@ SàT team""" % {'login': login, 'password': password, 'jid': "%s@%s" % (login, s
return self.__deleteAccount(profile)
return error_ui()
# check for blog posts deletion
if 'GROUPBLOG' in self.host.plugins:
delete_posts_passwd = data[xml_tools.SAT_FORM_PREFIX + 'delete_posts_passwd']
delete_posts_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_posts_checkbox']
delete_comments_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_comments_checkbox']
posts = delete_posts_checkbox == 'true'
comments = delete_comments_checkbox == 'true'
if posts or comments:
if password == delete_posts_passwd:
return self.__deleteBlogPosts(posts, comments, profile)
return error_ui()
# check for password modification
current_passwd = data[xml_tools.SAT_FORM_PREFIX + 'current_passwd']
new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd1']
@ -291,12 +322,12 @@ SàT team""" % {'login': login, 'password': password, 'jid': "%s@%s" % (login, s
"""
def passwordChanged(result):
self.host.memory.setParam("Password", password, "Connection", profile_key=profile)
confirm_ui = xml_tools.XMLUI("popup", title="Confirmation")
confirm_ui = xml_tools.XMLUI("popup", title=D_("Confirmation"))
confirm_ui.addText(D_("Your password has been changed."))
return defer.succeed({'xmlui': confirm_ui.toXml()})
def errback(failure):
error_ui = xml_tools.XMLUI("popup", title="Error")
error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
error_ui.addText(D_("Your password could not be changed: %s") % failure.getErrorMessage())
return defer.succeed({'xmlui': error_ui.toXml()})
@ -308,8 +339,10 @@ SàT team""" % {'login': login, 'password': password, 'jid': "%s@%s" % (login, s
"""Ask for a confirmation before deleting the XMPP account and SàT profile
@param profile
"""
form_ui = xml_tools.XMLUI("form", title=D_("Delete your account ?"), submit_id=self.__delete_account_id)
form_ui = xml_tools.XMLUI("form", title=D_("Delete your account?"), submit_id=self.__delete_account_id)
form_ui.addText(D_("If you confirm this dialog, you will be disconnected and then your XMPP account AND your SàT profile will both be DELETED."))
target = D_('contact list, messages history, blog posts and comments' if 'GROUPBLOG' in self.host.plugins else D_('contact list and messages history'))
form_ui.addText(D_("All your data stored on %(server)s, including your %(target)s will be erased.") % {'server': self._getNewAccountDomain(), 'target': target})
form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
return {'xmlui': form_ui.toXml()}
@ -327,15 +360,77 @@ SàT team""" % {'login': login, 'password': password, 'jid': "%s@%s" % (login, s
for jid_ in self.host.memory.getWaitingSub(profile): # delete waiting subscriptions
self.host.memory.delWaitingSub(jid_)
self.host.disconnect(profile)
self.host.memory.asyncDeleteProfile(profile, force=True)
delete_profile = lambda: self.host.memory.asyncDeleteProfile(profile, force=True)
if 'GROUPBLOG' in self.host.plugins:
d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsAndComments(profile_key=profile)
d.addCallback(lambda dummy: delete_profile())
else:
delete_profile()
return defer.succeed({})
def errback(failure):
error_ui = xml_tools.XMLUI("popup", title="Error")
error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
error_ui.addText(D_("Your XMPP account could not be deleted: %s") % failure.getErrorMessage())
return defer.succeed({'xmlui': error_ui.toXml()})
d = ProsodyRegisterProtocol.prosodyctl(self, 'deluser', profile=profile)
d.addCallbacks(userDeleted, errback)
return d
def __deleteBlogPosts(self, posts, comments, profile):
"""Ask for a confirmation before deleting the blog posts
@param posts: delete all posts of the user (and their comments)
@param comments: delete all the comments of the user on other's posts
@param data
@param profile
"""
if posts:
if comments: # delete everything
form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog posts and comments?"), submit_id=self.__delete_posts_comments_id)
form_ui.addText(D_("If you confirm this dialog, all the (micro-)blog data you submitted will be erased."))
form_ui.addText(D_("These are the public and private posts and comments you sent to any group."))
form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
else: # delete only the posts
form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog posts?"), submit_id=self.__delete_posts_id)
form_ui.addText(D_("If you confirm this dialog, all the public and private posts you sent to any group will be erased."))
form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
elif comments: # delete only the comments
form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog comments?"), submit_id=self.__delete_comments_id)
form_ui.addText(D_("If you confirm this dialog, all the public and private comments you made on other people's posts will be erased."))
form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
return {'xmlui': form_ui.toXml()}
def __deleteBlogPostsCb(self, posts, comments, data, profile):
"""Actually delete the XMPP account and SàT profile
@param posts: delete all posts of the user (and their comments)
@param comments: delete all the comments of the user on other's posts
@param profile
"""
if posts:
if comments:
target = D_('blog posts and comments')
d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsAndComments(profile_key=profile)
else:
target = D_('blog posts')
d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogs(profile_key=profile)
elif comments:
target = D_('comments')
d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsComments(profile_key=profile)
def deleted(result):
ui = xml_tools.XMLUI("popup", title=D_("Deletion confirmation"))
# TODO: change the message when delete/retract notifications are done with XEP-0060
ui.addText(D_("Your %(target)s have been deleted.") % {'target': target})
ui.addText(D_("Known issue of the demo version: you need to refresh the page to make the deleted posts actually disappear."))
return defer.succeed({'xmlui': ui.toXml()})
def errback(failure):
error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
error_ui.addText(D_("Your %(target)s could not be deleted: %(message)s") % {'target': target, 'message': failure.getErrorMessage()})
return defer.succeed({'xmlui': error_ui.toXml()})
d.addCallbacks(deleted, errback)
return d

View file

@ -22,7 +22,7 @@ from sat.core.constants import Const as C
from logging import debug, info, warning, error
from twisted.internet import defer
from twisted.words.protocols.jabber import jid
from twisted.words.xish.domish import Element
from twisted.words.xish.domish import Element, generateElementsNamed
from sat.core import exceptions
from wokkel import disco, data_form, iwokkel
from zope.interface import implements
@ -53,7 +53,7 @@ ACCESS_TYPE_MAP = { 'PUBLIC': 'open',
PLUGIN_INFO = {
"name": "Group blogging throught collections",
"import_name": "groupblog",
"import_name": "GROUPBLOG",
"type": "MISC",
"protocols": [],
"dependencies": ["XEP-0277"],
@ -415,8 +415,8 @@ class GroupBlog(object):
def notify(d):
# TODO: this works only on the same host, and notifications for item deletion should be
# implemented according to http://xmpp.org/extensions/xep-0060.html#publisher-delete-success-notify
# instead. The notification mechanisms implemented in sat_pubsub and wokkel seem not compatible,
# see wokkel.pubsub.PubSubClient._onEvent_items and sat_pubsub.backend._doNotifyRetraction
# instead. The notification mechanism implemented in sat_pubsub and wokkel have apriori
# a problem with retrieving the subscriptions, or something else.
service, node, item_id = pub_data
publisher = self.host.getJidNStream(profile_key)[0]
profile = self.host.memory.getProfileName(profile_key)
@ -572,7 +572,6 @@ class GroupBlog(object):
d.addCallback(get_comments)
return d
def getLastGroupBlogs(self, pub_jid_s, max_items=10, profile_key=C.PROF_KEY_NONE):
"""Get the last published microblogs
@param pub_jid_s: jid of the publisher
@ -665,7 +664,7 @@ class GroupBlog(object):
"""
def sendResult(result):
"""send result of DeferredList (list of microblogs to the calling method"""
"""send result of DeferredList (dict of jid => microblogs) to the calling method"""
ret = {}
@ -693,7 +692,6 @@ class GroupBlog(object):
mblogs = []
for jid_ in jids:
d = self.host.plugins["XEP-0060"].getItems(client.item_access_pubsub, self.getNodeName(jid_),
max_items=max_items, profile_key=profile_key)
@ -773,6 +771,109 @@ class GroupBlog(object):
return self._initialise(profile_key).addCallback(initialised)
#TODO: we need to use the server corresponding the the host of the jid
def deleteAllGroupBlogsAndComments(self, profile_key=C.PROF_KEY_NONE):
"""Delete absolutely all the microblog data that the user has posted"""
calls = [self.deleteAllGroupBlogs(profile_key), self.deleteAllGroupBlogsComments(profile_key)]
return defer.DeferredList(calls)
def deleteAllGroupBlogs(self, profile_key=C.PROF_KEY_NONE):
"""Delete all the main items and their comments that the user has posted
"""
def initialised(result):
profile, client = result
service = client.item_access_pubsub
jid_ = client.jid
main_node = self.getNodeName(jid_)
d = self.host.plugins["XEP-0060"].deleteNode(service, main_node, profile_key=profile)
d.addCallback(lambda dummy: info(_("All microblog's main items from %s have been deleted!") % jid_.userhost()))
d.addErrback(lambda failure: error(_("Deletion of node %(node)s failed: %(message)s") %
{'node': main_node, 'message': failure.getErrorMessage()}))
return d
return self._initialise(profile_key).addCallback(initialised)
def deleteAllGroupBlogsComments(self, profile_key=C.PROF_KEY_NONE):
"""Delete all the comments that the user posted on other's main items.
We avoid the conversions from item to microblog data as we only need
to retrieve some attributes, no need to convert text syntax...
"""
def initialised(result):
"""Get all the main items from our contact list
@return: a DeferredList
"""
profile, client = result
service = client.item_access_pubsub
jids = [contact.jid.userhostJID() for contact in client.roster.getItems()]
blogs = []
for jid_ in jids:
main_node = self.getNodeName(jid_)
d = self.host.plugins["XEP-0060"].getItems(service, main_node, profile_key=profile)
d.addCallback(getComments, client)
d.addErrback(lambda failure, main_node: error(_("Retrieval of items for node %(node)s failed: %(message)s") %
{'node': main_node, 'message': failure.getErrorMessage()}), main_node)
blogs.append(d)
return defer.DeferredList(blogs)
def getComments(items, client):
"""Get all the comments for a list of items
@param items: a list of main items for one user
@param client: the client of the user
@return: a DeferredList
"""
comments = []
for item in items:
try:
entry = generateElementsNamed(item.elements(), 'entry').next()
link = generateElementsNamed(entry.elements(), 'link').next()
except StopIteration:
continue
href = link.getAttribute('href')
service, node = self.host.plugins['XEP-0277'].parseCommentUrl(href)
d = self.host.plugins["XEP-0060"].getItems(service, node, profile_key=profile_key)
d.addCallback(lambda items, service, node: (service, node, items), service, node)
d.addErrback(lambda failure, node: error(_("Retrieval of comments for node %(node)s failed: %(message)s") %
{'node': node, 'message': failure.getErrorMessage()}), node)
comments.append(d)
dlist = defer.DeferredList(comments)
dlist.addCallback(deleteComments, client)
return dlist
def deleteComments(result, client):
"""Delete all the comments of the user that are found in result
@param result: a list of couple (success, value) with success a
boolean and value a tuple (service as JID, node_id, comment_items)
@param client: the client of the user
@return: a DeferredList with the deletions result
"""
user_jid_s = client.jid.userhost()
for (success, value) in result:
if not success:
continue
service, node_id, comment_items = value
item_ids = []
for comment_item in comment_items: # for all the comments on one post
try:
entry = generateElementsNamed(comment_item.elements(), 'entry').next()
author = generateElementsNamed(entry.elements(), 'author').next()
name = generateElementsNamed(author.elements(), 'name').next()
except StopIteration:
continue
if name.children[0] == user_jid_s:
item_ids.append(comment_item.getAttribute('id'))
deletions = []
if item_ids: # remove the comments of the user on the given post
d = self.host.plugins['XEP-0060'].retractItems(service, node_id, item_ids, profile_key=profile_key)
d.addCallback(lambda dummy, node_id: debug(_('Comments of user %(user)s in node %(node)s have been retracted') %
{'user': user_jid_s, 'node': node_id}), node_id)
d.addErrback(lambda failure, node_id: error(_("Retraction of comments from %(user)s in node %(node)s failed: %(message)s") %
{'user': user_jid_s, 'node': node_id, 'message': failure.getErrorMessage()}), node_id)
deletions.append(d)
return defer.DeferredList(deletions)
return self._initialise(profile_key).addCallback(initialised)
class GroupBlog_handler(XMPPHandler):
implements(iwokkel.IDisco)