Replace the old NOTES.txt and PRINCIPLES.txt with the Sphinx versions. Fix Captcha title.
svn path=/plone.app.discussion/trunk/; revision=36998
This commit is contained in:
		
							parent
							
								
									adbe6c7db7
								
							
						
					
					
						commit
						af1a8bfacd
					
				@ -1,59 +1 @@
 | 
			
		||||
========================
 | 
			
		||||
Architectural Principles
 | 
			
		||||
========================
 | 
			
		||||
 | 
			
		||||
This document outlines architectural principles used in the design of
 | 
			
		||||
plone.app.discussion.
 | 
			
		||||
 | 
			
		||||
  **Discussion items have a portal_type**
 | 
			
		||||
    This makes it easier to search for them and manage them using existing
 | 
			
		||||
    CMF and Plone UI constructs.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are cataloged**
 | 
			
		||||
    It is possible to search for discussion items like any other type of
 | 
			
		||||
    content.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are subject to workflow and permission**
 | 
			
		||||
    Moderation, anonymous commenting, and auto approve/reject should be
 | 
			
		||||
    handled using workflow states, automatic and manual transitions, and
 | 
			
		||||
    permissions.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are light weight objects**
 | 
			
		||||
    Discussion item objects are as light weight as possible. Ideally, a 
 | 
			
		||||
    discussion item should be as lightweight as a catalog brain. This may mean 
 | 
			
		||||
    that we forego convenience base classes and re-implement certain interfaces. 
 | 
			
		||||
    Comments should not provide the full set of dublin core metadata, though 
 | 
			
		||||
    custom indexers can be used to provide values for standard catalog indexes.
 | 
			
		||||
 | 
			
		||||
  **Optimise for retrival speed**
 | 
			
		||||
    HTML filtering and other processing should happen on save, not on render,
 | 
			
		||||
    to make rendering quick.
 | 
			
		||||
 | 
			
		||||
  **Settings are stored using plone.registry** 
 | 
			
		||||
    Any global setting should be stored in plone.registry records.
 | 
			
		||||
    
 | 
			
		||||
  **Forms are constructed using extensible z3c.form forms**
 | 
			
		||||
    This allows plugins (such as spam protection algorithms) to provide
 | 
			
		||||
    additional validation. It also allows integrators to write add-ons that add
 | 
			
		||||
    new fields to the comment form.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are stored in a BTree container**
 | 
			
		||||
    This allows faster lookup and manipulation.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are accessed using a dict-like interface**
 | 
			
		||||
    This makes iteration and manipulation more natural. Even if comments are
 | 
			
		||||
    not stored threaded, the dict interface should act as if they are, i.e.
 | 
			
		||||
    calling items() on a comment should return the replies to that comment
 | 
			
		||||
    (in order).
 | 
			
		||||
    
 | 
			
		||||
  **Discussion items are retrieved in reverse creation date order**
 | 
			
		||||
    Discussion items do not need to support explicit ordering. They should
 | 
			
		||||
    always be retrieved in reverse creation date order (most recent for).
 | 
			
		||||
    They can be stored with keys so that this is always true.
 | 
			
		||||
    
 | 
			
		||||
  **Discussion items do not need readable ids**
 | 
			
		||||
    Ids can be based on the creation date.
 | 
			
		||||
    
 | 
			
		||||
  **Discussion items send events**
 | 
			
		||||
    The usual zope.lifecycleevent and zope.container events are fired when 
 | 
			
		||||
    discussion items are added, removed, or modified.
 | 
			
		||||
.. include:: ../../plone/app/discussion/architecture.txt
 | 
			
		||||
@ -1,167 +1 @@
 | 
			
		||||
============
 | 
			
		||||
Design Notes
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
This document contains design notes for plone.app.discussion.
 | 
			
		||||
 | 
			
		||||
Storage and traversal
 | 
			
		||||
---------------------
 | 
			
		||||
 | 
			
		||||
For each content item, there is a Conversation object stored in annotations.
 | 
			
		||||
This can be traversed to via the ++conversation++ namespace, but also fetched
 | 
			
		||||
