From f9c432ca127d6e7d87b49d2fbf6aace34bea0e06 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Wed, 21 Sep 2011 13:25:27 +0100
Subject: Add support for kicking people from MUCs.

This also introduces a new DOM-like parser structure, used for the
MUC parsers.

Partially
Resolves: #689

diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index aa0a1e7..19d7b4d 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -23,6 +23,7 @@
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
 #include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
 #include <Swiften/Avatars/AvatarManager.h>
 #include <Swiften/Elements/Delay.h>
 #include <Swiften/MUC/MUC.h>
@@ -68,6 +69,8 @@ MUCController::MUCController (
 	chatWindow_->setTabComplete(completer_);
 	chatWindow_->setName(muc->getJID().getNode());
 	chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this));
+	chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1));
+	chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2));
 	muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
 	muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1));
 	muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
@@ -97,6 +100,21 @@ MUCController::~MUCController() {
 	delete completer_;
 }
 
+void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item) {
+	std::vector<ChatWindow::OccupantAction> actions;
+	//FIXME
+	if (item) {
+		actions.push_back(ChatWindow::Kick);
+	}
+	chatWindow_->setAvailableOccupantActions(actions);
+}
+
+void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction action, ContactRosterItem* item) {
+	switch (action) {
+		case ChatWindow::Kick: muc_->kickUser(item->getJID());break;
+	}
+}
+
 void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) {
 	ChatWindow::Tristate support = ChatWindow::Yes;
 	bool any = false;
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 3a79920..39e5fa4 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -19,11 +19,12 @@
 #include <Swiften/JID/JID.h>
 #include <Swiften/MUC/MUC.h>
 #include <Swiften/Elements/MUCOccupant.h>
+#include <Swift/Controllers/Roster/RosterItem.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
 
 namespace Swift {
 	class StanzaChannel;
 	class IQRouter;
-	class ChatWindow;
 	class ChatWindowFactory;
 	class Roster;
 	class AvatarManager;
@@ -64,6 +65,8 @@ namespace Swift {
 		private:
 			void clearPresenceQueue();
 			void addPresenceMessage(const std::string& message);
+			void handleWindowOccupantSelectionChanged(ContactRosterItem* item);
+			void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item);
 			void handleWindowClosed();
 			void handleAvatarChanged(const JID& jid);
 			void handleOccupantJoined(const MUCOccupant& occupant);
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index e84116d..faef5c8 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -22,11 +22,14 @@ namespace Swift {
 	class TreeWidget;
 	class Roster;
 	class TabComplete;
+	class RosterItem;
+	class ContactRosterItem;
 
 	class ChatWindow {
 		public:
 			enum AckState {Pending, Received, Failed};
 			enum Tristate {Yes, No, Maybe};
+			enum OccupantAction {Kick};
 			ChatWindow() {}
 			virtual ~ChatWindow() {};
 
@@ -72,6 +75,10 @@ namespace Swift {
 			 */
 			virtual void cancelAlert() = 0;
 
+			/**
+			 * Actions that can be performed on the selected occupant.
+			 */
+			virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions) = 0;
 			boost::signal<void ()> onClosed;
 			boost::signal<void ()> onAllMessagesRead;
 			boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest;
@@ -79,6 +86,8 @@ namespace Swift {
 			boost::signal<void ()> onUserTyping;
 			boost::signal<void ()> onUserCancelsTyping;
 			boost::signal<void ()> onAlertButtonClicked;
+			boost::signal<void (ContactRosterItem*)> onOccupantSelectionChanged;
+			boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected;
 	};
 }
 #endif
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 7d6828f..574248f 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -40,6 +40,7 @@ namespace Swift {
 			virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {};
 			virtual void cancelAlert() {};
 			virtual void setCorrectionEnabled(Tristate /*enabled*/) {}
+			void setAvailableOccupantActions(const std::vector<OccupantAction>& actions) {}
 
 			boost::signal<void ()> onClosed;
 			boost::signal<void ()> onAllMessagesRead;
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index a52d2de..a36bc32 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -7,7 +7,9 @@
 #include "QtChatWindow.h"
 #include "QtSwiftUtil.h"
 #include "Swift/Controllers/Roster/Roster.h"
-#include "Roster/QtTreeWidget.h"
+#include "Swift/Controllers/Roster/RosterItem.h"
+#include "Swift/Controllers/Roster/ContactRosterItem.h"
+#include "Roster/QtOccupantListWidget.h"
 #include "SwifTools/Linkify.h"
 #include "QtChatView.h"
 #include "MessageSnippet.h"
@@ -70,7 +72,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	messageLog_ = new QtChatView(theme, this);
 	logRosterSplitter_->addWidget(messageLog_);
 
-	treeWidget_ = new QtTreeWidget(eventStream_);
+	treeWidget_ = new QtOccupantListWidget(eventStream_, this);
 	treeWidget_->hide();
 	logRosterSplitter_->addWidget(treeWidget_);
 	logRosterSplitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
@@ -115,7 +117,8 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	connect(messageLog_, SIGNAL(gotFocus()), input_, SLOT(setFocus()));
 	resize(400,300);
 	connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int)));
-
+	treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1));
+	treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2));
 
 }
 
@@ -123,6 +126,9 @@ QtChatWindow::~QtChatWindow() {
 
 }
 
+void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) {
+	onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item));
+}
 
 void QtChatWindow::handleFontResized(int fontSizeSteps) {
 	messageLog_->resizeFont(fontSizeSteps);
@@ -530,4 +536,8 @@ void QtChatWindow::replaceLastMessage(const std::string& message) {
 	messageLog_->replaceLastMessage(P2QSTRING(Linkify::linkify(message)));
 }
 
+void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) {
+	treeWidget_->setAvailableOccupantActions(actions);
+}
+
 }
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index bc1045d..d38e9c6 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -23,8 +23,7 @@ class QPushButton;
 
 namespace Swift {
 	class QtChatView;
-	class QtTreeWidget;
-	class QtTreeWidgetFactory;
+	class QtOccupantListWidget;
 	class QtChatTheme;
 	class TreeWidget;
 	class QtTextEdit;
@@ -60,6 +59,7 @@ namespace Swift {
 			void setAckState(const std::string& id, AckState state);
 			void flash();
 			QByteArray getSplitterState();
+			virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions);
 
 		public slots:
 			void handleChangeSplitterState(QByteArray state);
@@ -88,13 +88,13 @@ namespace Swift {
 			void handleKeyPressEvent(QKeyEvent* event);
 			void handleSplitterMoved(int pos, int index);
 			void handleAlertButtonClicked();
-
 		private:
 			void updateTitleWithUnreadCount();
 			void tabComplete();
 			void beginCorrection();
 			void cancelCorrection();
 			std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const QString& style, const boost::posix_time::ptime& time);
+			void handleOccupantSelectionChanged(RosterItem* item);
 
 			int unreadCount_;
 			bool contactIsTyping_;
@@ -105,7 +105,7 @@ namespace Swift {
 			QtChatTheme* theme_;
 			QtTextEdit* input_;
 			QComboBox* labelsWidget_;
-			QtTreeWidget* treeWidget_;
+			QtOccupantListWidget* treeWidget_;
 			QLabel* correctingLabel_;
 			QLabel* alertLabel_;
 			QWidget* alertWidget_;
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 7c84773..9d35435 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -62,7 +62,7 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
 	contactTabLayout->setSpacing(0);
 	contactTabLayout->setContentsMargins(0, 0, 0, 0);
 
-	treeWidget_ = new QtRosterWidget(uiEventStream_);
+	treeWidget_ = new QtRosterWidget(uiEventStream_, this);
 	contactTabLayout->addWidget(treeWidget_);
 
 	tabs_->addTab(contactsTabWidget_, tr("&Contacts"));
@@ -115,7 +115,7 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
 	connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction()));
 	actionsMenu->addAction(signOutAction);
 
