mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-24 03:00:15 +00:00
Reorganize XEP-0004.
Changes: May now use underscored method names form.field is replaced by form['fields'] form.get_fields no longer accepts use_dict parameter, it always returns an OrderedDict now form.set_fields will accept either an OrderedDict, or a list of (var, dict) tuples as before. Setting the form type to 'submit' will remove extra meta data from the form fields, leaving just the 'var' and 'value' Setting the form type to 'cancel' will remove all fields.
This commit is contained in:
parent
a189cb8333
commit
9b7ed73f95
8 changed files with 538 additions and 430 deletions
|
@ -1,395 +0,0 @@
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import copy
|
|
||||||
from . import base
|
|
||||||
from .. xmlstream.handler.callback import Callback
|
|
||||||
from .. xmlstream.matcher.xpath import MatchXPath
|
|
||||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
|
||||||
from .. stanza.message import Message
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Form(ElementBase):
|
|
||||||
namespace = 'jabber:x:data'
|
|
||||||
name = 'x'
|
|
||||||
plugin_attrib = 'form'
|
|
||||||
interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values'))
|
|
||||||
sub_interfaces = set(('title',))
|
|
||||||
form_types = set(('cancel', 'form', 'result', 'submit'))
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
title = None
|
|
||||||
if 'title' in kwargs:
|
|
||||||
title = kwargs['title']
|
|
||||||
del kwargs['title']
|
|
||||||
ElementBase.__init__(self, *args, **kwargs)
|
|
||||||
if title is not None:
|
|
||||||
self['title'] = title
|
|
||||||
self.field = FieldAccessor(self)
|
|
||||||
|
|
||||||
def setup(self, xml=None):
|
|
||||||
if ElementBase.setup(self, xml): #if we had to generate xml
|
|
||||||
self['type'] = 'form'
|
|
||||||
|
|
||||||
def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs):
|
|
||||||
kwtype = kwargs.get('type', None)
|
|
||||||
if kwtype is None:
|
|
||||||
kwtype = ftype
|
|
||||||
|
|
||||||
field = FormField(parent=self)
|
|
||||||
field['var'] = var
|
|
||||||
field['type'] = kwtype
|
|
||||||
field['label'] = label
|
|
||||||
field['desc'] = desc
|
|
||||||
field['required'] = required
|
|
||||||
field['value'] = value
|
|
||||||
if options is not None:
|
|
||||||
field['options'] = options
|
|
||||||
return field
|
|
||||||
|
|
||||||
def getXML(self, type='submit'):
|
|
||||||
self['type'] = type
|
|
||||||
log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
|
|
||||||
return self.xml
|
|
||||||
|
|
||||||
def fromXML(self, xml):
|
|
||||||
log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
|
|
||||||
n = Form(xml=xml)
|
|
||||||
return n
|
|
||||||
|
|
||||||
def addItem(self, values):
|
|
||||||
itemXML = ET.Element('{%s}item' % self.namespace)
|
|
||||||
self.xml.append(itemXML)
|
|
||||||
reported_vars = self['reported'].keys()
|
|
||||||
for var in reported_vars:
|
|
||||||
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
|
||||||
itemXML.append(fieldXML)
|
|
||||||
field = FormField(xml=fieldXML)
|
|
||||||
field['var'] = var
|
|
||||||
field['value'] = values.get(var, None)
|
|
||||||
|
|
||||||
def addReported(self, var, ftype=None, label='', desc='', **kwargs):
|
|
||||||
kwtype = kwargs.get('type', None)
|
|
||||||
if kwtype is None:
|
|
||||||
kwtype = ftype
|
|
||||||
reported = self.xml.find('{%s}reported' % self.namespace)
|
|
||||||
if reported is None:
|
|
||||||
reported = ET.Element('{%s}reported' % self.namespace)
|
|
||||||
self.xml.append(reported)
|
|
||||||
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
|
||||||
reported.append(fieldXML)
|
|
||||||
field = FormField(xml=fieldXML)
|
|
||||||
field['var'] = var
|
|
||||||
field['type'] = kwtype
|
|
||||||
field['label'] = label
|
|
||||||
field['desc'] = desc
|
|
||||||
return field
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
self['type'] = 'cancel'
|
|
||||||
|
|
||||||
def delFields(self):
|
|
||||||
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
|
|
||||||
for fieldXML in fieldsXML:
|
|
||||||
self.xml.remove(fieldXML)
|
|
||||||
|
|
||||||
def delInstructions(self):
|
|
||||||
instsXML = self.xml.findall('{%s}instructions')
|
|
||||||
for instXML in instsXML:
|
|
||||||
self.xml.remove(instXML)
|
|
||||||
|
|
||||||
def delItems(self):
|
|
||||||
itemsXML = self.xml.find('{%s}item' % self.namespace)
|
|
||||||
for itemXML in itemsXML:
|
|
||||||
self.xml.remove(itemXML)
|
|
||||||
|
|
||||||
def delReported(self):
|
|
||||||
reportedXML = self.xml.find('{%s}reported' % self.namespace)
|
|
||||||
if reportedXML is not None:
|
|
||||||
self.xml.remove(reportedXML)
|
|
||||||
|
|
||||||
def getFields(self, use_dict=False):
|
|
||||||
fields = {} if use_dict else []
|
|
||||||
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
|
|
||||||
for fieldXML in fieldsXML:
|
|
||||||
field = FormField(xml=fieldXML)
|
|
||||||
if use_dict:
|
|
||||||
fields[field['var']] = field
|
|
||||||
else:
|
|
||||||
fields.append((field['var'], field))
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def getInstructions(self):
|
|
||||||
instructions = ''
|
|
||||||
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
|
|
||||||
return "\n".join([instXML.text for instXML in instsXML])
|
|
||||||
|
|
||||||
def getItems(self):
|
|
||||||
items = []
|
|
||||||
itemsXML = self.xml.findall('{%s}item' % self.namespace)
|
|
||||||
for itemXML in itemsXML:
|
|
||||||
item = {}
|
|
||||||
fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
|
|
||||||
for fieldXML in fieldsXML:
|
|
||||||
field = FormField(xml=fieldXML)
|
|
||||||
item[field['var']] = field['value']
|
|
||||||
items.append(item)
|
|
||||||
return items
|
|
||||||
|
|
||||||
def getReported(self):
|
|
||||||
fields = {}
|
|
||||||
fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
|
|
||||||
FormField.namespace))
|
|
||||||
for fieldXML in fieldsXML:
|
|
||||||
field = FormField(xml=fieldXML)
|
|
||||||
fields[field['var']] = field
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def getValues(self):
|
|
||||||
values = {}
|
|
||||||
fields = self.getFields(use_dict=True)
|
|
||||||
for var in fields:
|
|
||||||
values[var] = fields[var]['value']
|
|
||||||
return values
|
|
||||||
|
|
||||||
def reply(self):
|
|
||||||
if self['type'] == 'form':
|
|
||||||
self['type'] = 'submit'
|
|
||||||
elif self['type'] == 'submit':
|
|
||||||
self['type'] = 'result'
|
|
||||||
|
|
||||||
def setFields(self, fields, default=None):
|
|
||||||
del self['fields']
|
|
||||||
for field_data in fields:
|
|
||||||
var = field_data[0]
|
|
||||||
field = field_data[1]
|
|
||||||
field['var'] = var
|
|
||||||
|
|
||||||
self.addField(**field)
|
|
||||||
|
|
||||||
def setInstructions(self, instructions):
|
|
||||||
del self['instructions']
|
|
||||||
if instructions in [None, '']:
|
|
||||||
return
|
|
||||||
instructions = instructions.split('\n')
|
|
||||||
for instruction in instructions:
|
|
||||||
inst = ET.Element('{%s}instructions' % self.namespace)
|
|
||||||
inst.text = instruction
|
|
||||||
self.xml.append(inst)
|
|
||||||
|
|
||||||
def setItems(self, items):
|
|
||||||
for item in items:
|
|
||||||
self.addItem(item)
|
|
||||||
|
|
||||||
def setReported(self, reported, default=None):
|
|
||||||
for var in reported:
|
|
||||||
field = reported[var]
|
|
||||||
field['var'] = var
|
|
||||||
self.addReported(var, **field)
|
|
||||||
|
|
||||||
def setValues(self, values):
|
|
||||||
fields = self.getFields(use_dict=True)
|
|
||||||
for field in values:
|
|
||||||
fields[field]['value'] = values[field]
|
|
||||||
|
|
||||||
def merge(self, other):
|
|
||||||
new = copy.copy(self)
|
|
||||||
if type(other) == dict:
|
|
||||||
new.setValues(other)
|
|
||||||
return new
|
|
||||||
nfields = new.getFields(use_dict=True)
|
|
||||||
ofields = other.getFields(use_dict=True)
|
|
||||||
nfields.update(ofields)
|
|
||||||
new.setFields([(x, nfields[x]) for x in nfields])
|
|
||||||
return new
|
|
||||||
|
|
||||||
class FieldAccessor(object):
|
|
||||||
def __init__(self, form):
|
|
||||||
self.form = form
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self.form.getFields(use_dict=True)[key]
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
return key in self.form.getFields(use_dict=True)
|
|
||||||
|
|
||||||
def has_key(self, key):
|
|
||||||
return key in self.form.getFields(use_dict=True)
|
|
||||||
|
|
||||||
|
|
||||||
class FormField(ElementBase):
|
|
||||||
namespace = 'jabber:x:data'
|
|
||||||
name = 'field'
|
|
||||||
plugin_attrib = 'field'
|
|
||||||
interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var'))
|
|
||||||
sub_interfaces = set(('desc',))
|
|
||||||
field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
|
|
||||||
'list-single', 'text-multi', 'text-private', 'text-single'))
|
|
||||||
multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi'))
|
|
||||||
multi_line_types = set(('hidden', 'text-multi'))
|
|
||||||
option_types = set(('list-multi', 'list-single'))
|
|
||||||
true_values = set((True, '1', 'true'))
|
|
||||||
|
|
||||||
def addOption(self, label='', value=''):
|
|
||||||
if self['type'] in self.option_types:
|
|
||||||
opt = FieldOption(parent=self)
|
|
||||||
opt['label'] = label
|
|
||||||
opt['value'] = value
|
|
||||||
else:
|
|
||||||
raise ValueError("Cannot add options to a %s field." % self['type'])
|
|
||||||
|
|
||||||
def delOptions(self):
|
|
||||||
optsXML = self.xml.findall('{%s}option' % self.namespace)
|
|
||||||
for optXML in optsXML:
|
|
||||||
self.xml.remove(optXML)
|
|
||||||
|
|
||||||
def delRequired(self):
|
|
||||||
reqXML = self.xml.find('{%s}required' % self.namespace)
|
|
||||||
if reqXML is not None:
|
|
||||||
self.xml.remove(reqXML)
|
|
||||||
|
|
||||||
def delValue(self):
|
|
||||||
valsXML = self.xml.findall('{%s}value' % self.namespace)
|
|
||||||
for valXML in valsXML:
|
|
||||||
self.xml.remove(valXML)
|
|
||||||
|
|
||||||
def getAnswer(self):
|
|
||||||
return self.getValue()
|
|
||||||
|
|
||||||
def getOptions(self):
|
|
||||||
options = []
|
|
||||||
optsXML = self.xml.findall('{%s}option' % self.namespace)
|
|
||||||
for optXML in optsXML:
|
|
||||||
opt = FieldOption(xml=optXML)
|
|
||||||
options.append({'label': opt['label'], 'value':opt['value']})
|
|
||||||
return options
|
|
||||||
|
|
||||||
def getRequired(self):
|
|
||||||
reqXML = self.xml.find('{%s}required' % self.namespace)
|
|
||||||
return reqXML is not None
|
|
||||||
|
|
||||||
def getValue(self):
|
|
||||||
valsXML = self.xml.findall('{%s}value' % self.namespace)
|
|
||||||
if len(valsXML) == 0:
|
|
||||||
return None
|
|
||||||
elif self['type'] == 'boolean':
|
|
||||||
return valsXML[0].text in self.true_values
|
|
||||||
elif self['type'] in self.multi_value_types:
|
|
||||||
values = []
|
|
||||||
for valXML in valsXML:
|
|
||||||
if valXML.text is None:
|
|
||||||
valXML.text = ''
|
|
||||||
values.append(valXML.text)
|
|
||||||
if self['type'] == 'text-multi':
|
|
||||||
values = "\n".join(values)
|
|
||||||
return values
|
|
||||||
else:
|
|
||||||
return valsXML[0].text
|
|
||||||
|
|
||||||
def setAnswer(self, answer):
|
|
||||||
self.setValue(answer)
|
|
||||||
|
|
||||||
def setFalse(self):
|
|
||||||
self.setValue(False)
|
|
||||||
|
|
||||||
def setOptions(self, options):
|
|
||||||
for value in options:
|
|
||||||
if isinstance(value, dict):
|
|
||||||
self.addOption(**value)
|
|
||||||
else:
|
|
||||||
self.addOption(value=value)
|
|
||||||
|
|
||||||
def setRequired(self, required):
|
|
||||||
exists = self.getRequired()
|
|
||||||
if not exists and required:
|
|
||||||
self.xml.append(ET.Element('{%s}required' % self.namespace))
|
|
||||||
elif exists and not required:
|
|
||||||
self.delRequired()
|
|
||||||
|
|
||||||
def setTrue(self):
|
|
||||||
self.setValue(True)
|
|
||||||
|
|
||||||
def setValue(self, value):
|
|
||||||
self.delValue()
|
|
||||||
valXMLName = '{%s}value' % self.namespace
|
|
||||||
|
|
||||||
if self['type'] == 'boolean':
|
|
||||||
if value in self.true_values:
|
|
||||||
valXML = ET.Element(valXMLName)
|
|
||||||
valXML.text = '1'
|
|
||||||
self.xml.append(valXML)
|
|
||||||
else:
|
|
||||||
valXML = ET.Element(valXMLName)
|
|
||||||
valXML.text = '0'
|
|
||||||
self.xml.append(valXML)
|
|
||||||
elif self['type'] in self.multi_value_types or self['type'] in ['', None]:
|
|
||||||
if self['type'] in self.multi_line_types and isinstance(value, str):
|
|
||||||
value = value.split('\n')
|
|
||||||
if not isinstance(value, list):
|
|
||||||
value = [value]
|
|
||||||
for val in value:
|
|
||||||
if self['type'] in ['', None] and val in self.true_values:
|
|
||||||
val = '1'
|
|
||||||
valXML = ET.Element(valXMLName)
|
|
||||||
valXML.text = val
|
|
||||||
self.xml.append(valXML)
|
|
||||||
else:
|
|
||||||
if isinstance(value, list):
|
|
||||||
raise ValueError("Cannot add multiple values to a %s field." % self['type'])
|
|
||||||
valXML = ET.Element(valXMLName)
|
|
||||||
valXML.text = value
|
|
||||||
self.xml.append(valXML)
|
|
||||||
|
|
||||||
|
|
||||||
class FieldOption(ElementBase):
|
|
||||||
namespace = 'jabber:x:data'
|
|
||||||
name = 'option'
|
|
||||||
plugin_attrib = 'option'
|
|
||||||
interfaces = set(('label', 'value'))
|
|
||||||
sub_interfaces = set(('value',))
|
|
||||||
|
|
||||||
|
|
||||||
class xep_0004(base.base_plugin):
|
|
||||||
"""
|
|
||||||
XEP-0004: Data Forms
|
|
||||||
"""
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = '0004'
|
|
||||||
self.description = 'Data Forms'
|
|
||||||
|
|
||||||
self.xmpp.registerHandler(
|
|
||||||
Callback('Data Form',
|
|
||||||
MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns,
|
|
||||||
Form.namespace)),
|
|
||||||
self.handle_form))
|
|
||||||
|
|
||||||
registerStanzaPlugin(FormField, FieldOption)
|
|
||||||
registerStanzaPlugin(Form, FormField)
|
|
||||||
registerStanzaPlugin(Message, Form)
|
|
||||||
|
|
||||||
def makeForm(self, ftype='form', title='', instructions=''):
|
|
||||||
f = Form()
|
|
||||||
f['type'] = ftype
|
|
||||||
f['title'] = title
|
|
||||||
f['instructions'] = instructions
|
|
||||||
return f
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
base.base_plugin.post_init(self)
|
|
||||||
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
|
|
||||||
|
|
||||||
def handle_form(self, message):
|
|
||||||
self.xmpp.event("message_xform", message)
|
|
||||||
|
|
||||||
def buildForm(self, xml):
|
|
||||||
return Form(xml=xml)
|
|
11
sleekxmpp/plugins/xep_0004/__init__.py
Normal file
11
sleekxmpp/plugins/xep_0004/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0004.stanza import Form
|
||||||
|
from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption
|
||||||
|
from sleekxmpp.plugins.xep_0004.dataforms import xep_0004
|
60
sleekxmpp/plugins/xep_0004/dataforms.py
Normal file
60
sleekxmpp/plugins/xep_0004/dataforms.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from sleekxmpp.thirdparty import OrderedDict
|
||||||
|
|
||||||
|
from sleekxmpp import Message
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.plugins.base import base_plugin
|
||||||
|
from sleekxmpp.plugins.xep_0004 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption
|
||||||
|
|
||||||
|
|
||||||
|
class xep_0004(base_plugin):
|
||||||
|
"""
|
||||||
|
XEP-0004: Data Forms
|
||||||
|
"""
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
self.xep = '0004'
|
||||||
|
self.description = 'Data Forms'
|
||||||
|
self.stanza = stanza
|
||||||
|
|
||||||
|
self.xmpp.registerHandler(
|
||||||
|
Callback('Data Form',
|
||||||
|
StanzaPath('message/form'),
|
||||||
|
self.handle_form))
|
||||||
|
|
||||||
|
register_stanza_plugin(FormField, FieldOption, iterable=True)
|
||||||
|
register_stanza_plugin(Form, FormField, iterable=True)
|
||||||
|
register_stanza_plugin(Message, Form)
|
||||||
|
|
||||||
|
def make_form(self, ftype='form', title='', instructions=''):
|
||||||
|
f = Form()
|
||||||
|
f['type'] = ftype
|
||||||
|
f['title'] = title
|
||||||
|
f['instructions'] = instructions
|
||||||
|
return f
|
||||||
|
|
||||||
|
def post_init(self):
|
||||||
|
base_plugin.post_init(self)
|
||||||
|
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
|
||||||
|
|
||||||
|
def handle_form(self, message):
|
||||||
|
self.xmpp.event("message_xform", message)
|
||||||
|
|
||||||
|
def build_form(self, xml):
|
||||||
|
return Form(xml=xml)
|
||||||
|
|
||||||
|
|
||||||
|
xep_0004.makeForm = xep_0004.make_form
|
||||||
|
xep_0004.buildForm = xep_0004.build_form
|
10
sleekxmpp/plugins/xep_0004/stanza/__init__.py
Normal file
10
sleekxmpp/plugins/xep_0004/stanza/__init__.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0004.stanza.field import FormField, FieldOption
|
||||||
|
from sleekxmpp.plugins.xep_0004.stanza.form import Form
|
167
sleekxmpp/plugins/xep_0004/stanza/field.py
Normal file
167
sleekxmpp/plugins/xep_0004/stanza/field.py
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, ET
|
||||||
|
|
||||||
|
|
||||||
|
class FormField(ElementBase):
|
||||||
|
namespace = 'jabber:x:data'
|
||||||
|
name = 'field'
|
||||||
|
plugin_attrib = 'field'
|
||||||
|
interfaces = set(('answer', 'desc', 'required', 'value',
|
||||||
|
'options', 'label', 'type', 'var'))
|
||||||
|
sub_interfaces = set(('desc',))
|
||||||
|
plugin_tag_map = {}
|
||||||
|
plugin_attrib_map = {}
|
||||||
|
|
||||||
|
field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi',
|
||||||
|
'jid-single', 'list-multi', 'list-single',
|
||||||
|
'text-multi', 'text-private', 'text-single'))
|
||||||
|
|
||||||
|
true_values = set((True, '1', 'true'))
|
||||||
|
option_types = set(('list-multi', 'list-single'))
|
||||||
|
multi_line_types = set(('hidden', 'text-multi'))
|
||||||
|
multi_value_types = set(('hidden', 'jid-multi',
|
||||||
|
'list-multi', 'text-multi'))
|
||||||
|
|
||||||
|
def add_option(self, label='', value=''):
|
||||||
|
if self['type'] in self.option_types:
|
||||||
|
opt = FieldOption(parent=self)
|
||||||
|
opt['label'] = label
|
||||||
|
opt['value'] = value
|
||||||
|
else:
|
||||||
|
raise ValueError("Cannot add options to " + \
|
||||||
|
"a %s field." % self['type'])
|
||||||
|
|
||||||
|
def del_options(self):
|
||||||
|
optsXML = self.xml.findall('{%s}option' % self.namespace)
|
||||||
|
for optXML in optsXML:
|
||||||
|
self.xml.remove(optXML)
|
||||||
|
|
||||||
|
def del_required(self):
|
||||||
|
reqXML = self.xml.find('{%s}required' % self.namespace)
|
||||||
|
if reqXML is not None:
|
||||||
|
self.xml.remove(reqXML)
|
||||||
|
|
||||||
|
def del_value(self):
|
||||||
|
valsXML = self.xml.findall('{%s}value' % self.namespace)
|
||||||
|
for valXML in valsXML:
|
||||||
|
self.xml.remove(valXML)
|
||||||
|
|
||||||
|
def get_answer(self):
|
||||||
|
return self['value']
|
||||||
|
|
||||||
|
def get_options(self):
|
||||||
|
options = []
|
||||||
|
optsXML = self.xml.findall('{%s}option' % self.namespace)
|
||||||
|
for optXML in optsXML:
|
||||||
|
opt = FieldOption(xml=optXML)
|
||||||
|
options.append({'label': opt['label'], 'value': opt['value']})
|
||||||
|
return options
|
||||||
|
|
||||||
|
def get_required(self):
|
||||||
|
reqXML = self.xml.find('{%s}required' % self.namespace)
|
||||||
|
return reqXML is not None
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
valsXML = self.xml.findall('{%s}value' % self.namespace)
|
||||||
|
if len(valsXML) == 0:
|
||||||
|
return None
|
||||||
|
elif self['type'] == 'boolean':
|
||||||
|
return valsXML[0].text in self.true_values
|
||||||
|
elif self['type'] in self.multi_value_types:
|
||||||
|
values = []
|
||||||
|
for valXML in valsXML:
|
||||||
|
if valXML.text is None:
|
||||||
|
valXML.text = ''
|
||||||
|
values.append(valXML.text)
|
||||||
|
if self['type'] == 'text-multi':
|
||||||
|
values = "\n".join(values)
|
||||||
|
return values
|
||||||
|
else:
|
||||||
|
return valsXML[0].text
|
||||||
|
|
||||||
|
def set_answer(self, answer):
|
||||||
|
self['value'] = answer
|
||||||
|
|
||||||
|
def set_false(self):
|
||||||
|
self['value'] = False
|
||||||
|
|
||||||
|
def set_options(self, options):
|
||||||
|
for value in options:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
self.add_option(**value)
|
||||||
|
else:
|
||||||
|
self.add_option(value=value)
|
||||||
|
|
||||||
|
def set_required(self, required):
|
||||||
|
exists = self['required']
|
||||||
|
if not exists and required:
|
||||||
|
self.xml.append(ET.Element('{%s}required' % self.namespace))
|
||||||
|
elif exists and not required:
|
||||||
|
del self['required']
|
||||||
|
|
||||||
|
def set_true(self):
|
||||||
|
self['value'] = True
|
||||||
|
|
||||||
|
def set_value(self, value):
|
||||||
|
del self['value']
|
||||||
|
valXMLName = '{%s}value' % self.namespace
|
||||||
|
|
||||||
|
if self['type'] == 'boolean':
|
||||||
|
if value in self.true_values:
|
||||||
|
valXML = ET.Element(valXMLName)
|
||||||
|
valXML.text = '1'
|
||||||
|
self.xml.append(valXML)
|
||||||
|
else:
|
||||||
|
valXML = ET.Element(valXMLName)
|
||||||
|
valXML.text = '0'
|
||||||
|
self.xml.append(valXML)
|
||||||
|
elif self['type'] in self.multi_value_types or not self['type']:
|
||||||
|
if not isinstance(value, list):
|
||||||
|
if self['type'] in self.multi_line_types:
|
||||||
|
value = value.split('\n')
|
||||||
|
else:
|
||||||
|
value = [value]
|
||||||
|
for val in value:
|
||||||
|
if self['type'] in ['', None] and val in self.true_values:
|
||||||
|
val = '1'
|
||||||
|
valXML = ET.Element(valXMLName)
|
||||||
|
valXML.text = val
|
||||||
|
self.xml.append(valXML)
|
||||||
|
else:
|
||||||
|
if isinstance(value, list):
|
||||||
|
raise ValueError("Cannot add multiple values " + \
|
||||||
|
"to a %s field." % self['type'])
|
||||||
|
valXML = ET.Element(valXMLName)
|
||||||
|
valXML.text = value
|
||||||
|
self.xml.append(valXML)
|
||||||
|
|
||||||
|
|
||||||
|
class FieldOption(ElementBase):
|
||||||
|
namespace = 'jabber:x:data'
|
||||||
|
name = 'option'
|
||||||
|
plugin_attrib = 'option'
|
||||||
|
interfaces = set(('label', 'value'))
|
||||||
|
sub_interfaces = set(('value',))
|
||||||
|
|
||||||
|
|
||||||
|
FormField.addOption = FormField.add_option
|
||||||
|
FormField.delOptions = FormField.del_options
|
||||||
|
FormField.delRequired = FormField.del_required
|
||||||
|
FormField.delValue = FormField.del_value
|
||||||
|
FormField.getAnswer = FormField.get_answer
|
||||||
|
FormField.getOptions = FormField.get_options
|
||||||
|
FormField.getRequired = FormField.get_required
|
||||||
|
FormField.getValue = FormField.get_value
|
||||||
|
FormField.setAnswer = FormField.set_answer
|
||||||
|
FormField.setFalse = FormField.set_false
|
||||||
|
FormField.setOptions = FormField.set_options
|
||||||
|
FormField.setRequired = FormField.set_required
|
||||||
|
FormField.setTrue = FormField.set_true
|
||||||
|
FormField.setValue = FormField.set_value
|
250
sleekxmpp/plugins/xep_0004/stanza/form.py
Normal file
250
sleekxmpp/plugins/xep_0004/stanza/form.py
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.thirdparty import OrderedDict
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, ET
|
||||||
|
from sleekxmpp.plugins.xep_0004.stanza import FormField
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Form(ElementBase):
|
||||||
|
namespace = 'jabber:x:data'
|
||||||
|
name = 'x'
|
||||||
|
plugin_attrib = 'form'
|
||||||
|
interfaces = set(('fields', 'instructions', 'items',
|
||||||
|
'reported', 'title', 'type', 'values'))
|
||||||
|
sub_interfaces = set(('title',))
|
||||||
|
form_types = set(('cancel', 'form', 'result', 'submit'))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
title = None
|
||||||
|
if 'title' in kwargs:
|
||||||
|
title = kwargs['title']
|
||||||
|
del kwargs['title']
|
||||||
|
ElementBase.__init__(self, *args, **kwargs)
|
||||||
|
if title is not None:
|
||||||
|
self['title'] = title
|
||||||
|
|
||||||
|
def setup(self, xml=None):
|
||||||
|
if ElementBase.setup(self, xml):
|
||||||
|
# If we had to generate xml
|
||||||
|
self['type'] = 'form'
|
||||||
|
|
||||||
|
def set_type(self, ftype):
|
||||||
|
self._set_attr('type', ftype)
|
||||||
|
if ftype == 'submit':
|
||||||
|
fields = self['fields']
|
||||||
|
for var in fields:
|
||||||
|
field = fields[var]
|
||||||
|
del field['type']
|
||||||
|
del field['label']
|
||||||
|
del field['desc']
|
||||||
|
del field['required']
|
||||||
|
del field['options']
|
||||||
|
elif ftype == 'cancel':
|
||||||
|
del self['fields']
|
||||||
|
|
||||||
|
def add_field(self, var='', ftype=None, label='', desc='',
|
||||||
|
required=False, value=None, options=None, **kwargs):
|
||||||
|
kwtype = kwargs.get('type', None)
|
||||||
|
if kwtype is None:
|
||||||
|
kwtype = ftype
|
||||||
|
|
||||||
|
field = FormField(parent=self)
|
||||||
|
field['var'] = var
|
||||||
|
field['type'] = kwtype
|
||||||
|
field['value'] = value
|
||||||
|
if self['type'] in ('form', 'result'):
|
||||||
|
field['label'] = label
|
||||||
|
field['desc'] = desc
|
||||||
|
field['required'] = required
|
||||||
|
if options is not None:
|
||||||
|
field['options'] = options
|
||||||
|
else:
|
||||||
|
del field['type']
|
||||||
|
return field
|
||||||
|
|
||||||
|
def getXML(self, type='submit'):
|
||||||
|
self['type'] = type
|
||||||
|
log.warning("Form.getXML() is deprecated API compatibility " + \
|
||||||
|
"with plugins/old_0004.py")
|
||||||
|
return self.xml
|
||||||
|
|
||||||
|
def fromXML(self, xml):
|
||||||
|
log.warning("Form.fromXML() is deprecated API compatibility " + \
|
||||||
|
"with plugins/old_0004.py")
|
||||||
|
n = Form(xml=xml)
|
||||||
|
return n
|
||||||
|
|
||||||
|
def add_item(self, values):
|
||||||
|
itemXML = ET.Element('{%s}item' % self.namespace)
|
||||||
|
self.xml.append(itemXML)
|
||||||
|
reported_vars = self['reported'].keys()
|
||||||
|
for var in reported_vars:
|
||||||
|
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
||||||
|
itemXML.append(fieldXML)
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
field['var'] = var
|
||||||
|
field['value'] = values.get(var, None)
|
||||||
|
|
||||||
|
def add_reported(self, var, ftype=None, label='', desc='', **kwargs):
|
||||||
|
kwtype = kwargs.get('type', None)
|
||||||
|
if kwtype is None:
|
||||||
|
kwtype = ftype
|
||||||
|
reported = self.xml.find('{%s}reported' % self.namespace)
|
||||||
|
if reported is None:
|
||||||
|
reported = ET.Element('{%s}reported' % self.namespace)
|
||||||
|
self.xml.append(reported)
|
||||||
|
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
||||||
|
reported.append(fieldXML)
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
field['var'] = var
|
||||||
|
field['type'] = kwtype
|
||||||
|
field['label'] = label
|
||||||
|
field['desc'] = desc
|
||||||
|
return field
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self['type'] = 'cancel'
|
||||||
|
|
||||||
|
def del_fields(self):
|
||||||
|
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
|
||||||
|
for fieldXML in fieldsXML:
|
||||||
|
self.xml.remove(fieldXML)
|
||||||
|
|
||||||
|
def del_instructions(self):
|
||||||
|
instsXML = self.xml.findall('{%s}instructions')
|
||||||
|
for instXML in instsXML:
|
||||||
|
self.xml.remove(instXML)
|
||||||
|
|
||||||
|
def del_items(self):
|
||||||
|
itemsXML = self.xml.find('{%s}item' % self.namespace)
|
||||||
|
for itemXML in itemsXML:
|
||||||
|
self.xml.remove(itemXML)
|
||||||
|
|
||||||
|
def del_reported(self):
|
||||||
|
reportedXML = self.xml.find('{%s}reported' % self.namespace)
|
||||||
|
if reportedXML is not None:
|
||||||
|
self.xml.remove(reportedXML)
|
||||||
|
|
||||||
|
def get_fields(self, use_dict=False):
|
||||||
|
fields = OrderedDict()
|
||||||
|
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
|
||||||
|
for fieldXML in fieldsXML:
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
fields[field['var']] = field
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_instructions(self):
|
||||||
|
instructions = ''
|
||||||
|
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
|
||||||
|
return "\n".join([instXML.text for instXML in instsXML])
|
||||||
|
|
||||||
|
def get_items(self):
|
||||||
|
items = []
|
||||||
|
itemsXML = self.xml.findall('{%s}item' % self.namespace)
|
||||||
|
for itemXML in itemsXML:
|
||||||
|
item = {}
|
||||||
|
fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
|
||||||
|
for fieldXML in fieldsXML:
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
item[field['var']] = field['value']
|
||||||
|
items.append(item)
|
||||||
|
return items
|
||||||
|
|
||||||
|
def get_reported(self):
|
||||||
|
fields = {}
|
||||||
|
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
|
||||||
|
FormField.namespace))
|
||||||
|
for field in xml:
|
||||||
|
field = FormField(xml=field)
|
||||||
|
fields[field['var']] = field
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_values(self):
|
||||||
|
values = {}
|
||||||
|
fields = self['fields']
|
||||||
|
for var in fields:
|
||||||
|
values[var] = fields[var]['value']
|
||||||
|
return values
|
||||||
|
|
||||||
|
def reply(self):
|
||||||
|
if self['type'] == 'form':
|
||||||
|
self['type'] = 'submit'
|
||||||
|
elif self['type'] == 'submit':
|
||||||
|
self['type'] = 'result'
|
||||||
|
|
||||||
|
def set_fields(self, fields):
|
||||||
|
del self['fields']
|
||||||
|
if not isinstance(fields, list):
|
||||||
|
fields = fields.items()
|
||||||
|
for var, field in fields:
|
||||||
|
field['var'] = var
|
||||||
|
self.add_field(**field)
|
||||||
|
|
||||||
|
def set_instructions(self, instructions):
|
||||||
|
del self['instructions']
|
||||||
|
if instructions in [None, '']:
|
||||||
|
return
|
||||||
|
instructions = instructions.split('\n')
|
||||||
|
for instruction in instructions:
|
||||||
|
inst = ET.Element('{%s}instructions' % self.namespace)
|
||||||
|
inst.text = instruction
|
||||||
|
self.xml.append(inst)
|
||||||
|
|
||||||
|
def set_items(self, items):
|
||||||
|
for item in items:
|
||||||
|
self.add_item(item)
|
||||||
|
|
||||||
|
def set_reported(self, reported):
|
||||||
|
for var in reported:
|
||||||
|
field = reported[var]
|
||||||
|
field['var'] = var
|
||||||
|
self.add_reported(var, **field)
|
||||||
|
|
||||||
|
def set_values(self, values):
|
||||||
|
fields = self['fields']
|
||||||
|
for field in values:
|
||||||
|
fields[field]['value'] = values[field]
|
||||||
|
|
||||||
|
def merge(self, other):
|
||||||
|
new = copy.copy(self)
|
||||||
|
if type(other) == dict:
|
||||||
|
new['values'] = other
|
||||||
|
return new
|
||||||
|
nfields = new['fields']
|
||||||
|
ofields = other['fields']
|
||||||
|
nfields.update(ofields)
|
||||||
|
new['fields'] = nfields
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
Form.setType = Form.set_type
|
||||||
|
Form.addField = Form.add_field
|
||||||
|
Form.addItem = Form.add_item
|
||||||
|
Form.addReported = Form.add_reported
|
||||||
|
Form.delFields = Form.del_fields
|
||||||
|
Form.delInstructions = Form.del_instructions
|
||||||
|
Form.delItems = Form.del_items
|
||||||
|
Form.delReported = Form.del_reported
|
||||||
|
Form.getFields = Form.get_fields
|
||||||
|
Form.getInstructions = Form.get_instructions
|
||||||
|
Form.getItems = Form.get_items
|
||||||
|
Form.getReported = Form.get_reported
|
||||||
|
Form.getValues = Form.get_values
|
||||||
|
Form.setFields = Form.set_fields
|
||||||
|
Form.setInstructions = Form.set_instructions
|
||||||
|
Form.setItems = Form.set_items
|
||||||
|
Form.setReported = Form.set_reported
|
||||||
|
Form.setValues = Form.set_values
|
|
@ -1,4 +1,6 @@
|
||||||
from sleekxmpp.test import *
|
from sleekxmpp.test import *
|
||||||
|
from sleekxmpp.thirdparty import OrderedDict
|
||||||
|
|
||||||
import sleekxmpp.plugins.xep_0004 as xep_0004
|
import sleekxmpp.plugins.xep_0004 as xep_0004
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,21 +49,25 @@ class TestDataForms(SleekTest):
|
||||||
</message>
|
</message>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
form['fields'] = [('f1', {'type': 'text-single',
|
fields = OrderedDict()
|
||||||
|
fields['f1'] = {'type': 'text-single',
|
||||||
'label': 'Username',
|
'label': 'Username',
|
||||||
'required': True}),
|
'required': True}
|
||||||
('f2', {'type': 'text-private',
|
fields['f2'] = {'type': 'text-private',
|
||||||
'label': 'Password',
|
'label': 'Password',
|
||||||
'required': True}),
|
'required': True}
|
||||||
('f3', {'type': 'text-multi',
|
fields['f3'] = {'type': 'text-multi',
|
||||||
'label': 'Message',
|
'label': 'Message',
|
||||||
'value': 'Enter message.\nA long one even.'}),
|
'value': 'Enter message.\nA long one even.'}
|
||||||
('f4', {'type': 'list-single',
|
fields['f4'] = {'type': 'list-single',
|
||||||
'label': 'Message Type',
|
'label': 'Message Type',
|
||||||
'options': [{'label': 'Cool!',
|
'options': [{'label': 'Cool!',
|
||||||
'value': 'cool'},
|
'value': 'cool'},
|
||||||
{'label': 'Urgh!',
|
{'label': 'Urgh!',
|
||||||
'value': 'urgh'}]})]
|
'value': 'urgh'}]}
|
||||||
|
form['fields'] = fields
|
||||||
|
|
||||||
|
|
||||||
self.check(msg, """
|
self.check(msg, """
|
||||||
<message>
|
<message>
|
||||||
<x xmlns="jabber:x:data" type="form">
|
<x xmlns="jabber:x:data" type="form">
|
||||||
|
@ -92,9 +98,8 @@ class TestDataForms(SleekTest):
|
||||||
|
|
||||||
msg = self.Message()
|
msg = self.Message()
|
||||||
form = msg['form']
|
form = msg['form']
|
||||||
form.setFields([
|
form.add_field(var='foo', ftype='text-single')
|
||||||
('foo', {'type': 'text-single'}),
|
form.add_field(var='bar', ftype='list-multi')
|
||||||
('bar', {'type': 'list-multi'})])
|
|
||||||
|
|
||||||
form.setValues({'foo': 'Foo!',
|
form.setValues({'foo': 'Foo!',
|
||||||
'bar': ['a', 'b']})
|
'bar': ['a', 'b']})
|
||||||
|
|
|
@ -182,7 +182,7 @@ class TestPubsubStanzas(SleekTest):
|
||||||
<subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp">
|
<subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp">
|
||||||
<options node="cheese" jid="fritzy@netflint.net/sleekxmpp">
|
<options node="cheese" jid="fritzy@netflint.net/sleekxmpp">
|
||||||
<x xmlns="jabber:x:data" type="submit">
|
<x xmlns="jabber:x:data" type="submit">
|
||||||
<field var="pubsub#title" type="text-single">
|
<field var="pubsub#title">
|
||||||
<value>this thing is awesome</value>
|
<value>this thing is awesome</value>
|
||||||
</field>
|
</field>
|
||||||
</x>
|
</x>
|
||||||
|
@ -306,42 +306,42 @@ class TestPubsubStanzas(SleekTest):
|
||||||
<create node="testnode2" />
|
<create node="testnode2" />
|
||||||
<configure>
|
<configure>
|
||||||
<x xmlns="jabber:x:data" type="submit">
|
<x xmlns="jabber:x:data" type="submit">
|
||||||
<field var="FORM_TYPE" type="hidden">
|
<field var="FORM_TYPE">
|
||||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#node_type" type="list-single" label="Select the node type">
|
<field var="pubsub#node_type">
|
||||||
<value>leaf</value>
|
<value>leaf</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#title" type="text-single" label="A friendly name for the node" />
|
<field var="pubsub#title" />
|
||||||
<field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications">
|
<field var="pubsub#deliver_notifications">
|
||||||
<value>1</value>
|
<value>1</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications">
|
<field var="pubsub#deliver_payloads">
|
||||||
<value>1</value>
|
<value>1</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" />
|
<field var="pubsub#notify_config" />
|
||||||
<field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" />
|
<field var="pubsub#notify_delete" />
|
||||||
<field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node">
|
<field var="pubsub#notify_retract">
|
||||||
<value>1</value>
|
<value>1</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#notify_sub" type="boolean" label="Notify owners about new subscribers and unsubscribes" />
|
<field var="pubsub#notify_sub" />
|
||||||
<field var="pubsub#persist_items" type="boolean" label="Persist items in storage" />
|
<field var="pubsub#persist_items" />
|
||||||
<field var="pubsub#max_items" type="text-single" label="Max # of items to persist">
|
<field var="pubsub#max_items">
|
||||||
<value>10</value>
|
<value>10</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#subscribe" type="boolean" label="Whether to allow subscriptions">
|
<field var="pubsub#subscribe">
|
||||||
<value>1</value>
|
<value>1</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#access_model" type="list-single" label="Specify the subscriber model">
|
<field var="pubsub#access_model">
|
||||||
<value>open</value>
|
<value>open</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#publish_model" type="list-single" label="Specify the publisher model">
|
<field var="pubsub#publish_model">
|
||||||
<value>publishers</value>
|
<value>publishers</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#send_last_published_item" type="list-single" label="Send last published item">
|
<field var="pubsub#send_last_published_item">
|
||||||
<value>never</value>
|
<value>never</value>
|
||||||
</field>
|
</field>
|
||||||
<field var="pubsub#presence_based_delivery" type="boolean" label="Deliver notification only to available users" />
|
<field var="pubsub#presence_based_delivery" />
|
||||||
</x>
|
</x>
|
||||||
</configure>
|
</configure>
|
||||||
</pubsub>
|
</pubsub>
|
||||||
|
|
Loading…
Reference in a new issue