via an adapter lookup to IConversation.
 | 
			
		||||
 | 
			
		||||
The conversation stores all comments related to a content object. Each
 | 
			
		||||
comment has an integer id (also representable as a string, to act as an OFS
 | 
			
		||||
id and allow traversal). Hence, traversing to obj/++conversation++/123 retrieves
 | 
			
		||||
the comment with id 123.
 | 
			
		||||
 | 
			
		||||
Comments ids are assigned in order, so a comment with id N was posted before
 | 
			
		||||
a comment with id N + 1. However, it is not guaranteed that ids will be 
 | 
			
		||||
incremental. Ids must be positive integers - 0 or negative numbers are not
 | 
			
		||||
allowed.
 | 
			
		||||
 | 
			
		||||
Threading information is stored in the conversation: we keep track of the
 | 
			
		||||
set of children and the parent if any comment. Top-level comments have a
 | 
			
		||||
parent id of 0. This information is managed by the conversation class when
 | 
			
		||||
comments are manipulated using a dict-like API.
 | 
			
		||||
 | 
			
		||||
Note that the __parent__/acquisition parent of an IComment is the
 | 
			
		||||
IConversation, and the __parent__/acquisition parent of an IConversation is
 | 
			
		||||
the content object.
 | 
			
		||||
 | 
			
		||||
Events
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
Manipulating the IConversation object should fire the usual IObjectAddedEvent
 | 
			
		||||
and IObjectRemovedEvent events. The UI may further fire IObjectCreatedEvent
 | 
			
		||||
and IObjectModifiedEvent for comments.
 | 
			
		||||
 | 
			
		||||
Factories
 | 
			
		||||
---------
 | 
			
		||||
 | 
			
		||||
Comments should always be created via the 'Discussion Item' IFactory utility.
 | 
			
		||||
Conversations should always be obtained via the IConversation adapter (even
 | 
			
		||||
the ++conversation++ namespace should use this). This makes it possible to 
 | 
			
		||||
replace conversations and comments transparently.
 | 
			
		||||
 | 
			
		||||
The Comment class
 | 
			
		||||
-----------------
 | 
			
		||||
 | 
			
		||||
The inheritance tree for DiscussionItem is shown below. Classes we want to 
 | 
			
		||||
mix in and interface we want to implement in the Comment class are marked
 | 
			
		||||
with [x].
 | 
			
		||||
 | 
			
		||||
    [ ] DiscussionItem
 | 
			
		||||
        [ ] Document
 | 
			
		||||
            [ ] PortalContent                       = [ ] IContentish
 | 
			
		||||
                [ ] DynamicType                     = [ ] IDynamicType
 | 
			
		||||
                [ ] CMFCatalogAware                 = [ ] <no interface>
 | 
			
		||||
                [ ] SimpleItem                      = [ ] ISimpleItem
 | 
			
		||||
                    [ ] Item                          [ ] 
 | 
			
		||||
                        [?] Base                    = [ ] <no interface>
 | 
			
		||||
                        [ ] Resource                = [ ] <no interface>
 | 
			
		||||
                        [ ] CopySource              = [ ] ICopySource 
 | 
			
		||||
                        [ ] Tabs                    = [ ] <no interface>
 | 
			
		||||
                        [x] Traversable             = [ ] ITraversable
 | 
			
		||||
                        [ ] Element                 = [ ] <no interface>
 | 
			
		||||
                        [x] Owned                   = [ ] IOwned
 | 
			
		||||
                        [ ] UndoSupport             = [ ] IUndoSupport
 | 
			
		||||
                    [ ] Persistent                    [ ] 
 | 
			
		||||
                    [ ] Implicit                      [ ] 
 | 
			
		||||
                    [x] RoleManager                 = [ ] IRoleManager
 | 
			
		||||
                        [ ] RoleManager             = [ ] IPermissionMappingSupport
 | 
			
		||||
            [ ] DefaultDublinCoreImpl               = [ ] IDublinCore
 | 
			
		||||
                                                      [ ] ICatalogableDublinCore
 | 
			
		||||
                                                      [ ] IMutableDublinCore
 | 
			
		||||
                [ ] PropertyManager                 = [ ] IPropertyManager
 | 
			
		||||
 | 
			
		||||
