summaryrefslogtreecommitdiff
path: root/slixmpp/plugins/xep_0060/stanza
diff options
context:
space:
mode:
Diffstat (limited to 'slixmpp/plugins/xep_0060/stanza')
-rw-r--r--slixmpp/plugins/xep_0060/stanza/__init__.py12
-rw-r--r--slixmpp/plugins/xep_0060/stanza/base.py29
-rw-r--r--slixmpp/plugins/xep_0060/stanza/pubsub.py272
-rw-r--r--slixmpp/plugins/xep_0060/stanza/pubsub_errors.py86
-rw-r--r--slixmpp/plugins/xep_0060/stanza/pubsub_event.py151
-rw-r--r--slixmpp/plugins/xep_0060/stanza/pubsub_owner.py134
6 files changed, 684 insertions, 0 deletions
diff --git a/slixmpp/plugins/xep_0060/stanza/__init__.py b/slixmpp/plugins/xep_0060/stanza/__init__.py
new file mode 100644
index 00000000..31c4ac68
--- /dev/null
+++ b/slixmpp/plugins/xep_0060/stanza/__init__.py
@@ -0,0 +1,12 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.plugins.xep_0060.stanza.pubsub import *
+from slixmpp.plugins.xep_0060.stanza.pubsub_owner import *
+from slixmpp.plugins.xep_0060.stanza.pubsub_event import *
+from slixmpp.plugins.xep_0060.stanza.pubsub_errors import *
diff --git a/slixmpp/plugins/xep_0060/stanza/base.py b/slixmpp/plugins/xep_0060/stanza/base.py
new file mode 100644
index 00000000..b8f3d6cc
--- /dev/null
+++ b/slixmpp/plugins/xep_0060/stanza/base.py
@@ -0,0 +1,29 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.xmlstream import ET
+
+
+class OptionalSetting(object):
+
+ interfaces = set(('required',))
+
+ def set_required(self, value):
+ if value in (True, 'true', 'True', '1'):
+ self.xml.append(ET.Element("{%s}required" % self.namespace))
+ elif self['required']:
+ self.del_required()
+
+ def get_required(self):
+ required = self.xml.find("{%s}required" % self.namespace)
+ return required is not None
+
+ def del_required(self):
+ required = self.xml.find("{%s}required" % self.namespace)
+ if required is not None:
+ self.xml.remove(required)
diff --git a/slixmpp/plugins/xep_0060/stanza/pubsub.py b/slixmpp/plugins/xep_0060/stanza/pubsub.py
new file mode 100644
index 00000000..b4293918
--- /dev/null
+++ b/slixmpp/plugins/xep_0060/stanza/pubsub.py
@@ -0,0 +1,272 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp import Iq, Message
+from slixmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
+from slixmpp.plugins import xep_0004
+from slixmpp.plugins.xep_0060.stanza.base import OptionalSetting
+
+
+class Pubsub(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'pubsub'
+ plugin_attrib = name
+ interfaces = set(tuple())
+
+
+class Affiliations(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'affiliations'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class Affiliation(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'affiliation'
+ plugin_attrib = name
+ interfaces = set(('node', 'affiliation', 'jid'))
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+
+class Subscription(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'subscription'
+ plugin_attrib = name
+ interfaces = set(('jid', 'node', 'subscription', 'subid'))
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+
+class Subscriptions(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'subscriptions'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class SubscribeOptions(ElementBase, OptionalSetting):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'subscribe-options'
+ plugin_attrib = 'suboptions'
+ interfaces = set(('required',))
+
+
+class Item(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'item'
+ plugin_attrib = name
+ interfaces = set(('id', 'payload'))
+
+ def set_payload(self, value):
+ del self['payload']
+ if isinstance(value, ElementBase):
+ if value.tag_name() in self.plugin_tag_map:
+ self.init_plugin(value.plugin_attrib, existing_xml=value.xml)
+ self.xml.append(value.xml)
+ else:
+ self.xml.append(value)
+
+ def get_payload(self):
+ childs = list(self.xml)
+ if len(childs) > 0:
+ return childs[0]
+
+ def del_payload(self):
+ for child in self.xml:
+ self.xml.remove(child)
+
+
+class Items(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'items'
+ plugin_attrib = name
+ interfaces = set(('node', 'max_items'))
+
+ def set_max_items(self, value):
+ self._set_attr('max_items', str(value))
+
+
+class Create(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'create'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class Default(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'default'
+ plugin_attrib = name
+ interfaces = set(('node', 'type'))
+
+ def get_type(self):
+ t = self._get_attr('type')
+ if not t:
+ return 'leaf'
+ return t
+
+
+class Publish(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'publish'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class Retract(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'retract'
+ plugin_attrib = name
+ interfaces = set(('node', 'notify'))
+
+ def get_notify(self):
+ notify = self._get_attr('notify')
+ if notify in ('0', 'false'):
+ return False
+ elif notify in ('1', 'true'):
+ return True
+ return None
+
+ def set_notify(self, value):
+ del self['notify']
+ if value is None:
+ return
+ elif value in (True, '1', 'true', 'True'):
+ self._set_attr('notify', 'true')
+ else:
+ self._set_attr('notify', 'false')
+
+
+class Unsubscribe(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'unsubscribe'
+ plugin_attrib = name
+ interfaces = set(('node', 'jid', 'subid'))
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+
+class Subscribe(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'subscribe'
+ plugin_attrib = name
+ interfaces = set(('node', 'jid'))
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+
+class Configure(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'configure'
+ plugin_attrib = name
+ interfaces = set(('node', 'type'))
+
+ def getType(self):
+ t = self._get_attr('type')
+ if not t:
+ t == 'leaf'
+ return t
+
+
+class Options(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'options'
+ plugin_attrib = name
+ interfaces = set(('jid', 'node', 'options'))
+
+ def __init__(self, *args, **kwargs):
+ ElementBase.__init__(self, *args, **kwargs)
+
+ def get_options(self):
+ config = self.xml.find('{jabber:x:data}x')
+ form = xep_0004.Form(xml=config)
+ return form
+
+ def set_options(self, value):
+ self.xml.append(value.getXML())
+ return self
+
+ def del_options(self):
+ config = self.xml.find('{jabber:x:data}x')
+ self.xml.remove(config)
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+
+class PublishOptions(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'publish-options'
+ plugin_attrib = 'publish_options'
+ interfaces = set(('publish_options',))
+ is_extension = True
+
+ def get_publish_options(self):
+ config = self.xml.find('{jabber:x:data}x')
+ if config is None:
+ return None
+ form = xep_0004.Form(xml=config)
+ return form
+
+ def set_publish_options(self, value):
+ if value is None:
+ self.del_publish_options()
+ else:
+ self.xml.append(value.getXML())
+ return self
+
+ def del_publish_options(self):
+ config = self.xml.find('{jabber:x:data}x')
+ if config is not None:
+ self.xml.remove(config)
+ self.parent().xml.remove(self.xml)
+
+
+register_stanza_plugin(Iq, Pubsub)
+register_stanza_plugin(Pubsub, Affiliations)
+register_stanza_plugin(Pubsub, Configure)
+register_stanza_plugin(Pubsub, Create)
+register_stanza_plugin(Pubsub, Default)
+register_stanza_plugin(Pubsub, Items)
+register_stanza_plugin(Pubsub, Options)
+register_stanza_plugin(Pubsub, Publish)
+register_stanza_plugin(Pubsub, PublishOptions)
+register_stanza_plugin(Pubsub, Retract)
+register_stanza_plugin(Pubsub, Subscribe)
+register_stanza_plugin(Pubsub, Subscription)
+register_stanza_plugin(Pubsub, Subscriptions)
+register_stanza_plugin(Pubsub, Unsubscribe)
+register_stanza_plugin(Affiliations, Affiliation, iterable=True)
+register_stanza_plugin(Configure, xep_0004.Form)
+register_stanza_plugin(Items, Item, iterable=True)
+register_stanza_plugin(Publish, Item, iterable=True)
+register_stanza_plugin(Retract, Item)
+register_stanza_plugin(Subscribe, Options)
+register_stanza_plugin(Subscription, SubscribeOptions)
+register_stanza_plugin(Subscriptions, Subscription, iterable=True)
diff --git a/slixmpp/plugins/xep_0060/stanza/pubsub_errors.py b/slixmpp/plugins/xep_0060/stanza/pubsub_errors.py
new file mode 100644
index 00000000..3e728009
--- /dev/null
+++ b/slixmpp/plugins/xep_0060/stanza/pubsub_errors.py
@@ -0,0 +1,86 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.stanza import Error
+from slixmpp.xmlstream import ElementBase, ET, register_stanza_plugin
+
+
+class PubsubErrorCondition(ElementBase):
+
+ plugin_attrib = 'pubsub'
+ interfaces = set(('condition', 'unsupported'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ conditions = set(('closed-node', 'configuration-required', 'invalid-jid',
+ 'invalid-options', 'invalid-payload', 'invalid-subid',
+ 'item-forbidden', 'item-required', 'jid-required',
+ 'max-items-exceeded', 'max-nodes-exceeded',
+ 'nodeid-required', 'not-in-roster-group',
+ 'not-subscribed', 'payload-too-big',
+ 'payload-required', 'pending-subscription',
+ 'presence-subscription-required', 'subid-required',
+ 'too-many-subscriptions', 'unsupported'))
+ condition_ns = 'http://jabber.org/protocol/pubsub#errors'
+
+ def setup(self, xml):
+ """Don't create XML for the plugin."""
+ self.xml = ET.Element('')
+
+ def get_condition(self):
+ """Return the condition element's name."""
+ for child in self.parent().xml:
+ if "{%s}" % self.condition_ns in child.tag:
+ cond = child.tag.split('}', 1)[-1]
+ if cond in self.conditions:
+ return cond
+ return ''
+
+ def set_condition(self, value):
+ """
+ Set the tag name of the condition element.
+
+ Arguments:
+ value -- The tag name of the condition element.
+ """
+ if value in self.conditions:
+ del self['condition']
+ cond = ET.Element("{%s}%s" % (self.condition_ns, value))
+ self.parent().xml.append(cond)
+ return self
+
+ def del_condition(self):
+ """Remove the condition element."""
+ for child in self.parent().xml:
+ if "{%s}" % self.condition_ns in child.tag:
+ tag = child.tag.split('}', 1)[-1]
+ if tag in self.conditions:
+ self.parent().xml.remove(child)
+ return self
+
+ def get_unsupported(self):
+ """Return the name of an unsupported feature"""
+ xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns)
+ if xml is not None:
+ return xml.attrib.get('feature', '')
+ return ''
+
+ def set_unsupported(self, value):
+ """Mark a feature as unsupported"""
+ self.del_unsupported()
+ xml = ET.Element('{%s}unsupported' % self.condition_ns)
+ xml.attrib['feature'] = value
+ self.parent().xml.append(xml)
+
+ def del_unsupported(self):
+ """Delete an unsupported feature condition."""
+ xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns)
+ if xml is not None:
+ self.parent().xml.remove(xml)
+
+
+register_stanza_plugin(Error, PubsubErrorCondition)
diff --git a/slixmpp/plugins/xep_0060/stanza/pubsub_event.py b/slixmpp/plugins/xep_0060/stanza/pubsub_event.py
new file mode 100644
index 00000000..1ba97c75
--- /dev/null
+++ b/slixmpp/plugins/xep_0060/stanza/pubsub_event.py
@@ -0,0 +1,151 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+import datetime as dt
+
+from slixmpp import Message
+from slixmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
+from slixmpp.plugins.xep_0004 import Form
+from slixmpp.plugins import xep_0082
+
+
+class Event(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'event'
+ plugin_attrib = 'pubsub_event'
+ interfaces = set()
+
+
+class EventItem(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'item'
+ plugin_attrib = name
+ interfaces = set(('id', 'payload', 'node', 'publisher'))
+
+ def set_payload(self, value):
+ self.xml.append(value)
+
+ def get_payload(self):
+ childs = list(self.xml)
+ if len(childs) > 0:
+ return childs[0]
+
+ def del_payload(self):
+ for child in self.xml:
+ self.xml.remove(child)
+
+
+class EventRetract(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'retract'
+ plugin_attrib = name
+ interfaces = set(('id',))
+
+
+class EventItems(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'items'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class EventCollection(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'collection'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class EventAssociate(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'associate'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class EventDisassociate(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'disassociate'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class EventConfiguration(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'configuration'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class EventPurge(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'purge'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class EventDelete(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'delete'
+ plugin_attrib = name
+ interfaces = set(('node', 'redirect'))
+
+ def set_redirect(self, uri):
+ del self['redirect']
+ redirect = ET.Element('{%s}redirect' % self.namespace)
+ redirect.attrib['uri'] = uri
+ self.xml.append(redirect)
+
+ def get_redirect(self):
+ redirect = self.xml.find('{%s}redirect' % self.namespace)
+ if redirect is not None:
+ return redirect.attrib.get('uri', '')
+ return ''
+
+ def del_redirect(self):
+ redirect = self.xml.find('{%s}redirect' % self.namespace)
+ if redirect is not None:
+ self.xml.remove(redirect)
+
+
+class EventSubscription(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#event'
+ name = 'subscription'
+ plugin_attrib = name
+ interfaces = set(('node', 'expiry', 'jid', 'subid', 'subscription'))
+
+ def get_expiry(self):
+ expiry = self._get_attr('expiry')
+ if expiry.lower() == 'presence':
+ return expiry
+ return xep_0082.parse(expiry)
+
+ def set_expiry(self, value):
+ if isinstance(value, dt.datetime):
+ value = xep_0082.format_datetime(value)
+ self._set_attr('expiry', value)
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+
+register_stanza_plugin(Message, Event)
+register_stanza_plugin(Event, EventCollection)
+register_stanza_plugin(Event, EventConfiguration)
+register_stanza_plugin(Event, EventPurge)
+register_stanza_plugin(Event, EventDelete)
+register_stanza_plugin(Event, EventItems)
+register_stanza_plugin(Event, EventSubscription)
+register_stanza_plugin(EventCollection, EventAssociate)
+register_stanza_plugin(EventCollection, EventDisassociate)
+register_stanza_plugin(EventConfiguration, Form)
+register_stanza_plugin(EventItems, EventItem, iterable=True)
+register_stanza_plugin(EventItems, EventRetract, iterable=True)
diff --git a/slixmpp/plugins/xep_0060/stanza/pubsub_owner.py b/slixmpp/plugins/xep_0060/stanza/pubsub_owner.py
new file mode 100644
index 00000000..402e5e30
--- /dev/null
+++ b/slixmpp/plugins/xep_0060/stanza/pubsub_owner.py
@@ -0,0 +1,134 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp import Iq
+from slixmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
+from slixmpp.plugins.xep_0004 import Form
+from slixmpp.plugins.xep_0060.stanza.base import OptionalSetting
+from slixmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation
+from slixmpp.plugins.xep_0060.stanza.pubsub import Configure, Subscriptions
+
+
+class PubsubOwner(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'pubsub'
+ plugin_attrib = 'pubsub_owner'
+ interfaces = set(tuple())
+
+
+class DefaultConfig(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'default'
+ plugin_attrib = name
+ interfaces = set(('node', 'config'))
+
+ def __init__(self, *args, **kwargs):
+ ElementBase.__init__(self, *args, **kwargs)
+
+ def get_config(self):
+ return self['form']
+
+ def set_config(self, value):
+ del self['from']
+ self.append(value)
+ return self
+
+
+class OwnerAffiliations(Affiliations):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('node',))
+
+ def append(self, affiliation):
+ if not isinstance(affiliation, OwnerAffiliation):
+ raise TypeError
+ self.xml.append(affiliation.xml)
+
+
+class OwnerAffiliation(Affiliation):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('affiliation', 'jid'))
+
+
+class OwnerConfigure(Configure):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'configure'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class OwnerDefault(OwnerConfigure):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('node',))
+
+
+class OwnerDelete(ElementBase, OptionalSetting):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'delete'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class OwnerPurge(ElementBase, OptionalSetting):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'purge'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+
+class OwnerRedirect(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'redirect'
+ plugin_attrib = name
+ interfaces = set(('node', 'jid'))
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+
+class OwnerSubscriptions(Subscriptions):
+ name = 'subscriptions'
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ plugin_attrib = name
+ interfaces = set(('node',))
+
+ def append(self, subscription):
+ if not isinstance(subscription, OwnerSubscription):
+ raise TypeError
+ self.xml.append(subscription.xml)
+
+
+class OwnerSubscription(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'subscription'
+ plugin_attrib = name
+ interfaces = set(('jid', 'subscription'))
+
+ def set_jid(self, value):
+ self._set_attr('jid', str(value))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid'))
+
+
+register_stanza_plugin(Iq, PubsubOwner)
+register_stanza_plugin(PubsubOwner, DefaultConfig)
+register_stanza_plugin(PubsubOwner, OwnerAffiliations)
+register_stanza_plugin(PubsubOwner, OwnerConfigure)
+register_stanza_plugin(PubsubOwner, OwnerDefault)
+register_stanza_plugin(PubsubOwner, OwnerDelete)
+register_stanza_plugin(PubsubOwner, OwnerPurge)
+register_stanza_plugin(PubsubOwner, OwnerSubscriptions)
+register_stanza_plugin(DefaultConfig, Form)
+register_stanza_plugin(OwnerAffiliations, OwnerAffiliation, iterable=True)
+register_stanza_plugin(OwnerConfigure, Form)
+register_stanza_plugin(OwnerDefault, Form)
+register_stanza_plugin(OwnerDelete, OwnerRedirect)
+register_stanza_plugin(OwnerSubscriptions, OwnerSubscription, iterable=True)