From 9ffdba8643ca8e52a30ce227331cd772a5fea6d1 Mon Sep 17 00:00:00 2001
From: Nathan Fritz <fritzy@netflint.net>
Date: Wed, 3 Aug 2011 23:56:24 -0700
Subject: the great xep_0060 re-organization in preperation for rewrite

---
 setup.py                                          |   1 +
 sleekxmpp/plugins/stanza_pubsub.py                | 557 ----------------------
 sleekxmpp/plugins/xep_0060/__init__.py            | 315 +-----------
 sleekxmpp/plugins/xep_0060/pubsub.py              | 313 ++++++++++++
 sleekxmpp/plugins/xep_0060/stanza/__init__.py     |   3 +
 sleekxmpp/plugins/xep_0060/stanza/base.py         |  24 +
 sleekxmpp/plugins/xep_0060/stanza/pubsub.py       | 277 +++++++++++
 sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py | 124 +++++
 sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py | 152 ++++++
 tests/test_stanza_xep_0060.py                     |   2 +-
 10 files changed, 897 insertions(+), 871 deletions(-)
 delete mode 100644 sleekxmpp/plugins/stanza_pubsub.py
 create mode 100644 sleekxmpp/plugins/xep_0060/pubsub.py
 create mode 100644 sleekxmpp/plugins/xep_0060/stanza/__init__.py
 create mode 100644 sleekxmpp/plugins/xep_0060/stanza/base.py
 create mode 100644 sleekxmpp/plugins/xep_0060/stanza/pubsub.py
 create mode 100644 sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py
 create mode 100644 sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py