Thus, we want:
 | 
			
		||||
 | 
			
		||||
  * Traversable, to get absolute_url() and friends
 | 
			
		||||
        - this requires a good acquisition chain at all times
 | 
			
		||||
  * Acquisition.Explicit, to support acquisition
 | 
			
		||||
        - we do not want implicit acquisition
 | 
			
		||||
  * Owned, to be able to track ownership
 | 
			
		||||
  * RoleManager, to support permissions and local roles
 | 
			
		||||
  
 | 
			
		||||
We also want to use a number of custom indexers for most of the standard
 | 
			
		||||
metadata such as creator, effective date etc.
 | 
			
		||||
 | 
			
		||||
Finally, we'll need event handlers to perform the actual indexing.
 | 
			
		||||
 | 
			
		||||
Discussion settings
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
Discussion can be enabled per-type and per-instance, via values in the FTI
 | 
			
		||||
(allow_discussion) and on the object. These will remain unchanged. The
 | 
			
		||||
IConversation object's 'enabled' property should consult these.
 | 
			
		||||
 | 
			
		||||
Global settings should be managed using plone.registry. A control panel
 | 
			
		||||
can be generated from this as well, using the helper class in
 | 
			
		||||
plone.app.registry.
 | 
			
		||||
 | 
			
		||||
Note that some settings, notably those to do with permissions and workflow,
 | 
			
		||||
will need to be wired up as custom form fields with custom data mangers
 | 
			
		||||
or similar.
 | 
			
		||||
 | 
			
		||||
Workflow and permissions
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
Where possible, we should use existing permissions:
 | 
			
		||||
 | 
			
		||||
  * View
 | 
			
		||||
  * Reply to Item
 | 
			
		||||
  * Modify Portal Content
 | 
			
		||||
  * Request Review
 | 
			
		||||
 | 
			
		||||
In addition, we'll need a 'Moderator' role and a moderation permission,
 | 
			
		||||
 | 
			
		||||
  * Moderate comment
 | 
			
		||||
  * Bypass moderation
 | 
			
		||||
  
 | 
			
		||||
To control whether Anonymous can post comments, we manage the 'Reply to Item'
 | 
			
		||||
permission. To control whether moderation is required for various roles, we
 | 
			
		||||
could manage the 'Bypass moderation' permission.
 | 
			
		||||
 | 
			
		||||
These could work in a workflow like this:
 | 
			
		||||
 | 
			
		||||
  * --> [posted] -- {publish} --> [published]--> *
 | 
			
		||||
           |                          ^
 | 
			
		||||
           |                          |
 | 
			
		||||
           +----- {auto-publish} -----+
 | 
			
		||||
           |                          |
 | 
			
		||||
           +----- {auto-moderate} ----+
 | 
			
		||||
    
 | 
			
		||||
The 'posted' state is the initial state. 'published' is the state where the
 | 
			
		||||
comment is visible to non-reviewers.
 | 
			
		||||
 | 
			
		||||
The 'publish' transition would be protected by the 'Moderate comment'
 | 
			
		||||
permission. We could have states and transition for 'rejected', etc, but it
 | 
			
		||||
is probably just as good to delete comments that are rejected.
 | 
			
		||||
 | 
			
		||||
The 'auto-publish' transition would be an automatic transition protected by
 | 
			
		||||
the 'Bypass moderation' permission.
 | 
			
		||||
 | 
			
		||||
The 'auto-moderate' transition would be another automatic transition protected
 | 
			
		||||
by an expression (e.g. calling a view) that returns True if the user is on
 | 
			
		||||
an auto-moderation 'white-list', e.g. by email address or username.
 | 
			
		||||
  
 | 
			
		||||
Forms and UI
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
The basic commenting display/reply form is placed in a viewlet.
 | 
			
		||||
 | 
			
		||||
The reply form is dynamically created right under the comment when the user hits
 | 
			
		||||