-	connect(treeWidget_, SIGNAL(onSomethingSelectedChanged(bool)), editUserAction_, SLOT(setEnabled(bool)));
+	treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QAction::setEnabled, editUserAction_, _1));
 
 	setAvailableAdHocCommands(std::vector<DiscoItems::Item>());
 	QAction* adHocAction = new QAction(tr("Collecting commands..."), this);
diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.cpp b/Swift/QtUI/Roster/QtOccupantListWidget.cpp
index 2f992bf..cbda0f1 100644
--- a/Swift/QtUI/Roster/QtOccupantListWidget.cpp
+++ b/Swift/QtUI/Roster/QtOccupantListWidget.cpp
@@ -9,6 +9,7 @@
 
 #include <QContextMenuEvent>
 #include <QMenu>
+#include <QAction>
 #include <QInputDialog>
 
 #include "Swift/Controllers/Roster/ContactRosterItem.h"
@@ -26,33 +27,33 @@ QtOccupantListWidget::~QtOccupantListWidget() {
 
 }
 
+void QtOccupantListWidget::setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions) {
+	availableOccupantActions_ = actions;
+}
+
 void QtOccupantListWidget::contextMenuEvent(QContextMenuEvent* event) {
-//	QModelIndex index = indexAt(event->pos());
-//	if (!index.isValid()) {
-//		return;
-//	}
-//	RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
-//	QMenu contextMenu;
-//	if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) {
-//		QAction* editContact = contextMenu.addAction(tr("Edit"));
-//		QAction* removeContact = contextMenu.addAction(tr("Remove"));
-//		QAction* result = contextMenu.exec(event->globalPos());
-//		if (result == editContact) {
-//			eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID()));
-//		}
-//		else if (result == removeContact) {
-//			if (QtContactEditWindow::confirmContactDeletion(contact->getJID())) {
-//				eventStream_->send(boost::make_shared<RemoveRosterItemUIEvent>(contact->getJID()));
-//			}
-//		}
-//	}
-//	else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) {
-//		QAction* renameGroupAction = contextMenu.addAction(tr("Rename"));
-//		QAction* result = contextMenu.exec(event->globalPos());
-//		if (result == renameGroupAction) {
-//			renameGroup(group);
-//		}
-//	}
+	QModelIndex index = indexAt(event->pos());
+	if (!index.isValid()) {
+		return;
+	}
+	RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
+	ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
+	if (contact) {
+		QMenu contextMenu;
+		std::map<QAction*, ChatWindow::OccupantAction> actions;
+		foreach (ChatWindow::OccupantAction availableAction, availableOccupantActions_) {
+			QString text = "Error: missing string";
+			switch (availableAction) {
+				case ChatWindow::Kick: text = tr("Kick user"); break;
+			}
+			QAction* action = contextMenu.addAction(text);
+			actions[action] = availableAction;
+		}
+		QAction* result = contextMenu.exec(event->globalPos());
+		if (result) {
+			onOccupantActionSelected(actions[result], contact);
+		}
+	}
 }
 
 }
diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.h b/Swift/QtUI/Roster/QtOccupantListWidget.h
index 5444210..ef29c00 100644
--- a/Swift/QtUI/Roster/QtOccupantListWidget.h
+++ b/Swift/QtUI/Roster/QtOccupantListWidget.h
@@ -8,6 +8,9 @@
 
 #include "Swift/QtUI/Roster/QtTreeWidget.h"
 
+#include "Swiften/Base/boost_bsignals.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
+
 namespace Swift {
 
 class QtOccupantListWidget : public QtTreeWidget {
@@ -15,8 +18,12 @@ class QtOccupantListWidget : public QtTreeWidget {
 	public:
 		QtOccupantListWidget(UIEventStream* eventStream, QWidget* parent = 0);
 		virtual ~QtOccupantListWidget();
+		void setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions);
+		boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected;
 	protected:
 		void contextMenuEvent(QContextMenuEvent* event);
+	private:
+		std::vector<ChatWindow::OccupantAction> availableOccupantActions_;
 };
 
 }
diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp
index 79d7f67..923f977 100644
--- a/Swift/QtUI/Roster/QtRosterWidget.cpp
+++ b/Swift/QtUI/Roster/QtRosterWidget.cpp
@@ -81,20 +81,4 @@ void QtRosterWidget::renameGroup(GroupRosterItem* group) {
 	}
 }
 
-void QtRosterWidget::currentChanged(const QModelIndex& current, const QModelIndex& previous) {
-	bool valid = false;
-	QModelIndexList selectedIndexList = getSelectedIndexes();
-	if (selectedIndexList.empty() || !selectedIndexList[0].isValid()) {
-		/* I didn't quite understand why using current didn't seem to work here.*/
-	}
-	else if (current.isValid()) {
-		RosterItem* item = static_cast<RosterItem*>(current.internalPointer());
-		if (dynamic_cast<ContactRosterItem*>(item)) {
-			valid = true;
-		}
-	}
-	onSomethingSelectedChanged(valid);
-	QTreeView::currentChanged(current, previous);
-}
-
 }