diff --git a/setup.py b/setup.py
index e83676f2..572dd1f0 100644
--- a/setup.py
+++ b/setup.py
@@ -53,6 +53,7 @@ packages     = [ 'sleekxmpp',
                  'sleekxmpp/plugins/xep_0050',
                  'sleekxmpp/plugins/xep_0059',
                  'sleekxmpp/plugins/xep_0060',
+                 'sleekxmpp/plugins/xep_0060/stanza',
                  'sleekxmpp/plugins/xep_0085',
                  'sleekxmpp/plugins/xep_0086',
                  'sleekxmpp/plugins/xep_0092',
diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py
deleted file mode 100644
index b5964537..00000000
--- a/sleekxmpp/plugins/stanza_pubsub.py
+++ /dev/null
@@ -1,557 +0,0 @@
-from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
-from .. stanza.iq import Iq
-from .. stanza.message import Message
-from .. basexmpp import basexmpp
-from .. xmlstream.xmlstream import XMLStream
-import logging
-from . import xep_0004
-
-
-class PubsubState(ElementBase):
-	namespace = 'http://jabber.org/protocol/psstate'
-	name = 'state'
-	plugin_attrib = 'psstate'
-	interfaces = set(('node', 'item', 'payload'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def setPayload(self, value):
-		self.xml.append(value)
-	
-	def getPayload(self):
-		childs = self.xml.getchildren()
-		if len(childs) > 0:
-			return childs[0]
-	
-	def delPayload(self):
-		for child in self.xml.getchildren():
-			self.xml.remove(child)
-
-registerStanzaPlugin(Iq, PubsubState)
-
-class PubsubStateEvent(ElementBase):
-	namespace = 'http://jabber.org/protocol/psstate#event'
-	name = 'event'
-	plugin_attrib = 'psstate_event'
-	intefaces = set(tuple())
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(Message, PubsubStateEvent)
-registerStanzaPlugin(PubsubStateEvent, PubsubState)
-
-class Pubsub(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'pubsub'
-	plugin_attrib = 'pubsub'
-	interfaces = set(tuple())
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(Iq, Pubsub)
-
-class PubsubOwner(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	name = 'pubsub'
-	plugin_attrib = 'pubsub_owner'
-	interfaces = set(tuple())
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(Iq, PubsubOwner)
-
-class Affiliation(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'affiliation'
-	plugin_attrib = name
-	interfaces = set(('node', 'affiliation'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-class Affiliations(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'affiliations'
-	plugin_attrib = 'affiliations'
-	interfaces = set(tuple())
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	subitem = (Affiliation,)
-
-	def append(self, affiliation):
-		if not isinstance(affiliation, Affiliation):
-			raise TypeError
-		self.xml.append(affiliation.xml)
-		return self.iterables.append(affiliation)
-
-registerStanzaPlugin(Pubsub, Affiliations)
-
-
-class Subscription(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'subscription'
-	plugin_attrib = name
-	interfaces = set(('jid', 'node', 'subscription', 'subid'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-	def setjid(self, value):
-		self._setattr('jid', str(value))
-	
-	def getjid(self):
-		return jid(self._getattr('jid'))
-
-registerStanzaPlugin(Pubsub, Subscription)
-
-class Subscriptions(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'subscriptions'
-	plugin_attrib = 'subscriptions'
-	interfaces = set(tuple())
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	subitem = (Subscription,)
-
-registerStanzaPlugin(Pubsub, Subscriptions)
-
-class OptionalSetting(object):
-	interfaces = set(('required',))
-
-	def setRequired(self, value):
-		value = bool(value)
-		if value and not self['required']:
-			self.xml.append(ET.Element("{%s}required" % self.namespace))
-		elif not value and self['required']:
-			self.delRequired()
-	
-	def getRequired(self):
-		required = self.xml.find("{%s}required" % self.namespace)
-		if required is not None:
-			return True
-		else:
-			return False
-	
-	def delRequired(self):
-		required = self.xml.find("{%s}required" % self.namespace)
-		if required is not None:
-			self.xml.remove(required)
-
-
-class SubscribeOptions(ElementBase, OptionalSetting):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'subscribe-options'
-	plugin_attrib = 'suboptions'
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	interfaces = set(('required',))
-
-registerStanzaPlugin(Subscription, SubscribeOptions)
-
-class Item(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'item'
-	plugin_attrib = name
-	interfaces = set(('id', 'payload'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-	def setPayload(self, value):
-		self.xml.append(value)
-	
-	def getPayload(self):
-		childs = self.xml.getchildren()
-		if len(childs) > 0:
-			return childs[0]
-	
-	def delPayload(self):
-		for child in self.xml.getchildren():
-			self.xml.remove(child)
-
-class Items(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'items'
-	plugin_attrib = 'items'
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	subitem = (Item,)
-
-registerStanzaPlugin(Pubsub, Items)
-
-class Create(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'create'
-	plugin_attrib = name
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(Pubsub, Create)
-
-#class Default(ElementBase):
-#	namespace = 'http://jabber.org/protocol/pubsub'
-#	name = 'default'
-#	plugin_attrib = name
-#	interfaces = set(('node', 'type'))
-#	plugin_attrib_map = {}
-#	plugin_tag_map = {}
-#
-#	def getType(self):
-#		t = self._getAttr('type')
-#		if not t: t == 'leaf'
-#		return t
-#
-#registerStanzaPlugin(Pubsub, Default)
-
-class Publish(Items):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'publish'
-	plugin_attrib = name
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	subitem = (Item,)
-
-registerStanzaPlugin(Pubsub, Publish)
-
-class Retract(Items):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'retract'
-	plugin_attrib = name
-	interfaces = set(('node', 'notify'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(Pubsub, Retract)
-
-class Unsubscribe(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'unsubscribe'
-	plugin_attrib = name
-	interfaces = set(('node', 'jid'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def setJid(self, value):
-		self._setAttr('jid', str(value))
-	
-	def getJid(self):
-		return JID(self._getAttr('jid'))
-
-registerStanzaPlugin(Pubsub, Unsubscribe)
-
-class Subscribe(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'subscribe'
-	plugin_attrib = name
-	interfaces = set(('node', 'jid'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-	def setJid(self, value):
-		self._setAttr('jid', str(value))
-	
-	def getJid(self):
-		return JID(self._getAttr('jid'))
-
-registerStanzaPlugin(Pubsub, Subscribe)
-
-class Configure(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'configure'
-	plugin_attrib = name
-	interfaces = set(('node', 'type'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-	def getType(self):
-		t = self._getAttr('type')
-		if not t: t == 'leaf'
-		return t
-	
-registerStanzaPlugin(Pubsub, Configure)
-registerStanzaPlugin(Configure, xep_0004.Form)
-
-class DefaultConfig(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	name = 'default'
-	plugin_attrib = 'default'
-	interfaces = set(('node', 'type', 'config'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def __init__(self, *args, **kwargs):
-		ElementBase.__init__(self, *args, **kwargs)
-
-	def getType(self):
-		t = self._getAttr('type')
-		if not t: t = 'leaf'
-		return t
-	
-	def getConfig(self):
-		return self['form']
-	
-	def setConfig(self, value):
-		self['form'].setStanzaValues(value.getStanzaValues())
-		return self
-
-registerStanzaPlugin(PubsubOwner, DefaultConfig)
-registerStanzaPlugin(DefaultConfig, xep_0004.Form)
-
-class Options(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub'
-	name = 'options'
-	plugin_attrib = 'options'
-	interfaces = set(('jid', 'node', 'options'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def __init__(self, *args, **kwargs):
-		ElementBase.__init__(self, *args, **kwargs)
-		
-	def getOptions(self):
-		config = self.xml.find('{jabber:x:data}x')
-		form = xep_0004.Form()
-		if config is not None:
-			form.fromXML(config)
-		return form
-	
-	def setOptions(self, value):
-		self.xml.append(value.getXML())
-		return self
-	
-	def delOptions(self):
-		config = self.xml.find('{jabber:x:data}x')
-		self.xml.remove(config)
-	
-	def setJid(self, value):
-		self._setAttr('jid', str(value))
-	
-	def getJid(self):
-		return JID(self._getAttr('jid'))
-
-registerStanzaPlugin(Pubsub, Options)
-registerStanzaPlugin(Subscribe, Options)
-
-class OwnerAffiliations(Affiliations):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	interfaces = set(('node'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def append(self, affiliation):
-		if not isinstance(affiliation, OwnerAffiliation):
-			raise TypeError
-		self.xml.append(affiliation.xml)
-		return self.affiliations.append(affiliation)
-
-registerStanzaPlugin(PubsubOwner, OwnerAffiliations)
-
-class OwnerAffiliation(Affiliation):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	interfaces = set(('affiliation', 'jid'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-class OwnerConfigure(Configure):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	interfaces = set(('node', 'config'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(PubsubOwner, OwnerConfigure)
-
-class OwnerDefault(OwnerConfigure):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	interfaces = set(('node', 'config'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def getConfig(self):
-		return self['form']
-	
-	def setConfig(self, value):
-		self['form'].setStanzaValues(value.getStanzaValues())
-		return self
-
-registerStanzaPlugin(PubsubOwner, OwnerDefault)
-registerStanzaPlugin(OwnerDefault, xep_0004.Form)
-
-class OwnerDelete(ElementBase, OptionalSetting):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	name = 'delete'
-	plugin_attrib = 'delete'
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	interfaces = set(('node',))
-
-registerStanzaPlugin(PubsubOwner, OwnerDelete)
-
-class OwnerPurge(ElementBase, OptionalSetting):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	name = 'purge'
-	plugin_attrib = name
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(PubsubOwner, OwnerPurge)
-
-class OwnerRedirect(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	name = 'redirect'
-	plugin_attrib = name
-	interfaces = set(('node', 'jid'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def setJid(self, value):
-		self._setAttr('jid', str(value))
-	
-	def getJid(self):
-		return JID(self._getAttr('jid'))
-
-registerStanzaPlugin(OwnerDelete, OwnerRedirect)
-
-class OwnerSubscriptions(Subscriptions):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def append(self, subscription):
-		if not isinstance(subscription, OwnerSubscription):
-			raise TypeError
-		self.xml.append(subscription.xml)
-		return self.subscriptions.append(subscription)
-
-registerStanzaPlugin(PubsubOwner, OwnerSubscriptions)
-
-class OwnerSubscription(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#owner'
-	name = 'subscription'
-	plugin_attrib = name
-	interfaces = set(('jid', 'subscription'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-	def setJid(self, value):
-		self._setAttr('jid', str(value))
-	
-	def getJid(self):
-		return JID(self._getAttr('from'))
-
-class Event(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'event'
-	plugin_attrib = 'pubsub_event'
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(Message, Event)
-
-class EventItem(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'item'
-	plugin_attrib = 'item'
-	interfaces = set(('id', 'payload'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-	def setPayload(self, value):
-		self.xml.append(value)
-	
-	def getPayload(self):
-		childs = self.xml.getchildren()
-		if len(childs) > 0:
-			return childs[0]
-	
-	def delPayload(self):
-		for child in self.xml.getchildren():
-			self.xml.remove(child)
-
-
-class EventRetract(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'retract'
-	plugin_attrib = 'retract'
-	interfaces = set(('id',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-class EventItems(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'items'
-	plugin_attrib = 'items'
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	subitem = (EventItem, EventRetract)
-
-registerStanzaPlugin(Event, EventItems)
-
-class EventCollection(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'collection'
-	plugin_attrib = name
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(Event, EventCollection)
-
-class EventAssociate(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'associate'
-	plugin_attrib = name
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(EventCollection, EventAssociate)
-
-class EventDisassociate(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'disassociate'
-	plugin_attrib = name
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(EventCollection, EventDisassociate)
-
-class EventConfiguration(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'configuration'
-	plugin_attrib = name
-	interfaces = set(('node', 'config'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-registerStanzaPlugin(Event, EventConfiguration)
-registerStanzaPlugin(EventConfiguration, xep_0004.Form)
-
-class EventPurge(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'purge'
-	plugin_attrib = name
-	interfaces = set(('node',))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-
-registerStanzaPlugin(Event, EventPurge)
-
-class EventSubscription(ElementBase):
-	namespace = 'http://jabber.org/protocol/pubsub#event'
-	name = 'subscription'
-	plugin_attrib = name
-	interfaces = set(('node','expiry', 'jid', 'subid', 'subscription'))
-	plugin_attrib_map = {}
-	plugin_tag_map = {}
-	
-	def setJid(self, value):
-		self._setAttr('jid', str(value))
-	
-	def getJid(self):
-		return JID(self._getAttr('jid'))
-
-registerStanzaPlugin(Event, EventSubscription)
diff --git a/sleekxmpp/plugins/xep_0060/__init__.py b/sleekxmpp/plugins/xep_0060/__init__.py
index bb7503a0..a0c91f85 100644
--- a/sleekxmpp/plugins/xep_0060/__init__.py
+++ b/sleekxmpp/plugins/xep_0060/__init__.py
@@ -1,313 +1,2 @@
-from __future__ import with_statement
-from sleekxmpp.plugins import base
-import logging
-#from xml.etree import cElementTree as ET
-from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
-from sleekxmpp.plugins import stanza_pubsub
-from sleekxmpp.plugins.xep_0004 import Form
-
-
-log = logging.getLogger(__name__)
-
-
-class xep_0060(base.base_plugin):
-	"""
-	XEP-0060 Publish Subscribe
-	"""
-
-	def plugin_init(self):
-		self.xep = '0060'
-		self.description = 'Publish-Subscribe'
-
-	def create_node(self, jid, node, config=None, collection=False, ntype=None):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
-		create = ET.Element('create')
-		create.set('node', node)
-		pubsub.append(create)
-		configure = ET.Element('configure')
-		if collection:
-			ntype = 'collection'
-		#if config is None:
-		#	submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
-		#else:
-		if config is not None:
-			submitform = config
-			if 'FORM_TYPE' in submitform.field:
-				submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
-			else:
-				submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
-			if ntype:
-				if 'pubsub#node_type' in submitform.field:
-					submitform.field['pubsub#node_type'].setValue(ntype)
-				else:
-					submitform.addField('pubsub#node_type', value=ntype)
-			else:
-				if 'pubsub#node_type' in submitform.field:
-					submitform.field['pubsub#node_type'].setValue('leaf')
-				else:
-					submitform.addField('pubsub#node_type', value='leaf')
-			submitform['type'] = 'submit'
-			configure.append(submitform.xml)
-		pubsub.append(configure)
-		iq = self.xmpp.makeIqSet(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is False or result is None or result['type'] == 'error': return False
-		return True
-
-	def subscribe(self, jid, node, bare=True, subscribee=None):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
-		subscribe = ET.Element('subscribe')
-		subscribe.attrib['node'] = node
-		if subscribee is None:
-			if bare:
-				subscribe.attrib['jid'] = self.xmpp.boundjid.bare
-			else:
-				subscribe.attrib['jid'] = self.xmpp.boundjid.full
-		else:
-			subscribe.attrib['jid'] = subscribee
-		pubsub.append(subscribe)
-		iq = self.xmpp.makeIqSet(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is False or result is None or result['type'] == 'error': return False
-		return True
-
-	def unsubscribe(self, jid, node, bare=True, subscribee=None):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
-		unsubscribe = ET.Element('unsubscribe')
-		unsubscribe.attrib['node'] = node
-		if subscribee is None:
-			if bare:
-				unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
-			else:
-				unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
-		else:
-			unsubscribe.attrib['jid'] = subscribee
-		pubsub.append(unsubscribe)
-		iq = self.xmpp.makeIqSet(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is False or result is None or result['type'] == 'error': return False
-		return True
-
-	def getNodeConfig(self, jid, node=None): # if no node, then grab default
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
-		if node is not None:
-			configure = ET.Element('configure')
-			configure.attrib['node'] = node
-		else:
-			configure = ET.Element('default')
-		pubsub.append(configure)
-		#TODO: Add configure support.
-		iq = self.xmpp.makeIqGet()
-		iq.append(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
-		result = iq.send()
-		if result is None or result == False or result['type'] == 'error':
-			log.warning("got error instead of config")
-			return False
-		if node is not None:
-			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
-		else:
-			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
-		if not form or form is None:
-			log.error("No form found.")
-			return False
-		return Form(xml=form)
-
-	def getNodeSubscriptions(self, jid, node):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
-		subscriptions = ET.Element('subscriptions')
-		subscriptions.attrib['node'] = node
-		pubsub.append(subscriptions)
-		iq = self.xmpp.makeIqGet()
-		iq.append(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is None or result == False or result['type'] == 'error':
-			log.warning("got error instead of config")
-			return False
-		else:
-			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
-			if results is None:
-				return False
-			subs = {}
-			for sub in results:
-				subs[sub.get('jid')] = sub.get('subscription')
-			return subs
-
-	def getNodeAffiliations(self, jid, node):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
-		affiliations = ET.Element('affiliations')
-		affiliations.attrib['node'] = node
-		pubsub.append(affiliations)
-		iq = self.xmpp.makeIqGet()
-		iq.append(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is None or result == False or result['type'] == 'error':
-			log.warning("got error instead of config")
-			return False
-		else:
-			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
-			if results is None:
-				return False
-			subs = {}
-			for sub in results:
-				subs[sub.get('jid')] = sub.get('affiliation')
-			return subs
-
-	def deleteNode(self, jid, node):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
-		iq = self.xmpp.makeIqSet()
-		delete = ET.Element('delete')
-		delete.attrib['node'] = node
-		pubsub.append(delete)
-		iq.append(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		result = iq.send()
-		if result is not None and result is not False and result['type'] != 'error':
-			return True
-		else:
-			return False
-
-
-	def setNodeConfig(self, jid, node, config):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
-		configure = ET.Element('configure')
-		configure.attrib['node'] = node
-		config = config.getXML('submit')
-		configure.append(config)
-		pubsub.append(configure)
-		iq = self.xmpp.makeIqSet(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is None or result['type'] == 'error':
-			return False
-		return True
-
-	def setItem(self, jid, node, items=[]):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
-		publish = ET.Element('publish')
-		publish.attrib['node'] = node
-		for pub_item in items:
-			id, payload = pub_item
-			item = ET.Element('item')
-			if id is not None:
-				item.attrib['id'] = id
-			item.append(payload)
-			publish.append(item)
-		pubsub.append(publish)
-		iq = self.xmpp.makeIqSet(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is None or result is False or result['type'] == 'error': return False
-		return True
-
-	def addItem(self, jid, node, items=[]):
-		return self.setItem(jid, node, items)
-
-	def deleteItem(self, jid, node, item):
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
-		retract = ET.Element('retract')
-		retract.attrib['node'] = node
-		itemn = ET.Element('item')
-		itemn.attrib['id'] = item
-		retract.append(itemn)
-		pubsub.append(retract)
-		iq = self.xmpp.makeIqSet(pubsub)
-		iq.attrib['to'] = jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is None or result is False or result['type'] == 'error': return False
-		return True
-
-	def getNodes(self, jid):
-		response = self.xmpp.plugin['xep_0030'].getItems(jid)
-		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
-		nodes = {}
-		if items is not None and items is not False:
-			for item in items:
-				nodes[item.get('node')] = item.get('name')
-		return nodes
-
-	def getItems(self, jid, node):
-		response = self.xmpp.plugin['xep_0030'].getItems(jid, node)
-		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
-		nodeitems = []
-		if items is not None and items is not False:
-			for item in items:
-				nodeitems.append(item.get('node'))
-		return nodeitems
-
-	def addNodeToCollection(self, jid, child, parent=''):
-		config = self.getNodeConfig(jid, child)
-		if not config or config is None:
-			self.lasterror = "Config Error"
-			return False
-		try:
-			config.field['pubsub#collection'].setValue(parent)
-		except KeyError:
-			log.warning("pubsub#collection doesn't exist in config, trying to add it")
-			config.addField('pubsub#collection', value=parent)
-		if not self.setNodeConfig(jid, child, config):
-			return False
-		return True
-
-	def modifyAffiliation(self, ps_jid, node, user_jid, affiliation):
-		if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'):
-			raise TypeError
-		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
-		affs = ET.Element('affiliations')
-		affs.attrib['node'] = node
-		aff = ET.Element('affiliation')
-		aff.attrib['jid'] = user_jid
-		aff.attrib['affiliation'] = affiliation
-		affs.append(aff)
-		pubsub.append(affs)
-		iq = self.xmpp.makeIqSet(pubsub)
-		iq.attrib['to'] = ps_jid
-		iq.attrib['from'] = self.xmpp.boundjid.full
-		id = iq['id']
-		result = iq.send()
-		if result is None or result is False or result['type'] == 'error':
-		    return False
-		return True
-
-	def addNodeToCollection(self, jid, child, parent=''):
-		config = self.getNodeConfig(jid, child)
-		if not config or config is None:
-			self.lasterror = "Config Error"
-			return False
-		try:
-			config.field['pubsub#collection'].setValue(parent)
-		except KeyError:
-			log.warning("pubsub#collection doesn't exist in config, trying to add it")
-			config.addField('pubsub#collection', value=parent)
-		if not self.setNodeConfig(jid, child, config):
-			return False
-		return True
-
-	def removeNodeFromCollection(self, jid, child):
-		self.addNodeToCollection(jid, child, '')
-
+from pubsub import xep_0060
+import stanza
diff --git a/sleekxmpp/plugins/xep_0060/pubsub.py b/sleekxmpp/plugins/xep_0060/pubsub.py
new file mode 100644
index 00000000..e199be07
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0060/pubsub.py
@@ -0,0 +1,313 @@
+from __future__ import with_statement
+from sleekxmpp.plugins import base
+import logging
+#from xml.etree import cElementTree as ET
+from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
+from sleekxmpp.plugins.xep_0060 import stanza
+from sleekxmpp.plugins.xep_0004 import Form
+
+
+log = logging.getLogger(__name__)
+
+
+class xep_0060(base.base_plugin):
+	"""
+	XEP-0060 Publish Subscribe
+	"""
+
+	def plugin_init(self):
+		self.xep = '0060'
+		self.description = 'Publish-Subscribe'
+
+	def create_node(self, jid, node, config=None, collection=False, ntype=None):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
+		create = ET.Element('create')
+		create.set('node', node)
+		pubsub.append(create)
+		configure = ET.Element('configure')
+		if collection:
+			ntype = 'collection'
+		#if config is None:
+		#	submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
+		#else:
+		if config is not None:
+			submitform = config
+			if 'FORM_TYPE' in submitform.field:
+				submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
+			else:
+				submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
+			if ntype:
+				if 'pubsub#node_type' in submitform.field:
+					submitform.field['pubsub#node_type'].setValue(ntype)
+				else:
+					submitform.addField('pubsub#node_type', value=ntype)
+			else:
+				if 'pubsub#node_type' in submitform.field:
+					submitform.field['pubsub#node_type'].setValue('leaf')
+				else:
+					submitform.addField('pubsub#node_type', value='leaf')
+			submitform['type'] = 'submit'
+			configure.append(submitform.xml)
+		pubsub.append(configure)
+		iq = self.xmpp.makeIqSet(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is False or result is None or result['type'] == 'error': return False
+		return True
+
+	def subscribe(self, jid, node, bare=True, subscribee=None):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
+		subscribe = ET.Element('subscribe')
+		subscribe.attrib['node'] = node
+		if subscribee is None:
+			if bare:
+				subscribe.attrib['jid'] = self.xmpp.boundjid.bare
+			else:
+				subscribe.attrib['jid'] = self.xmpp.boundjid.full
+		else:
+			subscribe.attrib['jid'] = subscribee
+		pubsub.append(subscribe)
+		iq = self.xmpp.makeIqSet(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is False or result is None or result['type'] == 'error': return False
+		return True
+
+	def unsubscribe(self, jid, node, bare=True, subscribee=None):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
+		unsubscribe = ET.Element('unsubscribe')
+		unsubscribe.attrib['node'] = node
+		if subscribee is None:
+			if bare:
+				unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
+			else:
+				unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
+		else:
+			unsubscribe.attrib['jid'] = subscribee
+		pubsub.append(unsubscribe)
+		iq = self.xmpp.makeIqSet(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is False or result is None or result['type'] == 'error': return False
+		return True
+
+	def getNodeConfig(self, jid, node=None): # if no node, then grab default
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
+		if node is not None:
+			configure = ET.Element('configure')
+			configure.attrib['node'] = node
+		else:
+			configure = ET.Element('default')
+		pubsub.append(configure)
+		#TODO: Add configure support.
+		iq = self.xmpp.makeIqGet()
+		iq.append(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
+		result = iq.send()
+		if result is None or result == False or result['type'] == 'error':
+			log.warning("got error instead of config")
+			return False
+		if node is not None:
+			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
+		else:
+			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
+		if not form or form is None:
+			log.error("No form found.")
+			return False
+		return Form(xml=form)
+
+	def getNodeSubscriptions(self, jid, node):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
+		subscriptions = ET.Element('subscriptions')
+		subscriptions.attrib['node'] = node
+		pubsub.append(subscriptions)
+		iq = self.xmpp.makeIqGet()
+		iq.append(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is None or result == False or result['type'] == 'error':
+			log.warning("got error instead of config")
+			return False
+		else:
+			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
+			if results is None:
+				return False
+			subs = {}
+			for sub in results:
+				subs[sub.get('jid')] = sub.get('subscription')
+			return subs
+
+	def getNodeAffiliations(self, jid, node):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
+		affiliations = ET.Element('affiliations')
+		affiliations.attrib['node'] = node
+		pubsub.append(affiliations)
+		iq = self.xmpp.makeIqGet()
+		iq.append(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is None or result == False or result['type'] == 'error':
+			log.warning("got error instead of config")
+			return False
+		else:
+			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
+			if results is None:
+				return False
+			subs = {}
+			for sub in results:
+				subs[sub.get('jid')] = sub.get('affiliation')
+			return subs
+
+	def deleteNode(self, jid, node):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
+		iq = self.xmpp.makeIqSet()
+		delete = ET.Element('delete')
+		delete.attrib['node'] = node
+		pubsub.append(delete)
+		iq.append(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		result = iq.send()
+		if result is not None and result is not False and result['type'] != 'error':
+			return True
+		else:
+			return False
+
+
+	def setNodeConfig(self, jid, node, config):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
+		configure = ET.Element('configure')
+		configure.attrib['node'] = node
+		config = config.getXML('submit')
+		configure.append(config)
+		pubsub.append(configure)
+		iq = self.xmpp.makeIqSet(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is None or result['type'] == 'error':
+			return False
+		return True
+
+	def setItem(self, jid, node, items=[]):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
+		publish = ET.Element('publish')
+		publish.attrib['node'] = node
+		for pub_item in items:
+			id, payload = pub_item
+			item = ET.Element('item')
+			if id is not None:
+				item.attrib['id'] = id
+			item.append(payload)
+			publish.append(item)
+		pubsub.append(publish)
+		iq = self.xmpp.makeIqSet(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is None or result is False or result['type'] == 'error': return False
+		return True
+
+	def addItem(self, jid, node, items=[]):
+		return self.setItem(jid, node, items)
+
+	def deleteItem(self, jid, node, item):
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
+		retract = ET.Element('retract')
+		retract.attrib['node'] = node
+		itemn = ET.Element('item')
+		itemn.attrib['id'] = item
+		retract.append(itemn)
+		pubsub.append(retract)
+		iq = self.xmpp.makeIqSet(pubsub)
+		iq.attrib['to'] = jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is None or result is False or result['type'] == 'error': return False
+		return True
+
+	def getNodes(self, jid):
+		response = self.xmpp.plugin['xep_0030'].getItems(jid)
+		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
+		nodes = {}
+		if items is not None and items is not False:
+			for item in items:
+				nodes[item.get('node')] = item.get('name')
+		return nodes
+
+	def getItems(self, jid, node):
+		response = self.xmpp.plugin['xep_0030'].getItems(jid, node)
+		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
+		nodeitems = []
+		if items is not None and items is not False:
+			for item in items:
+				nodeitems.append(item.get('node'))
+		return nodeitems
+
+	def addNodeToCollection(self, jid, child, parent=''):
+		config = self.getNodeConfig(jid, child)
+		if not config or config is None:
+			self.lasterror = "Config Error"
+			return False
+		try:
+			config.field['pubsub#collection'].setValue(parent)
+		except KeyError:
+			log.warning("pubsub#collection doesn't exist in config, trying to add it")
+			config.addField('pubsub#collection', value=parent)
+		if not self.setNodeConfig(jid, child, config):
+			return False
+		return True
+
+	def modifyAffiliation(self, ps_jid, node, user_jid, affiliation):
+		if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'):
+			raise TypeError
+		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
+		affs = ET.Element('affiliations')
+		affs.attrib['node'] = node
+		aff = ET.Element('affiliation')
+		aff.attrib['jid'] = user_jid
+		aff.attrib['affiliation'] = affiliation
+		affs.append(aff)
+		pubsub.append(affs)
+		iq = self.xmpp.makeIqSet(pubsub)
+		iq.attrib['to'] = ps_jid
+		iq.attrib['from'] = self.xmpp.boundjid.full
+		id = iq['id']
+		result = iq.send()
+		if result is None or result is False or result['type'] == 'error':
+		    return False
+		return True
+
+	def addNodeToCollection(self, jid, child, parent=''):
+		config = self.getNodeConfig(jid, child)
+		if not config or config is None:
+			self.lasterror = "Config Error"
+			return False
+		try:
+			config.field['pubsub#collection'].setValue(parent)
+		except KeyError:
+			log.warning("pubsub#collection doesn't exist in config, trying to add it")
+			config.addField('pubsub#collection', value=parent)
+		if not self.setNodeConfig(jid, child, config):
+			return False
+		return True
+
+	def removeNodeFromCollection(self, jid, child):
+		self.addNodeToCollection(jid, child, '')
+
diff --git a/sleekxmpp/plugins/xep_0060/stanza/__init__.py b/sleekxmpp/plugins/xep_0060/stanza/__init__.py
new file mode 100644
index 00000000..52a21efe
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0060/stanza/__init__.py
@@ -0,0 +1,3 @@
+from pubsub import Pubsub, Affiliation, Affiliations, Subscription, Subscriptions, SubscribeOptions, Item, Items, Create, Publish, Retract, Unsubscribe, Subscribe, Configure, Options, PubsubState, PubsubStateEvent
+from pubsub_owner import PubsubOwner, DefaultConfig, OwnerAffiliations, OwnerAffiliation, OwnerConfigure, OwnerDefault, OwnerDelete, OwnerPurge, OwnerRedirect, OwnerSubscriptions, OwnerSubscription 
+from pubsub_event import Event, EventItem, EventRetract, EventItems, EventCollection, EventAssociate, EventDisassociate, EventConfiguration, EventPurge, EventSubscription 
diff --git a/sleekxmpp/plugins/xep_0060/stanza/base.py b/sleekxmpp/plugins/xep_0060/stanza/base.py
new file mode 100644
index 00000000..9b1efe1b
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0060/stanza/base.py
@@ -0,0 +1,24 @@
+from xml.etree import cElementTree as ET
+
+class OptionalSetting(object):
+	interfaces = set(('required',))
+
+	def setRequired(self, value):
+		value = bool(value)
+		if value and not self['required']:
+			self.xml.append(ET.Element("{%s}required" % self.namespace))
+		elif not value and self['required']:
+			self.delRequired()
+	
+	def getRequired(self):
+		required = self.xml.find("{%s}required" % self.namespace)
+		if required is not None:
+			return True
+		else:
+			return False
+	
+	def delRequired(self):
+		required = self.xml.find("{%s}required" % self.namespace)
+		if required is not None:
+			self.xml.remove(required)
+
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
new file mode 100644
index 00000000..4d586cae
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
@@ -0,0 +1,277 @@
+from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
+from sleekxmpp.stanza.iq import Iq
+from sleekxmpp.stanza.message import Message
+from sleekxmpp.basexmpp import basexmpp
+from sleekxmpp.xmlstream.xmlstream import XMLStream
+import logging
+from sleekxmpp.plugins import xep_0004
+from base import OptionalSetting
+
+
+class Pubsub(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'pubsub'
+	plugin_attrib = 'pubsub'
+	interfaces = set(tuple())
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(Iq, Pubsub)
+
+
+class Affiliation(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'affiliation'
+	plugin_attrib = name
+	interfaces = set(('node', 'affiliation'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+class Affiliations(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'affiliations'
+	plugin_attrib = 'affiliations'
+	interfaces = set(tuple())
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	subitem = (Affiliation,)
+
+	def append(self, affiliation):
+		if not isinstance(affiliation, Affiliation):
+			raise TypeError
+		self.xml.append(affiliation.xml)
+		return self.iterables.append(affiliation)
+
+registerStanzaPlugin(Pubsub, Affiliations)
+
+
+class Subscription(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'subscription'
+	plugin_attrib = name
+	interfaces = set(('jid', 'node', 'subscription', 'subid'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+	def setjid(self, value):
+		self._setattr('jid', str(value))
+	
+	def getjid(self):
+		return jid(self._getattr('jid'))
+
+registerStanzaPlugin(Pubsub, Subscription)
+
+class Subscriptions(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'subscriptions'
+	plugin_attrib = 'subscriptions'
+	interfaces = set(tuple())
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	subitem = (Subscription,)
+
+registerStanzaPlugin(Pubsub, Subscriptions)
+
+
+class SubscribeOptions(ElementBase, OptionalSetting):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'subscribe-options'
+	plugin_attrib = 'suboptions'
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	interfaces = set(('required',))
+
+registerStanzaPlugin(Subscription, SubscribeOptions)
+
+class Item(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'item'
+	plugin_attrib = name
+	interfaces = set(('id', 'payload'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+	def setPayload(self, value):
+		self.xml.append(value)
+	
+	def getPayload(self):
+		childs = self.xml.getchildren()
+		if len(childs) > 0:
+			return childs[0]
+	
+	def delPayload(self):
+		for child in self.xml.getchildren():
+			self.xml.remove(child)
+
+class Items(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'items'
+	plugin_attrib = 'items'
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	subitem = (Item,)
+
+registerStanzaPlugin(Pubsub, Items)
+
+class Create(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'create'
+	plugin_attrib = name
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(Pubsub, Create)
+
+#class Default(ElementBase):
+#	namespace = 'http://jabber.org/protocol/pubsub'
+#	name = 'default'
+#	plugin_attrib = name
+#	interfaces = set(('node', 'type'))
+#	plugin_attrib_map = {}
+#	plugin_tag_map = {}
+#
+#	def getType(self):
+#		t = self._getAttr('type')
+#		if not t: t == 'leaf'
+#		return t
+#
+#registerStanzaPlugin(Pubsub, Default)
+
+class Publish(Items):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'publish'
+	plugin_attrib = name
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	subitem = (Item,)
+
+registerStanzaPlugin(Pubsub, Publish)
+
+class Retract(Items):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'retract'
+	plugin_attrib = name
+	interfaces = set(('node', 'notify'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(Pubsub, Retract)
+
+class Unsubscribe(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'unsubscribe'
+	plugin_attrib = name
+	interfaces = set(('node', 'jid'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+	def setJid(self, value):
+		self._setAttr('jid', str(value))
+	
+	def getJid(self):
+		return JID(self._getAttr('jid'))
+
+registerStanzaPlugin(Pubsub, Unsubscribe)
+
+class Subscribe(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'subscribe'
+	plugin_attrib = name
+	interfaces = set(('node', 'jid'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+	def setJid(self, value):
+		self._setAttr('jid', str(value))
+	
+	def getJid(self):
+		return JID(self._getAttr('jid'))
+
+registerStanzaPlugin(Pubsub, Subscribe)
+
+class Configure(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'configure'
+	plugin_attrib = name
+	interfaces = set(('node', 'type'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+	def getType(self):
+		t = self._getAttr('type')
+		if not t: t == 'leaf'
+		return t
+	
+registerStanzaPlugin(Pubsub, Configure)
+registerStanzaPlugin(Configure, xep_0004.Form)
+
+class Options(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub'
+	name = 'options'
+	plugin_attrib = 'options'
+	interfaces = set(('jid', 'node', 'options'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+	def __init__(self, *args, **kwargs):
+		ElementBase.__init__(self, *args, **kwargs)
+		
+	def getOptions(self):
+		config = self.xml.find('{jabber:x:data}x')
+		form = xep_0004.Form()
+		if config is not None:
+			form.fromXML(config)
+		return form
+	
+	def setOptions(self, value):
+		self.xml.append(value.getXML())
+		return self
+	
+	def delOptions(self):
+		config = self.xml.find('{jabber:x:data}x')
+		self.xml.remove(config)
+	
+	def setJid(self, value):
+		self._setAttr('jid', str(value))
+	
+	def getJid(self):
+		return JID(self._getAttr('jid'))
+
+registerStanzaPlugin(Pubsub, Options)
+registerStanzaPlugin(Subscribe, Options)
+
+class PubsubState(ElementBase):
+    namespace = 'http://jabber.org/protocol/psstate'
+    name = 'state'
+    plugin_attrib = 'psstate'
+    interfaces = set(('node', 'item', 'payload'))
+    plugin_attrib_map = {}
+    plugin_tag_map = {}
+    
+    def setPayload(self, value):
+        self.xml.append(value)
+    
+    def getPayload(self):
+        childs = self.xml.getchildren()
+        if len(childs) > 0:
+            return childs[0]
+    
+    def delPayload(self):
+        for child in self.xml.getchildren():
+            self.xml.remove(child)
+
+registerStanzaPlugin(Iq, PubsubState)
+
+class PubsubStateEvent(ElementBase):
+    namespace = 'http://jabber.org/protocol/psstate#event'
+    name = 'event'
+    plugin_attrib = 'psstate_event'
+    intefaces = set(tuple())
+    plugin_attrib_map = {}
+    plugin_tag_map = {}
+
+registerStanzaPlugin(Message, PubsubStateEvent)
+registerStanzaPlugin(PubsubStateEvent, PubsubState)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py
new file mode 100644
index 00000000..2dfe6c4a
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py
@@ -0,0 +1,124 @@
+from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
+from sleekxmpp.stanza.iq import Iq
+from sleekxmpp.stanza.message import Message
+from sleekxmpp.basexmpp import basexmpp
+from sleekxmpp.xmlstream.xmlstream import XMLStream
+import logging
+from sleekxmpp.plugins import xep_0004
+
+class Event(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'event'
+	plugin_attrib = 'pubsub_event'
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(Message, Event)
+
+class EventItem(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'item'
+	plugin_attrib = 'item'
+	interfaces = set(('id', 'payload'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+	def setPayload(self, value):
+		self.xml.append(value)
+	
+	def getPayload(self):
+		childs = self.xml.getchildren()
+		if len(childs) > 0:
+			return childs[0]
+	
+	def delPayload(self):
+		for child in self.xml.getchildren():
+			self.xml.remove(child)
+
+
+class EventRetract(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'retract'
+	plugin_attrib = 'retract'
+	interfaces = set(('id',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+class EventItems(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'items'
+	plugin_attrib = 'items'
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	subitem = (EventItem, EventRetract)
+
+registerStanzaPlugin(Event, EventItems)
+
+class EventCollection(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'collection'
+	plugin_attrib = name
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(Event, EventCollection)
+
+class EventAssociate(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'associate'
+	plugin_attrib = name
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(EventCollection, EventAssociate)
+
+class EventDisassociate(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'disassociate'
+	plugin_attrib = name
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(EventCollection, EventDisassociate)
+
+class EventConfiguration(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'configuration'
+	plugin_attrib = name
+	interfaces = set(('node', 'config'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+registerStanzaPlugin(Event, EventConfiguration)
+registerStanzaPlugin(EventConfiguration, xep_0004.Form)
+
+class EventPurge(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'purge'
+	plugin_attrib = name
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(Event, EventPurge)
+
+class EventSubscription(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#event'
+	name = 'subscription'
+	plugin_attrib = name
+	interfaces = set(('node','expiry', 'jid', 'subid', 'subscription'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+	def setJid(self, value):
+		self._setAttr('jid', str(value))
+	
+	def getJid(self):
+		return JID(self._getAttr('jid'))
+
+registerStanzaPlugin(Event, EventSubscription)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
new file mode 100644
index 00000000..05889611
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
@@ -0,0 +1,152 @@
+from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
+from sleekxmpp.stanza.iq import Iq
+from sleekxmpp.stanza.message import Message
+from sleekxmpp.basexmpp import basexmpp
+from sleekxmpp.xmlstream.xmlstream import XMLStream
+import logging
+from sleekxmpp.plugins import xep_0004
+from base import OptionalSetting
+from pubsub import Affiliations, Affiliation, Configure, Subscriptions
+
+class PubsubOwner(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	name = 'pubsub'
+	plugin_attrib = 'pubsub_owner'
+	interfaces = set(tuple())
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(Iq, PubsubOwner)
+
+class DefaultConfig(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	name = 'default'
+	plugin_attrib = 'default'
+	interfaces = set(('node', 'type', 'config'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+	def __init__(self, *args, **kwargs):
+		ElementBase.__init__(self, *args, **kwargs)
+
+	def getType(self):
+		t = self._getAttr('type')
+		if not t: t = 'leaf'
+		return t
+	
+	def getConfig(self):
+		return self['form']
+	
+	def setConfig(self, value):
+		self['form'].setStanzaValues(value.getStanzaValues())
+		return self
+
+registerStanzaPlugin(PubsubOwner, DefaultConfig)
+registerStanzaPlugin(DefaultConfig, xep_0004.Form)
+
+class OwnerAffiliations(Affiliations):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	interfaces = set(('node'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+	def append(self, affiliation):
+		if not isinstance(affiliation, OwnerAffiliation):
+			raise TypeError
+		self.xml.append(affiliation.xml)
+		return self.affiliations.append(affiliation)
+
+registerStanzaPlugin(PubsubOwner, OwnerAffiliations)
+
+class OwnerAffiliation(Affiliation):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	interfaces = set(('affiliation', 'jid'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+class OwnerConfigure(Configure):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	interfaces = set(('node', 'config'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(PubsubOwner, OwnerConfigure)
+
+class OwnerDefault(OwnerConfigure):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	interfaces = set(('node', 'config'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+	def getConfig(self):
+		return self['form']
+	
+	def setConfig(self, value):
+		self['form'].setStanzaValues(value.getStanzaValues())
+		return self
+
+registerStanzaPlugin(PubsubOwner, OwnerDefault)
+registerStanzaPlugin(OwnerDefault, xep_0004.Form)
+
+class OwnerDelete(ElementBase, OptionalSetting):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	name = 'delete'
+	plugin_attrib = 'delete'
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	interfaces = set(('node',))
+
+registerStanzaPlugin(PubsubOwner, OwnerDelete)
+
+class OwnerPurge(ElementBase, OptionalSetting):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	name = 'purge'
+	plugin_attrib = name
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+registerStanzaPlugin(PubsubOwner, OwnerPurge)
+
+class OwnerRedirect(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	name = 'redirect'
+	plugin_attrib = name
+	interfaces = set(('node', 'jid'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+	def setJid(self, value):
+		self._setAttr('jid', str(value))
+	
+	def getJid(self):
+		return JID(self._getAttr('jid'))
+
+registerStanzaPlugin(OwnerDelete, OwnerRedirect)
+
+class OwnerSubscriptions(Subscriptions):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	interfaces = set(('node',))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+	
+	def append(self, subscription):
+		if not isinstance(subscription, OwnerSubscription):
+			raise TypeError
+		self.xml.append(subscription.xml)
+		return self.subscriptions.append(subscription)
+
+registerStanzaPlugin(PubsubOwner, OwnerSubscriptions)
+
+class OwnerSubscription(ElementBase):
+	namespace = 'http://jabber.org/protocol/pubsub#owner'
+	name = 'subscription'
+	plugin_attrib = name
+	interfaces = set(('jid', 'subscription'))
+	plugin_attrib_map = {}
+	plugin_tag_map = {}
+
+	def setJid(self, value):
+		self._setAttr('jid', str(value))
+	
+	def getJid(self):
+		return JID(self._getAttr('from'))
diff --git a/tests/test_stanza_xep_0060.py b/tests/test_stanza_xep_0060.py
index 8e6e820d..d42c11bd 100644
--- a/tests/test_stanza_xep_0060.py
+++ b/tests/test_stanza_xep_0060.py
@@ -1,6 +1,6 @@
 from sleekxmpp.test import *
 import sleekxmpp.plugins.xep_0004 as xep_0004
-import sleekxmpp.plugins.stanza_pubsub as pubsub
+import sleekxmpp.plugins.xep_0060.stanza as pubsub
 
 
 class TestPubsubStanzas(SleekTest):
-- 
cgit v1.2.3