the reply button. To do so, we copy the standard comment form with a jQuery 
 | 
			
		||||
function. This function sets the form's hidden in_reply_to field to the id of
 | 
			
		||||
the comment the user wants to reply to. This also makes is possible to use 
 | 
			
		||||
z3c.form validation for the reply forms, because we can uniquely identify the 
 | 
			
		||||
a reply form request and return the reply form with validation errors.
 | 
			
		||||
 | 
			
		||||
Since we rely on JavaScript for the reply form creation, the reply button is
 | 
			
		||||
removed for nonJavaScript enabled browsers.
 | 
			
		||||
 | 
			
		||||
The comment form uses z3c.form and plone.z3cform's ExtensibleForm support. This 
 | 
			
		||||
makes it possible to plug in additional fields declaratively, e.g. to include 
 | 
			
		||||
SPAM protection.
 | 
			
		||||
.. include:: ../../plone/app/discussion/design.txt
 | 
			
		||||
@ -1,59 +0,0 @@
 | 
			
		||||
=============================================
 | 
			
		||||
plone.app.discussion architectural principles
 | 
			
		||||
=============================================
 | 
			
		||||
 | 
			
		||||
This document outlines architectural principles used in the design of
 | 
			
		||||
plone.app.discussion.
 | 
			
		||||
 | 
			
		||||
  Discussion items have a portal_type
 | 
			
		||||
    This makes it easier to search for them and manage them using existing
 | 
			
		||||
    CMF and Plone UI constructs.
 | 
			
		||||
 | 
			
		||||
  Discussion items are cataloged
 | 
			
		||||
    It should be possible to search for discussion items like any other
 | 
			
		||||
    type of content.
 | 
			
		||||
 | 
			
		||||
  Discussion items are subject to workflow and permission
 | 
			
		||||
    Moderation, anonymous commenting, and auto approve/reject should be
 | 
			
		||||
    handled using workflow states, automatic and manual transitions, and
 | 
			
		||||
    permissions.
 | 
			
		||||
 | 
			
		||||
  Discussion items are light weight objects
 | 
			
		||||
    All discussion item objects should be as light weight as possible. 
 | 
			
		||||
    Ideally, a discussion item should be as lightweight as a catalog brain.
 | 
			
		||||
    This may mean that we forego convenience base classes and re-implement
 | 
			
		||||
    certain interfaces. Comments should not provide the full set of dublin
 | 
			
		||||
    core metadata, though custom indexers can be used to provide values for
 | 
			
		||||
    standard catalog indexes.
 | 
			
		||||
 | 
			
		||||
  Optimise for retrival speed
 | 
			
		||||
    HTML filtering and other processing should happen on save, not on render,
 | 
			
		||||
    to make rendering quick.
 | 
			
		||||
 | 
			
		||||
  Settings are stored using plone.registry 
 | 
			
		||||
    Any global setting should be stored in plone.registry records
 | 
			
		||||
    
 | 
			
		||||
  Forms are constructed using extensible z3c.form forms
 | 
			
		||||
    This allows plugins (such as spam protection algorithms) to provide
 | 
			
		||||
    additional validation
 | 
			
		||||
 | 
			
		||||
  Discussion items are stored in a BTree container
 | 
			
		||||
    This allows faster lookup and manipulation
 | 
			
		||||
 | 
			
		||||
  Discussion items are accessed using a dict-like interface
 | 
			
		||||
    This makes iteration and manipulation more natural. Even if comments are
 | 
			
		||||
    not stored threaded, the dict interface should act as if they are, i.e.
 | 
			
		||||
    calling items() on a comment should return the replies to that comment
 | 
			
		||||
    (in order).
 | 
			
		||||
    
 | 
			
		||||
  Discussion items are retrieved in reverse creation date order
 | 
			
		||||
    Discussion items do not need to support explicit ordering. They should
 | 
			
		||||
    always be retrieved in reverse creation date order (most recent for).
 | 
			
		||||
    They can be stored with keys so that this is always true.
 | 
			
		||||
    
 | 
			
		||||
  Discussion items do not need readable ids
 | 
			
		||||
    Ids can be based on the creation date.
 | 
			
		||||
    
 | 
			
		||||
  Discussion items send events
 | 
			
		||||
    The usual zope.lifecycleevent and zope.container events should be
 | 
			
		||||
    fired when discussion items are added, removed, or modified.
 | 
			
		||||
							
								
								
									
										59
									
								
								plone/app/discussion/architecture.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								plone/app/discussion/architecture.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