diff --git a/Swift/QtUI/Roster/QtRosterWidget.h b/Swift/QtUI/Roster/QtRosterWidget.h
index 7781e07..f870237 100644
--- a/Swift/QtUI/Roster/QtRosterWidget.h
+++ b/Swift/QtUI/Roster/QtRosterWidget.h
@@ -9,7 +9,6 @@
 #include "Swift/QtUI/Roster/QtTreeWidget.h"
 
 namespace Swift {
-
 class QtRosterWidget : public QtTreeWidget {
 	Q_OBJECT
 	public:
@@ -17,12 +16,8 @@ class QtRosterWidget : public QtTreeWidget {
 		virtual ~QtRosterWidget();
 	public slots:
 		void handleEditUserActionTriggered(bool checked);
-	signals:
-		void onSomethingSelectedChanged(bool);
 	protected:
 		void contextMenuEvent(QContextMenuEvent* event);
-	protected slots:
-		virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous);
 	private:
 		void renameGroup(GroupRosterItem* group);
 };
diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp
index 7de4028..96a078b 100644
--- a/Swift/QtUI/Roster/QtTreeWidget.cpp
+++ b/Swift/QtUI/Roster/QtTreeWidget.cpp
@@ -81,6 +81,21 @@ QModelIndexList QtTreeWidget::getSelectedIndexes() const {
 	return selectedIndexList;
 }
 
+void QtTreeWidget::currentChanged(const QModelIndex& current, const QModelIndex& previous) {
+	RosterItem* item = NULL;
+	QModelIndexList selectedIndexList = getSelectedIndexes();
+	if (selectedIndexList.empty() || !selectedIndexList[0].isValid()) {
+		/* I didn't quite understand why using current didn't seem to work here.*/
+	}
+	else if (current.isValid()) {
+		item = static_cast<RosterItem*>(current.internalPointer());
+		item = dynamic_cast<ContactRosterItem*>(item);
+	}
+	onSomethingSelectedChanged(item);
+	QTreeView::currentChanged(current, previous);
+}
+
+
 void QtTreeWidget::handleItemActivated(const QModelIndex& index) {
 	RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
 	ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h
index c45a1cd..1ad56d6 100644
--- a/Swift/QtUI/Roster/QtTreeWidget.h
+++ b/Swift/QtUI/Roster/QtTreeWidget.h
@@ -23,6 +23,7 @@ class QtTreeWidget : public QTreeView{
 		QtTreeWidgetItem* getRoot();
 		void setRosterModel(Roster* roster);
 		Roster* getRoster() {return roster_;}
+		boost::signal<void (RosterItem*)> onSomethingSelectedChanged;
 
 	private slots:
 		void handleItemActivated(const QModelIndex&);
@@ -35,7 +36,8 @@ class QtTreeWidget : public QTreeView{
 		QModelIndexList getSelectedIndexes() const;
 	private:
 		void drawBranches(QPainter*, const QRect&, const QModelIndex&) const;
-	
+	protected slots:
+		virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous);
 	protected:
 		UIEventStream* eventStream_;
 
diff --git a/Swiften/Elements/MUCAdminPayload.h b/Swiften/Elements/MUCAdminPayload.h
new file mode 100644
index 0000000..7b3da20
--- /dev/null
+++ b/Swiften/Elements/MUCAdminPayload.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/Payload.h>
+#include <Swiften/Elements/MUCOccupant.h>
+#include <Swiften/Elements/MUCItem.h>
+
+namespace Swift {
+	class MUCAdminPayload : public Payload {
+		public:
+			typedef boost::shared_ptr<MUCAdminPayload> ref;
+
+
+			MUCAdminPayload() {
+			}
+
+			void addItem(MUCItem item) {items_.push_back(item);}
+
+			const std::vector<MUCItem>& getItems() const {return items_;}
+
+		private:
+			std::vector<MUCItem> items_;
+	};
+}
diff --git a/Swiften/Elements/MUCItem.h b/Swiften/Elements/MUCItem.h
new file mode 100644
index 0000000..86217ec
--- /dev/null
+++ b/Swiften/Elements/MUCItem.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/MUCOccupant.h>
+#include <Swiften/JID/JID.h>
+namespace Swift {
+struct MUCItem {
+	MUCItem() {}
+	boost::optional<JID> realJID;
+	boost::optional<std::string> nick;
+	boost::optional<MUCOccupant::Affiliation> affiliation;
+	boost::optional<MUCOccupant::Role> role;
+	boost::optional<JID> actor;
+	boost::optional<std::string> reason;
+};
+}
diff --git a/Swiften/Elements/MUCUserPayload.h b/Swiften/Elements/MUCUserPayload.h
index 0276964..de9f1e5 100644
--- a/Swiften/Elements/MUCUserPayload.h
+++ b/Swiften/Elements/MUCUserPayload.h
@@ -14,20 +14,13 @@
 #include <Swiften/JID/JID.h>
 #include <Swiften/Elements/Payload.h>
 #include <Swiften/Elements/MUCOccupant.h>
+#include <Swiften/Elements/MUCItem.h>
 
 namespace Swift {
 	class MUCUserPayload : public Payload {
 		public:
 			typedef boost::shared_ptr<MUCUserPayload> ref;
 
-			struct Item {
-				Item(MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation, MUCOccupant::Role role = MUCOccupant::NoRole) : affiliation(affiliation), role(role) {}
-				boost::optional<JID> realJID;
-				boost::optional<std::string> nick;
-				MUCOccupant::Affiliation affiliation;
-				MUCOccupant::Role role;
-			};
-
 			struct StatusCode {
 				StatusCode() : code(0) {}
 				int code;
@@ -48,16 +41,16 @@ namespace Swift {
 			MUCUserPayload() {
 			}
 
-			void addItem(Item item) {items_.push_back(item);}
+			void addItem(MUCItem item) {items_.push_back(item);}
 		
 			void addStatusCode(StatusCode code) {statusCodes_.push_back(code);}
 
-			const std::vector<Item>& getItems() const {return items_;}
+			const std::vector<MUCItem>& getItems() const {return items_;}
 
 			const std::vector<StatusCode>& getStatusCodes() const {return statusCodes_;}
 
 		private:
-			std::vector<Item> items_;
+			std::vector<MUCItem> items_;
 			std::vector<StatusCode> statusCodes_;
 	};
 }
diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp
index 1ade7e3..4b3960f 100644
--- a/Swiften/MUC/MUC.cpp
+++ b/Swiften/MUC/MUC.cpp
@@ -17,6 +17,7 @@
 #include <Swiften/Elements/Form.h>
 #include <Swiften/Elements/IQ.h>
 #include <Swiften/Elements/MUCUserPayload.h>
+#include <Swiften/Elements/MUCAdminPayload.h>
 #include <Swiften/Elements/MUCPayload.h>
 #include <Swiften/MUC/MUCRegistry.h>
 #include <Swiften/Queries/GenericRequest.h>
@@ -121,8 +122,8 @@ void MUC::handleIncomingPresence(Presence::ref presence) {
 	MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation);
 	boost::optional<JID> realJID;
 	if (mucPayload && mucPayload->getItems().size() > 0) {
-		role = mucPayload->getItems()[0].role;
-		affiliation = mucPayload->getItems()[0].affiliation;
+		role = mucPayload->getItems()[0].role ? mucPayload->getItems()[0].role.get() : MUCOccupant::NoRole;
+		affiliation = mucPayload->getItems()[0].affiliation ? mucPayload->getItems()[0].affiliation.get() : MUCOccupant::NoAffiliation;
 		realJID = mucPayload->getItems()[0].realJID;
 	}
 
@@ -217,6 +218,23 @@ MUCOccupant MUC::getOccupant(const std::string& nick) {
 	return occupants.find(nick)->second;
 }
 
+void MUC::kickUser(const JID& jid) {
+	MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>();
+	MUCItem item;
+	item.role = MUCOccupant::NoRole;
+	item.nick = jid.getResource();
+	mucPayload->addItem(item);
+	GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
+	request->onResponse.connect(boost::bind(&MUC::handleKickResponse, this, _1, _2, jid));
+	request->send();
+}
+
+void MUC::handleKickResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid) {
+	if (error) {
+		onKickFailed(error, jid);
+	}
+}
+
 //FIXME: Recognise Topic changes
 
 //TODO: Invites(direct/mediated)
diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h
index 828a36f..41dbc4a 100644
--- a/Swiften/MUC/MUC.h
+++ b/Swiften/MUC/MUC.h
@@ -13,6 +13,7 @@
 #include <Swiften/Elements/MUCOccupant.h>
 #include <Swiften/MUC/MUCRegistry.h>
 #include <Swiften/Elements/MUCOwnerPayload.h>
+#include <Swiften/Elements/MUCAdminPayload.h>
 
 #include <boost/shared_ptr.hpp>
 #include <Swiften/Base/boost_bsignals.h>
@@ -54,9 +55,11 @@ namespace Swift {
 			/** Get occupant information*/
 			MUCOccupant getOccupant(const std::string& nick);
 			bool hasOccupant(const std::string& nick);
+			void kickUser(const JID& jid);
 		public:
 			boost::signal<void (const std::string& /*nick*/)> onJoinComplete;
 			boost::signal<void (ErrorPayload::ref)> onJoinFailed;
+			boost::signal<void (ErrorPayload::ref, const JID&)> onKickFailed;
 			boost::signal<void (Presence::ref)> onOccupantPresenceChange;
 			boost::signal<void (const std::string&, const MUCOccupant& /*now*/, const MUCOccupant::Role& /*old*/)> onOccupantRoleChanged;
 			boost::signal<void (const std::string&, const MUCOccupant::Affiliation& /*new*/, const MUCOccupant::Affiliation& /*old*/)> onOccupantAffiliationChanged;
@@ -79,6 +82,7 @@ namespace Swift {
 			void handleIncomingPresence(Presence::ref presence);
 			void internalJoin(const std::string& nick);
 			void handleCreationConfigResponse(MUCOwnerPayload::ref, ErrorPayload::ref);
+			void handleKickResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&);
 
 		private:
 			JID ownMUCJID;
diff --git a/Swiften/Parser/GenericPayloadTreeParser.cpp b/Swiften/Parser/GenericPayloadTreeParser.cpp
new file mode 100644
index 0000000..0e25cbc
--- /dev/null
+++ b/Swiften/Parser/GenericPayloadTreeParser.cpp
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Parser/GenericPayloadTreeParser.h>
+
diff --git a/Swiften/Parser/GenericPayloadTreeParser.h b/Swiften/Parser/GenericPayloadTreeParser.h
new file mode 100644
index 0000000..0030af7
--- /dev/null
+++ b/Swiften/Parser/GenericPayloadTreeParser.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <deque>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Parser/GenericPayloadParser.h>
+#include <Swiften/Parser/Tree/ParserElement.h>
+#include <Swiften/Parser/Tree/NullParserElement.h>
+
+#include <iostream>
+
+namespace Swift {
+
+
+	/**
+	 * Generic parser offering something a bit like a DOM to work with.
+	 */
+	template<typename PAYLOAD_TYPE>
+	class GenericPayloadTreeParser : public GenericPayloadParser<PAYLOAD_TYPE> {
+		public:
+			virtual void handleStartElement(const std::string& element, const std::string& xmlns, const AttributeMap& attributes) {
+				//std::cerr << element << ", " << xmlns << ", " << attributes.getEntries().size();
+				if (!root_) {
+					root_ = boost::make_shared<ParserElement>(element, xmlns, attributes);
+					elementStack_.push_back(root_);
+				} else {
+					ParserElement::ref current = *elementStack_.rbegin();
+					elementStack_.push_back(current->addChild(element, xmlns, attributes));
+				}
+			}
+
+			virtual void handleEndElement(const std::string& /*element*/, const std::string&) {
+				elementStack_.pop_back();
+				if (elementStack_.empty()) {
+					handleTree(root_);
+				}
+			}
+
+			virtual void handleCharacterData(const std::string& data) {
+				ParserElement::ref current = *elementStack_.rbegin();
+				current->appendCharacterData(data);
+			}
+
+			virtual void handleTree(ParserElement::ref root) = 0;
+
+		private:
+			std::deque<ParserElement::ref> elementStack_;
+			ParserElement::ref root_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index e9c49ee..1b59f6f 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -44,6 +44,7 @@
 #include <Swiften/Parser/PayloadParsers/PrivateStorageParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/DelayParser.h>
 #include <Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h>
 #include <Swiften/Parser/PayloadParsers/NicknameParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/ReplaceParser.h>
 #include <Swiften/Parser/PayloadParsers/LastParser.h>
@@ -88,6 +89,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new PrivateStorageParserFactory(this)));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new ChatStateParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new MUCUserPayloadParserFactory()));
+	factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<MUCAdminPayloadParser>("query", "http://jabber.org/protocol/muc#admin")));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new NicknameParserFactory()));
 	foreach(shared_ptr<PayloadParserFactory> factory, factories_) {
 		addFactory(factory.get());
diff --git a/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.cpp b/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.cpp
new file mode 100644
index 0000000..137ead4
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Elements/MUCOccupant.h>
+
+namespace Swift {
+
+void MUCAdminPayloadParser::handleTree(ParserElement::ref root) {
+	foreach (ParserElement::ref itemElement, root->getChildren("item", "http://jabber.org/protocol/muc#admin")) {
+		MUCItem item = MUCItemParser::itemFromTree(itemElement);
+		getPayloadInternal()->addItem(item);
+	}
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h b/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h
new file mode 100644
index 0000000..dfa26da
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Elements/MUCAdminPayload.h>
+#include <Swiften/Parser/GenericPayloadTreeParser.h>
+#include <Swiften/Parser/PayloadParsers/MUCItemParser.h>
+
+namespace Swift {
+	class MUCAdminPayloadParser : public GenericPayloadTreeParser<MUCAdminPayload> {
+		public:
+			virtual void handleTree(ParserElement::ref root);
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/MUCItemParser.cpp b/Swiften/Parser/PayloadParsers/MUCItemParser.cpp
new file mode 100644
index 0000000..2eb9997
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MUCItemParser.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/MUCItemParser.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <Swiften/Elements/MUCOccupant.h>
+
+#include <cassert>
+#include <iostream>
+
+namespace Swift {
+
+MUCItem MUCItemParser::itemFromTree(ParserElement::ref root) {
+	MUCItem item;
+	std::string affiliation = root->getAttributes().getAttribute("affiliation");
+	std::string role = root->getAttributes().getAttribute("role");
+	std::string nick = root->getAttributes().getAttribute("nick");
+	std::string jid = root->getAttributes().getAttribute("jid");
+	item.affiliation = parseAffiliation(affiliation);
+	item.role = parseRole(role);
+	if (!jid.empty()) {
+		item.realJID = JID(jid);
+	}
+	if (!nick.empty()) {
+		item.nick = nick;
+	}
+	std::string xmlns = root->getNamespace();
+	std::string reason = root->getChild("reason", xmlns)->getText();
+	std::string actor = root->getChild("actor", xmlns)->getAttributes().getAttribute("jid");
+	if (!reason.empty()) {
+		item.reason = reason;
+	}
+	if (!actor.empty()) {
+		item.actor = JID(actor);
+	}
+
+	return item;
+}
+
+boost::optional<MUCOccupant::Role> MUCItemParser::parseRole(const std::string& roleString) {
+	if (roleString == "moderator") {
+		return MUCOccupant::Moderator;
+	}
+	if (roleString == "participant") {
+		return MUCOccupant::Participant;
+	}
+	if (roleString == "visitor") {
+		return MUCOccupant::Visitor;
+	}
+	if (roleString == "none") {
+		return MUCOccupant::NoRole;
+	}
+	return boost::optional<MUCOccupant::Role>();
+}
+
+boost::optional<MUCOccupant::Affiliation> MUCItemParser::parseAffiliation(const std::string& affiliationString) {
+	if (affiliationString == "owner") {
+		return MUCOccupant::Owner;
+	}
+	if (affiliationString == "admin") {
+		return MUCOccupant::Admin;
+	}
+	if (affiliationString == "member") {
+		return MUCOccupant::Member;
+	}
+	if (affiliationString == "outcast") {
+		return MUCOccupant::Outcast;
+	}
+	if (affiliationString == "none") {
+		return MUCOccupant::NoAffiliation;
+	}
+	return boost::optional<MUCOccupant::Affiliation>();
+}
+
+}
+
+
diff --git a/Swiften/Parser/PayloadParsers/MUCItemParser.h b/Swiften/Parser/PayloadParsers/MUCItemParser.h
new file mode 100644
index 0000000..a0ea060
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MUCItemParser.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/MUCItem.h>
+#include <Swiften/Parser/GenericPayloadTreeParser.h>
+
+namespace Swift {
+	class MUCItemParser  {
+		public:
+			static MUCItem itemFromTree(ParserElement::ref root);
+		private:
+			static boost::optional<MUCOccupant::Role> parseRole(const std::string& itemString);
+			static boost::optional<MUCOccupant::Affiliation> parseAffiliation(const std::string& statusString);
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp
index bd81b88..71ae571 100644
--- a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp
+++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp
@@ -8,87 +8,24 @@
 
 #include <boost/lexical_cast.hpp>
 
+#include <Swiften/Base/foreach.h>
 #include <Swiften/Elements/MUCOccupant.h>
 
-#include <cassert>
-#include <iostream>
-
 namespace Swift {
 
-MUCUserPayloadParser::MUCUserPayloadParser() : level(TopLevel) {
-}
-
-void MUCUserPayloadParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) {
-	if (level == ItemLevel) {
-		if (element == "item") {
-			MUCUserPayload::Item item;
-			std::string affiliation = attributes.getAttribute("affiliation");
-			std::string role = attributes.getAttribute("role");
-			std::string nick = attributes.getAttribute("nick");
-			std::string jid = attributes.getAttribute("jid");
-			item.affiliation = parseAffiliation(affiliation);
-			item.role = parseRole(role);
-			if (!jid.empty()) {
-				item.realJID = JID(jid);
-			}
-			if (!nick.empty()) {
-				item.nick = nick;
-			}
-			getPayloadInternal()->addItem(item);
-		} else if (element == "status") {
-			MUCUserPayload::StatusCode status;
-			try {
-				status.code = boost::lexical_cast<int>(attributes.getAttribute("code").c_str());
-				getPayloadInternal()->addStatusCode(status);
-			} catch (boost::bad_lexical_cast&) {
-			}
+void MUCUserPayloadParser::handleTree(ParserElement::ref root) {
+	foreach (ParserElement::ref itemElement, root->getChildren("item", "http://jabber.org/protocol/muc#user")) {
+		MUCItem item = MUCItemParser::itemFromTree(itemElement);
+		getPayloadInternal()->addItem(item);
+	}
+	foreach (ParserElement::ref statusElement, root->getChildren("item", "http://jabber.org/protocol/muc#user")) {
+		MUCUserPayload::StatusCode status;
+		try {
+			status.code = boost::lexical_cast<int>(statusElement->getAttributes().getAttribute("code").c_str());
+			getPayloadInternal()->addStatusCode(status);
+		} catch (boost::bad_lexical_cast&) {
 		}
 	}
-	++level;
-}
-
-MUCOccupant::Role MUCUserPayloadParser::parseRole(const std::string& roleString) const {
-	if (roleString == "moderator") {
-		return MUCOccupant::Moderator;
-	}
-	if (roleString == "participant") {
-		return MUCOccupant::Participant;
-	}
-	if (roleString == "visitor") {
-		return MUCOccupant::Visitor;
-	}
-	if (roleString == "none") {
-		return MUCOccupant::NoRole;
-	}
-	return MUCOccupant::NoRole;
-}
-
-MUCOccupant::Affiliation MUCUserPayloadParser::parseAffiliation(const std::string& affiliationString) const {
-	if (affiliationString == "owner") {
-		return MUCOccupant::Owner;
-	}
-	if (affiliationString == "admin") {
-		return MUCOccupant::Admin;
-	}
-	if (affiliationString == "member") {
-		return MUCOccupant::Member;
-	}
-	if (affiliationString == "outcast") {
-		return MUCOccupant::Outcast;
-	}
-	if (affiliationString == "none") {
-		return MUCOccupant::NoAffiliation;
-	}
-	return MUCOccupant::NoAffiliation;
-}
-
-
-void MUCUserPayloadParser::handleEndElement(const std::string& /*element*/, const std::string&) {
-	--level;
-}
-
-void MUCUserPayloadParser::handleCharacterData(const std::string& /*data*/) {
-
 }
 
 }
diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h
index b819905..e020127 100644
--- a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h
+++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h
@@ -9,23 +9,12 @@
 #include <boost/optional.hpp>
 
 #include <Swiften/Elements/MUCUserPayload.h>
-#include <Swiften/Parser/GenericPayloadParser.h>
+#include <Swiften/Parser/GenericPayloadTreeParser.h>
+#include <Swiften/Parser/PayloadParsers/MUCItemParser.h>
 
 namespace Swift {
-	class MUCUserPayloadParser : public GenericPayloadParser<MUCUserPayload> {
+	class MUCUserPayloadParser : public GenericPayloadTreeParser<MUCUserPayload> {
 		public:
-			MUCUserPayloadParser();
-
-			virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes);
-			virtual void handleEndElement(const std::string& element, const std::string&);
-			virtual void handleCharacterData(const std::string& data);
-			MUCOccupant::Role parseRole(const std::string& itemString) const;
-			MUCOccupant::Affiliation parseAffiliation(const std::string& statusString) const;
-		private:
-			enum Level { 
-				TopLevel = 0, 
-				ItemLevel = 1
-			};
-			int level;
+			virtual void handleTree(ParserElement::ref root);
 	};
 }
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp
new file mode 100644
index 0000000..0648f58
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h>
+#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h>
+
+using namespace Swift;
+
+class MUCAdminPayloadParserTest : public CppUnit::TestFixture
+{
+		CPPUNIT_TEST_SUITE(MUCAdminPayloadParserTest);
+		CPPUNIT_TEST(testParse);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		MUCAdminPayloadParserTest() {}
+
+		void testParse() {
+			PayloadsParserTester parser;
+
+			CPPUNIT_ASSERT(parser.parse("<query xmlns=\"http://jabber.org/protocol/muc#admin\"><item affiliation=\"owner\" role=\"visitor\"><actor jid=\"kev@tester.lit\"/><reason>malice</reason></item></query>"));
+
+			MUCAdminPayload::ref payload = boost::dynamic_pointer_cast<MUCAdminPayload>(parser.getPayload());
+			MUCItem item = payload->getItems()[0];
+			CPPUNIT_ASSERT_EQUAL(MUCOccupant::Owner, item.affiliation.get());
+			CPPUNIT_ASSERT_EQUAL(MUCOccupant::Visitor, item.role.get());
+			CPPUNIT_ASSERT_EQUAL(JID("kev@tester.lit"), item.actor.get());
+			CPPUNIT_ASSERT_EQUAL(std::string("malice"), item.reason.get());
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(MUCAdminPayloadParserTest);
+
+
+
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index 17505f1..b9fcebb 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -14,6 +14,7 @@ sources = [
 		"CompressParser.cpp",
 		"ElementParser.cpp",
 		"IQParser.cpp",
+		"GenericPayloadTreeParser.cpp",
 		"MessageParser.cpp",
 		"PayloadParser.cpp",
 		"StanzaAckParser.cpp",
@@ -51,6 +52,8 @@ sources = [
 		"PayloadParsers/VCardUpdateParser.cpp",
 		"PayloadParsers/DelayParser.cpp",
 		"PayloadParsers/MUCUserPayloadParser.cpp",
+		"PayloadParsers/MUCAdminPayloadParser.cpp",
+		"PayloadParsers/MUCItemParser.cpp",
 		"PayloadParsers/NicknameParser.cpp",
 		"PayloadParsers/ReplaceParser.cpp",
 		"PayloadParsers/LastParser.cpp",
@@ -63,6 +66,7 @@ sources = [
 		"StreamManagementEnabledParser.cpp",
 		"StreamResumeParser.cpp",
 		"StreamResumedParser.cpp",
+		"Tree/ParserElement.cpp",
 		"XMLParser.cpp",
 		"XMLParserClient.cpp",
 		"XMLParserFactory.cpp",
diff --git a/Swiften/Parser/Tree/NullParserElement.h b/Swiften/Parser/Tree/NullParserElement.h
new file mode 100644
index 0000000..93c0662
--- /dev/null
+++ b/Swiften/Parser/Tree/NullParserElement.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <Swiften/Parser/Tree/ParserElement.h>
+
+namespace Swift {
+
+	class NullParserElement : public ParserElement {
+		public:
+			NullParserElement() : ParserElement("", "", AttributeMap()) {}
+			virtual operator bool() {return false;};
+	};
+}
diff --git a/Swiften/Parser/Tree/ParserElement.cpp b/Swiften/Parser/Tree/ParserElement.cpp
new file mode 100644
index 0000000..c851b41
--- /dev/null
+++ b/Swiften/Parser/Tree/ParserElement.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+
+#include <Swiften/Parser/Tree/ParserElement.h>
+#include <Swiften/Parser/Tree/NullParserElement.h>
+
+#include <iostream>
+
+namespace Swift{
+
+ParserElement::ParserElement(const std::string& name, const std::string& xmlns, const AttributeMap& attributes) : name_(name), xmlns_(xmlns), attributes_(attributes) {
+
+}
+
+ParserElement::~ParserElement() {
+
+}
+
+ParserElement::operator bool() {
+	return true;
+}
+
+ParserElement::ref ParserElement::addChild(const std::string& name, const std::string& xmlns, const AttributeMap& attributes) {
+	ParserElement::ref child = boost::make_shared<ParserElement>(name, xmlns, attributes);
+	children_.push_back(child);
+	return child;
+}
+
+void ParserElement::appendCharacterData(const std::string& data) {
+	text_ += data;
+}
+
+std::string ParserElement::getText() {
+	return text_;
+}
+
+std::string ParserElement::getName() {
+	return name_;
+}
+
+std::string ParserElement::getNamespace() {
+	return xmlns_;
+}
+
+struct DoesntMatch {
+	public:
+		DoesntMatch(const std::string& tagName, const std::string& ns) : tagName(tagName), ns(ns) {}
+		bool operator()(ParserElement::ref element) { return element->getName() != tagName || element->getNamespace() != ns; }
+	private:
+		std::string tagName;
+		std::string ns;
+};
+
+
+std::vector<ParserElement::ref> ParserElement::getChildren(const std::string& name, const std::string& xmlns) {
+	std::vector<ParserElement::ref> result;
+	std::remove_copy_if(children_.begin(), children_.end(), std::back_inserter(result), DoesntMatch(name, xmlns));
+	return result;
+}
+
+ParserElement::ref ParserElement::getChild(const std::string& name, const std::string& xmlns) {
+	std::vector<ParserElement::ref> results = getChildren(name, xmlns);
+	boost::shared_ptr<NullParserElement> nullParser = boost::make_shared<NullParserElement>();
+	ParserElement::ref result = results.empty() ? boost::dynamic_pointer_cast<ParserElement>(nullParser) : results[0];
+	return result;
+}
+
+}
diff --git a/Swiften/Parser/Tree/ParserElement.h b/Swiften/Parser/Tree/ParserElement.h
new file mode 100644
index 0000000..ddf67fa
--- /dev/null
+++ b/Swiften/Parser/Tree/ParserElement.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Parser/AttributeMap.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+namespace Swift {
+class ParserElement {
+	public:
+		typedef boost::shared_ptr<ParserElement> ref;
+		ParserElement(const std::string& name, const std::string& xmlns, const AttributeMap& attributes);
+		virtual ~ParserElement();
+		virtual operator bool();
+		ParserElement::ref addChild(const std::string& name, const std::string& xmlns, const AttributeMap& attributes);
+		void appendCharacterData(const std::string& data);
+		std::string getText();
+		std::string getName();
+		std::string getNamespace();
+		std::vector<ParserElement::ref> getChildren(const std::string& name, const std::string& xmlns);
+		ParserElement::ref getChild(const std::string& name, const std::string& xmlns);
+		const AttributeMap& getAttributes() const {return attributes_;}
+	private:
+		std::vector<ParserElement::ref> children_;
+		std::string name_;
+		std::string xmlns_;
+		AttributeMap attributes_;
+		std::string text_;
+
+};
+
+}
diff --git a/Swiften/Parser/UnitTest/GenericPayloadTreeParserTest.cpp b/Swiften/Parser/UnitTest/GenericPayloadTreeParserTest.cpp
new file mode 100644
index 0000000..d095afc
--- /dev/null
+++ b/Swiften/Parser/UnitTest/GenericPayloadTreeParserTest.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <Swiften/Parser/GenericPayloadTreeParser.h>
+#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h>
+#include <Swiften/Elements/RawXMLPayload.h>
+
+using namespace Swift;
+
+class GenericPayloadTreeParserTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(GenericPayloadTreeParserTest);
+		CPPUNIT_TEST(testTree);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testTree() {
+			MyParser testling;
+
+			std::string data = "<topLevel xmlns='urn:test:top'><firstLevelInheritedEmpty/><firstLevelInherited><secondLevelMultiChildren num='1'/><secondLevelMultiChildren num='2'/></firstLevelInherited><firstLevelNS xmlns='urn:test:first'/></topLevel>";
+
+			PayloadParserTester tester(&testling);
+			tester.parse(data);
+
+			ParserElement::ref tree = testling.tree;
+
+			CPPUNIT_ASSERT_EQUAL(std::string("topLevel"), tree->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("urn:test:top"), tree->getNamespace());
+			CPPUNIT_ASSERT(tree->getChild("firstLevelInheritedEmpty", "urn:test:top"));
+			CPPUNIT_ASSERT(!*tree->getChild("firstLevelInheritedEmpty", ""));
+			CPPUNIT_ASSERT(tree->getChild("firstLevelInherited", "urn:test:top"));
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), tree->getChild("firstLevelInherited", "urn:test:top")->getChildren("secondLevelMultiChildren", "urn:test:top").size());
+			CPPUNIT_ASSERT_EQUAL(std::string("1"), tree->getChild("firstLevelInherited", "urn:test:top")->getChildren("secondLevelMultiChildren", "urn:test:top")[0]->getAttributes().getAttribute("num"));
+			CPPUNIT_ASSERT_EQUAL(std::string("2"), tree->getChild("firstLevelInherited", "urn:test:top")->getChildren("secondLevelMultiChildren", "urn:test:top")[1]->getAttributes().getAttribute("num"));
+			CPPUNIT_ASSERT(tree->getChild("firstLevelNS", "urn:test:first"));
+		}
+
+	private:
+
+
+		class MyParser : public GenericPayloadTreeParser<RawXMLPayload>
+		{
+			public:
+				virtual ~MyParser() {}
+				virtual void handleTree(ParserElement::ref root) {
+					tree = root;
+				}
+				ParserElement::ref tree;
+		};
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GenericPayloadTreeParserTest);
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 0144ddb..e20e5e6 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -146,6 +146,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp",
 			"Serializer/PayloadSerializers/MUCPayloadSerializer.cpp",
 			"Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp",
+			"Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp",
 			"Serializer/PayloadSerializers/MUCOwnerPayloadSerializer.cpp",
 			"Serializer/PayloadSerializers/ResourceBindSerializer.cpp",
 			"Serializer/PayloadSerializers/RosterItemExchangeSerializer.cpp",
@@ -292,8 +293,10 @@ if env["SCONS_STAGE"] == "build" :
 			File("Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/VCardUpdateParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/ReplaceTest.cpp"),
+			File("Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp"),
 			File("Parser/UnitTest/AttributeMapTest.cpp"),
 			File("Parser/UnitTest/IQParserTest.cpp"),
+			File("Parser/UnitTest/GenericPayloadTreeParserTest.cpp"),
 			File("Parser/UnitTest/MessageParserTest.cpp"),
 			File("Parser/UnitTest/PayloadParserFactoryCollectionTest.cpp"),
 			File("Parser/UnitTest/PresenceParserTest.cpp"),
@@ -336,6 +339,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp"),
+			File("Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp"),
 			File("Serializer/UnitTest/StreamFeaturesSerializerTest.cpp"),
 			File("Serializer/UnitTest/AuthSuccessSerializerTest.cpp"),
 			File("Serializer/UnitTest/AuthChallengeSerializerTest.cpp"),
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 0746c37..0ddd445 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -21,6 +21,7 @@
 #include <Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MUCOwnerPayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/StatusSerializer.h>
@@ -61,6 +62,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new RosterItemExchangeSerializer());
 	serializers_.push_back(new MUCPayloadSerializer());
 	serializers_.push_back(new MUCUserPayloadSerializer());
+	serializers_.push_back(new MUCAdminPayloadSerializer());
 	serializers_.push_back(new MUCOwnerPayloadSerializer(this));
 	serializers_.push_back(new SoftwareVersionSerializer());
 	serializers_.push_back(new StatusSerializer());
diff --git a/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp
new file mode 100644
index 0000000..552f7f1
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h>
+
+#include <sstream>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+#include <Swiften/Serializer/XML/XMLTextNode.h>
+#include <Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h>
+
+
+namespace Swift {
+
+MUCAdminPayloadSerializer::MUCAdminPayloadSerializer() : GenericPayloadSerializer<MUCAdminPayload>() {
+}
+
+std::string MUCAdminPayloadSerializer::serializePayload(boost::shared_ptr<MUCAdminPayload> payload)  const {
+	XMLElement mucElement("query", "http://jabber.org/protocol/muc#admin");
+	foreach (const MUCItem item, payload->getItems()) {
+		mucElement.addNode(MUCItemSerializer::itemToElement(item));
+	}
+	return mucElement.serialize();
+}
+
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h
new file mode 100644
index 0000000..e288cd7
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+#include <Swiften/Elements/MUCAdminPayload.h>
+
+namespace Swift {
+	class MUCAdminPayloadSerializer : public GenericPayloadSerializer<MUCAdminPayload> {
+		public:
+			MUCAdminPayloadSerializer();
+			std::string affiliationToString(MUCOccupant::Affiliation affiliation) const;
+			std::string roleToString(MUCOccupant::Role role) const;
+
+			virtual std::string serializePayload(boost::shared_ptr<MUCAdminPayload> version)  const;
+	};
+}
+
diff --git a/Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h
new file mode 100644
index 0000000..7cb662c
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+#include <Swiften/Elements/MUCItem.h>
+#include <boost/smart_ptr/make_shared.hpp>
+
+namespace Swift {
+	class MUCItemSerializer {
+		public:
+			static std::string affiliationToString(MUCOccupant::Affiliation affiliation) {
+				std::string result;
+				switch (affiliation) {
+				case MUCOccupant::Owner: result = "owner"; break;
+				case MUCOccupant::Admin: result = "admin"; break;
+				case MUCOccupant::Member: result = "member"; break;
+				case MUCOccupant::Outcast: result = "outcast"; break;
+				case MUCOccupant::NoAffiliation: result = "none"; break;
+				default: assert(false);
+				}
+				return result;
+			}
+
+			static std::string roleToString(MUCOccupant::Role role) {
+				std::string result;
+				switch (role) {
+				case MUCOccupant::Moderator: result = "moderator"; break;
+				case MUCOccupant::NoRole: result = "none"; break;
+				case MUCOccupant::Participant: result = "participant"; break;
+				case MUCOccupant::Visitor: result = "visitor"; break;
+				default: assert(false);
+				}
+				return result;
+
+			}
+
+			static boost::shared_ptr<XMLElement> itemToElement(const MUCItem& item) {
+				boost::shared_ptr<XMLElement> itemElement(new XMLElement("item"));
+				if (item.affiliation) {
+					itemElement->setAttribute("affiliation", affiliationToString(item.affiliation.get()));
+				}
+				if (item.role) {
+					itemElement->setAttribute("role", roleToString(item.role.get()));
+				}
+				if (item.realJID) {
+					itemElement->setAttribute("jid", item.realJID.get());
+				}
+				if (item.nick) {
+					itemElement->setAttribute("nick", item.nick.get());
+				}
+				if (item.actor) {
+					boost::shared_ptr<XMLElement> actorElement(new XMLElement("actor"));
+					actorElement->setAttribute("jid", item.actor->toString());
+					itemElement->addNode(actorElement);
+				}
+				if (item.reason) {
+					boost::shared_ptr<XMLElement> reasonElement(new XMLElement("reason"));
+					reasonElement->addNode(boost::make_shared<XMLTextNode>(*item.reason));
+					itemElement->addNode(reasonElement);
+				}
+				return itemElement;
+			}
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp
index 44aa506..0a8153d 100644
--- a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp
@@ -13,7 +13,7 @@
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Serializer/XML/XMLElement.h>
 #include <Swiften/Serializer/XML/XMLTextNode.h>
-
+#include <Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h>
 
 namespace Swift {
 
@@ -29,46 +29,13 @@ std::string MUCUserPayloadSerializer::serializePayload(boost::shared_ptr<MUCUser
 		statusElement->setAttribute("code", code.str());
 		mucElement.addNode(statusElement);
 	}
-	foreach (const MUCUserPayload::Item item, payload->getItems()) {
-		boost::shared_ptr<XMLElement> itemElement(new XMLElement("item"));
-		itemElement->setAttribute("affiliation", affiliationToString(item.affiliation));
-		itemElement->setAttribute("role", roleToString(item.role));
-		if (item.realJID) {
-			itemElement->setAttribute("jid", item.realJID.get());
-		}
-		if (item.nick) {
-			itemElement->setAttribute("nick", item.nick.get());
-		}
-		mucElement.addNode(itemElement);
+	foreach (const MUCItem item, payload->getItems()) {
+		mucElement.addNode(MUCItemSerializer::itemToElement(item));
 	}
 	return mucElement.serialize();
 }
 
-std::string MUCUserPayloadSerializer::affiliationToString(MUCOccupant::Affiliation affiliation) const {
-	std::string result;
-	switch (affiliation) {
-	case MUCOccupant::Owner: result = "owner"; break;
-	case MUCOccupant::Admin: result = "admin"; break;
-	case MUCOccupant::Member: result = "member"; break;
-	case MUCOccupant::Outcast: result = "outcast"; break;
-	case MUCOccupant::NoAffiliation: result = "none"; break;
-	default: assert(false);
-	}
-	return result;
-}
-
-std::string MUCUserPayloadSerializer::roleToString(MUCOccupant::Role role) const {
-	std::string result;
-	switch (role) {
-	case MUCOccupant::Moderator: result = "moderator"; break;
-	case MUCOccupant::NoRole: result = "none"; break;
-	case MUCOccupant::Participant: result = "participant"; break;
-	case MUCOccupant::Visitor: result = "visitor"; break;
-	default: assert(false);
-	}
-	return result;
 
-}
 
 
 }
diff --git a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h
index 634ce22..84c4a96 100644
--- a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h
+++ b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h
@@ -13,8 +13,6 @@ namespace Swift {
 	class MUCUserPayloadSerializer : public GenericPayloadSerializer<MUCUserPayload> {
 		public:
 			MUCUserPayloadSerializer();
-			std::string affiliationToString(MUCOccupant::Affiliation affiliation) const;
-			std::string roleToString(MUCOccupant::Role role) const;
 
 			virtual std::string serializePayload(boost::shared_ptr<MUCUserPayload> version)  const;
 	};
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp
new file mode 100644
index 0000000..a8acf80
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h>
+
+using namespace Swift;
+
+class MUCAdminPayloadSerializerTest : public CppUnit::TestFixture
+{
+		CPPUNIT_TEST_SUITE(MUCAdminPayloadSerializerTest);
+		CPPUNIT_TEST(testSerialize);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		MUCAdminPayloadSerializerTest() {}
+
+		void testSerialize() {
+			MUCAdminPayloadSerializer testling;
+			boost::shared_ptr<MUCAdminPayload> admin = boost::make_shared<MUCAdminPayload>();
+			MUCItem item;
+			item.affiliation = MUCOccupant::Owner;
+			item.role = MUCOccupant::Visitor;
+			item.reason = "malice";
+			item.actor = JID("kev@tester.lit");
+			admin->addItem(item);
+
+			CPPUNIT_ASSERT_EQUAL(std::string("<query xmlns=\"http://jabber.org/protocol/muc#admin\"><item affiliation=\"owner\" role=\"visitor\"><actor jid=\"kev@tester.lit\"/><reason>malice</reason></item></query>"), testling.serialize(admin));
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(MUCAdminPayloadSerializerTest);
-- 
cgit v0.10.2-6-g49f6