========================
 | 
			
		||||
Architectural Principles
 | 
			
		||||
========================
 | 
			
		||||
 | 
			
		||||
This document outlines architectural principles used in the design of
 | 
			
		||||
plone.app.discussion.
 | 
			
		||||
 | 
			
		||||
  **Discussion items have a portal_type**
 | 
			
		||||
    This makes it easier to search for them and manage them using existing
 | 
			
		||||
    CMF and Plone UI constructs.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are cataloged**
 | 
			
		||||
    It is possible to search for discussion items like any other type of
 | 
			
		||||
    content.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are subject to workflow and permission**
 | 
			
		||||
    Moderation, anonymous commenting, and auto approve/reject should be
 | 
			
		||||
    handled using workflow states, automatic and manual transitions, and
 | 
			
		||||
    permissions.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are light weight objects**
 | 
			
		||||
    Discussion item objects are as light weight as possible. Ideally, a 
 | 
			
		||||
    discussion item should be as lightweight as a catalog brain. This may mean 
 | 
			
		||||
    that we forego convenience base classes and re-implement certain interfaces. 
 | 
			
		||||
    Comments should not provide the full set of dublin core metadata, though 
 | 
			
		||||
    custom indexers can be used to provide values for standard catalog indexes.
 | 
			
		||||
 | 
			
		||||
  **Optimise for retrival speed**
 | 
			
		||||
    HTML filtering and other processing should happen on save, not on render,
 | 
			
		||||
    to make rendering quick.
 | 
			
		||||
 | 
			
		||||
  **Settings are stored using plone.registry** 
 | 
			
		||||
    Any global setting should be stored in plone.registry records.
 | 
			
		||||
    
 | 
			
		||||
  **Forms are constructed using extensible z3c.form forms**
 | 
			
		||||
    This allows plugins (such as spam protection algorithms) to provide
 | 
			
		||||
    additional validation. It also allows integrators to write add-ons that add
 | 
			
		||||
    new fields to the comment form.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are stored in a BTree container**
 | 
			
		||||
    This allows faster lookup and manipulation.
 | 
			
		||||
 | 
			
		||||
  **Discussion items are accessed using a dict-like interface**
 | 
			
		||||
    This makes iteration and manipulation more natural. Even if comments are
 | 
			
		||||
    not stored threaded, the dict interface should act as if they are, i.e.
 | 
			
		||||
    calling items() on a comment should return the replies to that comment
 | 
			
		||||
    (in order).
 | 
			
		||||
    
 | 
			
		||||
  **Discussion items are retrieved in reverse creation date order**
 | 
			
		||||
    Discussion items do not need to support explicit ordering. They should
 | 
			
		||||
    always be retrieved in reverse creation date order (most recent for).
 | 
			
		||||
    They can be stored with keys so that this is always true.
 | 
			
		||||
    
 | 
			
		||||
  **Discussion items do not need readable ids**
 | 
			
		||||
    Ids can be based on the creation date.
 | 
			
		||||
    
 | 
			
		||||
  **Discussion items send events**
 | 
			
		||||
    The usual zope.lifecycleevent and zope.container events are fired when 
 | 
			
		||||
    discussion items are added, removed, or modified.
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
Captcha Design Notes
 | 
			
		||||
====================
 | 
			
		||||
Captcha Plugin Architecture
 | 
			
		||||
===========================
 | 
			
		||||
 | 
			
		||||
This document contains design notes for the plone.app.discussion Captcha plugin
 | 
			
		||||
architecture.
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
=================================
 | 
			
		||||
plone.app.discussion design notes
 | 
			
		||||
=================================
 | 
			
		||||
============
 | 
			
		||||
Design Notes
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
This document contains design notes for plone.app.discussion.
 | 
			
		||||
 | 
			
		||||
@ -8,12 +8,12 @@ Storage and traversal
 | 
			
		||||
---------------------
 | 
			
		||||
 | 
			
		||||
For each content item, there is a Conversation object stored in annotations.
 | 
			
		||||
This can be traversed to via the ++comments++ namespace, but also fetched
 | 
			
		||||
This can be traversed to via the ++conversation++ namespace, but also fetched
 | 
			
		||||
via an adapter lookup to IConversation.
 | 
			
		||||
 | 
			
		||||
The conversation stores all comments related to a content object. Each
 | 
			
		||||
comment has an integer id (also representable as a string, to act as an OFS
 | 
			
		||||
id and allow traversal). Hence, traversing to obj/++comments++/123 retrieves
 | 
			
		||||
id and allow traversal). Hence, traversing to obj/++conversation++/123 retrieves
 | 
			
		||||
the comment with id 123.
 | 
			
		||||
 | 
			
		||||
Comments ids are assigned in order, so a comment with id N was posted before
 | 
			
		||||
@ -42,8 +42,8 @@ Factories
 | 
			
		||||
 | 
			
		||||
Comments should always be created via the 'Discussion Item' IFactory utility.
 | 
			
		||||
Conversations should always be obtained via the IConversation adapter (even
 | 
			
		||||
the ++comments++ namespace should use this). This makes it possible to replace
 | 
			
		||||
conversations and comments transparently.
 | 
			
		||||
the ++conversation++ namespace should use this). This makes it possible to 
 | 
			
		||||
replace conversations and comments transparently.
 | 
			
		||||
 | 
			
		||||
The Comment class
 | 
			
		||||
-----------------
 | 
			
		||||
@ -150,20 +150,18 @@ an auto-moderation 'white-list', e.g. by email address or username.
 | 
			
		||||
Forms and UI
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
The basic commenting display/reply form should be placed in a viewlet.
 | 
			
		||||
The basic commenting display/reply form is placed in a viewlet.
 | 
			
		||||
 | 
			
		||||
Ideally, the reply form should be inline, perhaps revealed with JavaScript
 | 
			
		||||
if enabled. This allows full contextualisation of replies. The current
 | 
			
		||||
solution, with a separate form that shows some context, is brittle and
 | 
			
		||||
over-complicated.
 | 
			
		||||
The reply form is dynamically created right under the comment when the user hits
 | 
			
		||||
the reply button. To do so, we copy the standard comment form with a jQuery 
 | 
			
		||||
function. This function sets the form's hidden in_reply_to field to the id of
 | 
			
		||||
the comment the user wants to reply to. This also makes is possible to use 
 | 
			
		||||
z3c.form validation for the reply forms, because we can uniquely identify the 
 | 
			
		||||
a reply form request and return the reply form with validation errors.
 | 
			
		||||
 | 
			
		||||
If we support quoting of comments in replies, we can load the text to quote
 | 
			
		||||
using JavaScript as well.
 | 
			
		||||
Since we rely on JavaScript for the reply form creation, the reply button is
 | 
			
		||||
removed for nonJavaScript enabled browsers.
 | 
			
		||||
 | 
			
		||||
As a fall-back for non-JavaScript enabled browsers, it is probably OK not to
 | 
			
		||||
support quoting and/or viewing of context, e.g. the user is taken to a standalone
 | 
			
		||||
'comment reply' form.
 | 
			
		||||
 | 
			
		||||
All actual forms should be handled using z3c.form and plone.z3cform's
 | 
			
		||||
ExtensibleForm support. This makes it possible to plug in additional fields
 | 
			
		||||
declaratively, e.g. to include spam protection.
 | 
			
		||||
The comment form uses z3c.form and plone.z3cform's ExtensibleForm support. This 
 | 
			
		||||
makes it possible to plug in additional fields declaratively, e.g. to include 
 | 
			
		||||
SPAM protection.
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user