Add-on: User Generated Content

The user generated content addon (UGC) allows your website to implement features like comments, votings, quizzes or form submissions.

Table of Contents

Working with the UGC as an Editor

There are currently six domains of user generated content this addon can handle:

  1. comments
  2. image uploads
  3. ratings
  4. form submissions
  5. quizzes
  6. votings

It provides you not only with the mechanisms of reliably persisting the user input, but also enables you to view each user's submission, export the user data and search the user data.

Comments

Show and Edit Comments

There are different possibilities to list the comments.

  1. the general overview with all comments (for browser-editing only)
  2. the document overview (1) with the number of comments that belong to a document (for browser and DeskClient)
  3. the document overview (2) with all comments that belong to a document (for browser and DeskClient)
  4. the comment overview with all answer-comments for one comment (for browser and DeskClient)

All overviews show all important information at a glance: name of the author of the comment, the creation date, if it is an answer-comment or a main comment, the corresponding document and its status, and the status of the comment itself.

Editors can open the edit dialog of a comment by clicking the pen icon. In this dialog, the editor can edit the comment, approve or disapprove it. The title and the comment can be edited. However, it is not possible to change the creation date. The username and the externalUserId can be changed if it has been enabled in the configuration by an admin. A shortcut to dis-/approve a comment is to click the corresponding button in the action menu.

Editors can reply directly to a comment, see "Reply to Comments".
To handle more than one comment at a time, you can use "batch processing". For more information see batch processing.

It is possible to delete comments using batch processing. However, if a comment is deleted, it can't be restored by editors. Therefore we advise that unwanted comments should be disapproved, not deleted.
Mandatory fields in the edit dialog and in the reply dialog are marked with red stars.

An editor can also write completly new comments (instead of replying to an existing comment) using the answer button above the data table at the document overview.

There is also an overview of the number of dis-/approved comment that belong to a document (1):

comments

Batch Processing

Batch processing helps the editor for faster editing and dis-/approving comments. With this feature it is, for instance, possible to dis-/approve a bunch of comments with one click.

Above the table in the table menu is the dropdown for the comment selection, the column configuration, the reload button and the create-a-comment button.
The dropdown for the comment selection includes the following options: all comments, all dis-/approved comments and all unedited comments. After choosing a comment, the table menu changes to the approval menu with following options: dis-/approved, delete, delete all comments of this user.

It is possible to delete all comments of a user, if the function is activated and the external user IDs are stored in an external database. The user will be identified by the externalUserID, not by user name.

It is possible to delete comments using batch processing. However, if a comment is deleted, it can't be restored by editors. Therefore we advise that unwanted comments should be disapproved, not deleted.

Reply to Comments

Editors can reply directly to comments in a pop-up dialog after clicking the arrow button of the action menu.

reply

The red-encircled symbols in the image show the editor if it is an answer-comment or a main comment. Clicking on the discussion button or the answer comment counter leads to the overview of a comment with all answer-comments.

Working with Multi-Accounts

If multi-accounts are configured by a system admin, the editor can choose one of the multi-accounts for replying a comment. The advantage is that different editors can answer under one name.

multi user fuction

Filter

To help getting a good overview of the comments, there are different filtering options. For instance, the word filter allows to search for specific terms within comments and their headlines. Beyond that, there are several other filters:

  • serial programm: Filters by the serial programm for which a comment has been submitted. This filter also allows selecting more than one serial programms.
  • comment by: It is possible to filter for a multi-account or all comments of guest users (if the comment type "storeUserComment"/ the flag guest will be used in the playout).
  • document type: Finds all comments that belong to the choosen document type.
  • status: It is possible to filter for all comments, deleted, edited or dis-/approved comments or untouched comments.
  • creation date: Shows only comments that have been submitted in a certain period.
  • markers: Shows all unmarked, all marked comments or those with the selected marker.
filter

Table Configuration

Editors will be allowed to configure the view of the table as it's needed. For example it will be possible to mask the user ID or type of comment if the information isn't needed. This configuration will be saved for a multi-account, so that the table does not always has to be configured again.

table configuration

Mark Comments

In order to assign comments to an area or to record them for the next processing step there is the possibility to mark comments. Individual comments can be marked in the edit dialog. If several comments are to be marked, all selected comments can be marked with batch processing.

Marked comments can be recognized by the color at the beginning of a table line. To be able to process marked comments faster you can filter for markers and select comments with a specific marking.

Write Notes on Comments

If notes are enabled comments can be provided with notes. Notes can be used to provide hints for the further processing of comments. In the edit dialog notes can be written and edited. If a note is attached to a comment a preview of the content can be seen in the footer. If the mouse pointer is held over the preview text, the entire content of the note is displayed in a tooltip. A click on the preview text opens the edit dialog. In addition, it can be recognized by the information symbol on the edit button in the action bar if the comment has a note.

Pinning Comments

If this feature is enabled, comments may be pinned. Pinned comments can be used for a variety of purposes. The most common use case is to show certain comments (e.g. editorial comments) always at the top of the comment section.

Comments can only be pinned if they are direct responses to a document, not replies to other comments. Comments that meet this criterion can be pinned in the comments creation dialog or in the edit dialog of the UGC webapp. A pin symbol at the beginning of a row in the comment overview marks a pinned comment.

Ratings

Using the ratings function, website user can submit how they liked an articel, video, etc. Submitted ratings can be displayed in the UGC backend and may also be displayed in the website. The rating lists all documents that have ratings. The rating list contains the average rating of the users and the number of votes.

ratings ugc

Forms: Creating Forms (DeskClient)

Using UGC forms, it is possible to create different types of forms, from simple comment forms to sophisticated surveys. With the FormWizard, forms can be composed of different types of fields: date, text field (single-line or text area), number, dropdown, radio button, checkbox etc. Furthermore, fields can be grouped, and forms may be split into several pages.

The available input field types stem from the html5 arguments.
formular wizard
Examples for input field types
NameDescription
textsingle-lined text field; optional: restrict the length of text
text areamultiline input field for any text
selecta list of options, whereof only one can be choosen (typically a dropdown menu)
radio buttona list of options, whereof only one can be choosen (typically listed one below the other)
checkboxsimple input box that can be un-/checked
filefield that allows selecting one or more local files for upload
emailinput for e-mail adresses (for instance mail@subshell.com) that performs validity checks
dateinput for dates
numberonly integer values are allowed
passwordshows asterisk instead of the entered text
resetbutton to empty all input fields
submitbutton to send the form

Technical Background - How Does this Add-on Work?

The addon consists of three components:

  1. taglib: tags and node functions to store the user input on the delivery machine and to retrieve the evaluated results
  2. submitter: periodically submits the user input from the delivery machine to the administration webapp
  3. ugc-webapp: an administration application that persists the user input, provides a web interface for administration and a JSON API for retrieving the results

Each submission of content by the user is handled asynchronously. I.e., when a user submits a comment on one of your deliveries, a JSON file is stored in the file system. Within the delivery webapp there is a thread, called the submitter, to check for newly created files. These files are read and submitted as a bundle to a backend application, which persists the submissions in a database. This application is called ugc-webapp.

The ugc-webapp has a web interface to manage the submitted content. The web interface can be accessed either by browser or by configuring browser tabs that allow the editors to access the administration interface in Sophora for just the document they have opened.

To access the stored information in your delivery there are tags and node functions. These will call the ugc-webapp and retrieve the user generated content via HTTP and JSON. Caching is supported and cache fragments will be deleted, if there are changes within the ugc-webapp related to that content.

Features

Comments

A comment consists of

  • the name of the user,
  • a headline and
  • a text representing the content of the comment.
  • It may optionally have a parentID when it is a response to another comment.
  • Optionally a type like EDITOR or GUEST.

When the comment is stored in the database all HTML tags will be removed and it is checked for spam words. If a spam word is found, it will be marked as spam in the backend. The list of spam list is managed in a document in sophora. Comments will not appear in your delivery before they are approved in the backend.

Responses

Comments may be a response to another comment in which case they get a parentID. There may only be one level of responses. You can not awnser to a response, only to a "root comment" (one without a parentID).

Frontend

  • store a comment with the storeUserComment tag
  • retrieve pages of comments with the getUserComments tag

Backend

  • view, edit, delete and approve comments
  • list recent comments (global or per document)
  • spam detection based on a configurable list of spam words
  • answer to user comment or add a comment to a document as an editor

Image Uploads

An image upload is similar to a comment except that it has an image attached. It consists of:

  • the name of the user,
  • a headline,
  • a text and
  • an image file (JPEG, PNG, GIF)

Image uploads are stored in the database but can be imported into Sophora in the backend.

Frontend

  • store image uploads with the storeImageUpload tag with automatic resize and scan for viruses

Backend

  • view, edit, delete comments
  • list recent comments (global or per document)
  • import images to Sophora (see button below)
image import

Ratings

A rating is a number of points a user attributes to a document. How much points are allowed is not limited by the addon but by the usage of the tags. The evaluation of all ratings just calculates the average number of points attributed to a document.

Frontend

  • store a rating with the storeUserRating tag
  • retrieve ratings with the getRatingEvaluation node function

Backend

  • overview over ratings for all documents
Legacy ratings: There is simpler version of ratings implemented which is deprecated. These ratings use the storeRating tag and the getRating node function. Please use the new rating implementation!

Form Submissions

A form has a configurable set of fields. The fields are determined by the form document in Sophora.

Frontend

  • store a form submission with the storeForm tag

Backend

  • view, filter and sort the submissions
  • export up to 65535 submissions as XSL
  • reset submissions

Quizzes

A quiz has a set of questions. Each question has a set of answers. Each answer has a number of points attached. The addon stores the answers the user has given and calculates the aggregate points for these answers. This allows the addon to even recalculate the sum of points for a user, when the points for a single answer have been changed.

Additionally, a quiz can have a form. This form can be used to provide personal information to a user submission and the results a user achieved.

Frontend

  • store quizzes with the storeQuiz tag

Backend

  • view, filter and sort (e.g. by points) the submissions
  • export up to 65535 submissions as XSL
  • reset submissions
  • recalculate points

Votings

A voting is a document that has several voting items. There are two types of voting available. They differ in the interpretation of the results:

  • simple
  • ranking

In a simple voting the user is allowed to vote for a specified number of items. Each vote counts as one point for the voting item.

In a ranking voting the voting items are ranked by the user. For a correct evaluation, the total number of items that can be ranked has to be defined. For the evaluation the backend now takes the item ranked first and gives it the number of possible items to be ranked as points. The item that was ranked second gets one point less as the first and so on.

Similar to a quiz a voting can have a form with personal information of the user.

Use media in votings

A voting on media like images, videos or audio requires an extension of the item nodetype and the templates.

  1. Extend the voting item nodetype in Sophora with media like images, videos or audio.
  2. Appropriately adapt the JSP templates to vote on various media.

Frontend

  • store votings with the storeVoting tag
  • get voting reports with the node function getReport

Backend

  • view, filter (e.g. who voted for whom) and sort the submissions
  • view voting results
  • export up to 65535 submissions as XSL
  • export voting report as XSL
  • reset voting
  • manually start a configurable manipulation check (details about manipulation control)

Full-Text Search in Form Data

Forms, quizzes and votings (with forms) save their data in form data. These can be searched through for personal information of users.

The full-text search in form data can be used to find personal data of a user, who entered these information himself in a form. Furthermore, the IP address and the creation date are also displayed. The search can be found at Debug -> Formular Suche.

If there is a matching result, it will be represented in a table with the columns: URL of the document | Fieldname | Content | IP-Adress | Date. Also it's possible to export the results as XLS.

The initial search for a specific term can take a few minutes. The search result is cached. Displaying the different pages of the result takes less time afterwards.

ugc-formsearch

Restriction on Structure Nodes

It's possible to restrict the search on a structure node path or a UUID. The search then takes place only under this node. If you enter an invalid structure node path or a UUID you will get a notification.

Double-Opt-In

Double-Opt-In means that a user has to confirm his submission via e-mail. This addon supports this feature for forms, quizzes and votings. For votings usage of this feature has the effect that the submission is not evaluated before the user confirmed the e-mail. For quizzes and forms this only has the effect that the submissions are marked differently in the backend.

Getting Started

This guide assumes that you run the tomcat with the delivery webapp and the ugc-webapp on the same server.

Repository

In order to use all features of the user generated content addon some configurations have to be made and some documents and scripts need to be imported. There is file named ugc-webapp-2.1.X-sophora.zip in our Maven repository (X needs to be replaced by the version you are using). The archive consists of configurations and documents in the Sophora XML format. If you want to try the addon in a test environment, just import all of these files and use the order of the folders as import order.

If you want to configure your production system, you don't need to import the node types labeled "04_example_nodetypes". They are helpful for a fresh start in a system where there haven't been any quizzes, votings and forms before. You don't need to use them, if you configure your node types accordingly and configure the usage of your properties in the configuration file of the ugc-webapp.

Finally, you need to activate the parts of the addon you bought. Get in touch with us for this step.

Database

This addon needs to have a database in a MySQL database system. Create a database and ensure that you have a user that has been granted all rights to this database (at least SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, CREATE TEMPORARY TABLES).
To be able to use emojis like 😀 in the user comments you have to add the lines

character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

in the MySQL server configuration. Furthermore in the database url of the ugc-webapp.json file you have to add ?useUnicode=true. When creating the database the collation should be utf8mb4_unicode_ci.

Due to limitations of the MySQL Connector, it cannot switch to a custom connection charset utf8mb4 using connection URL parameters (see Item 14.12 of the MySQL documentation). Thus, you have to set the defaults to utf8mb4 for the MySQL server in general.

Install UGC-Webapp

Get the webapp packed as tar.gz that has the same minor version as your Sophora system from our Maven repository. It's recommended to install the addon by using the following folder structure. Otherwise the ugc-webapp.sh skript should be modified to meet your folder structure.

cms-install-directory
-- apps
---- ugc-webapp-2.1.X
------ ugc-webapp.sh
------ ugc-webapp-2.1.X.war
---- ugc-webapp > Symbolic link to ugc-webapp-2.1.X
---- ...
-- ugc-webapp
---- config
------ ugc-webapp.json
------ logback.xml
---- logs
---- ugc-webapp.sh > Symbolic link to ../apps/ugc-webapp/ugc-webapp.sh
----ugc-webapp-2.1.X.war > Symbolic link to ../apps/ugc-webapp/ugc-webapp-2.1.X.war

This hierarchy is analogous to the directory structure of the Sophora server.

In ugc-webapp.json you should adapt the configuration of the connection to your Sophora server and database to match your settings.

{
	vmargs: "-Xmx1g",
	// this configures the connection to your Sophora server. Adapt this to match with your settings
	sophoraServer: {
		host: "http://localhost:1196",
		username: "admin",
		password: "admin"
	},
	// configuration of the database connection. Adapt this to match with your settings
    database : { 
    	// url of the jdbc connection, the last part is the name of the datadase 
		url : "jdbc:mysql://localhost:3306/usercontent", 
		// username for database 
		user : "USERNAME", 
		// password for database 
		password : "PASSWORD"
	},
	
	// configure the port of the embedded jetty
	jetty: {
		httpPort: 9080
	},
	
    // ratings, image uploads and comments can be enabled for a list of node types
	rating: {
		primaryTypes: ["sophora-content-nt:story"]
	},
	
	imageUpload: {
		primaryTypes: ["sophora-content-nt:story"]
	},
	
	comment: {
		primaryTypes: ["sophora-content-nt:story"]
	},
	
    // make the ugc-webapp to your main app to enable full functionality
	isMainApp: true
}

You can start the ugc-webapp by running ./ugc-webapp.sh start.

Enable the Add-on in Your Delivery Webapp

To enable the addon in your webapp, you have to add two dependencies to your pom.xml

<dependency>
	<groupId>com.subshell.sophora</groupId>
	<artifactId>ugc-submitter</artifactId>
	<version>X.X.X</version>
</dependency>
<dependency>
	<groupId>com.subshell.sophora</groupId>
	<artifactId>ugc-taglib</artifactId>
	<version>X.X.X</version>
</dependency>

Additionally, you have to enable the Spring context for the submitter in the web.xml of your webapp

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:spring-submitter.xml</param-value>
</context-param>
	
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Now, you can use the tags of the taglib to save user generated content.

UGC-Demo Example Webapp

The project ugc-demo offers the possibity to test the features offered by the user generated content. Get the ugc-demo packed as tar.gz that has the same minor version as your Sophora system from our Maven repository.

Configuring UGC-Demo

In src/webapp/WEB-INF/sites/sophora.properties you should adapt the configuration of the connection to your Sophora server to match your settings. You can also adapt the configuration to activate caching.

Exemplary sophora.properties File:

sophora.serviceUrl=http://localhost:1196
 sophora.delivery.client.username=admin 
sophora.delivery.client.password=admin
 # Enable or disable DevToolbar 
sophora.delivery.showDevToolbar=true 
# Caching configuration 
sophora.delivery.cache.db=derbyng
 sophora.delivery.cache.enabled=false
sophora.delivery.cache.directory=../ugc-demo/cache
sophora.delivery.cache.remove.notify.email= 
sophora.delivery.cache.remove.filterPattern.structureNode=
 sophora.delivery.defaultNamespaces=sophora, sophora-content,sophora-extension,sophora-ugc 
sophora.usercontent.baseurl=http://localhost:9080/ 
sophora.delivery.function.packageNames=com.subshell.sophora.usercontent.functions, com.subshell.sophora.usercontent.demo 
sophora.delivery.model.packageNames=com.subshell.sophora.usercontent.functions

For further details about the properties supported in sophora.properties you can take a look at the documentation of the delivery configuration.

In src/webapp/WEB-INF/classes/submitter.properties you should adapt the url of the submitter to match your settings, for example:

sophora.usercontent.submitter.webapp.url=http://localhost:9080/rest/submissions
Local Configuration Directory

Instead of adapting sophora.properties and submitter.properties in the ugc-demo you can use a local configuration directory. Therefore you can adapt src/webapp/META-INF/context.xml to match your local configuration directory's path.

Exemplary context.xml File:

<?xml version='1.0' encoding='utf-8'?>
 <Context>
 	<Parameter name="jsonDump" value="../ugc-demo/json_dump"/>
 	<Parameter name="localConfigDirectory" value="../ugc-demo"/>
 	<Environment name="logPath" value="../ugc-demo/logs" type="java.lang.String"/>
 </Context>

Starting UGC-Demo

You can start ugc-demo using Apache Tomcat. The URL to access the demo depends on your configuration. In a local environment e.g. you can access it under:

http://localhost:8080/ugc-demo/demosite/index.html

Taglib

<dependency>
  <groupId>com.subshell.sophora</groupId>
  <artifactId>ugc-taglib</artifactId>
  <version>X.X.X</version>
</dependency>

The taglib can be used to store and retrieve user generated content. In our Maven-Repository we provide you with the TLDDoc for the taglib. The submissions are stored as serialized JSON in a folder called json dump.

Besides the basic features the taglib allows you to monitor the number of submissions and limit the number of submissions the taglib should accept for a given intervall. When this limit is exceeded

  1. no more submissions will be accepted
  2. an e-mail is sent notifying you about this event

Tags

getUserComments, getAllUserComments, getPinnedUserComments

These three tags all use the same parameters and structure as described in the tables below. They differ in the way they deal with pinned comments. The getUserComments tag returns only comments that are not pinned, getPinnedUserComments returns only pinned comments, and getAllUserComments returns pinned and unpinned comments mixed. There are no differences for all three tags when it comes to responses to comments, because those cannot be pinned.

The tags getAllUserComments and getPinnedUserComments are available since 2.5.24.
Parameters
ParameterDescription
ascendingboolean parameter to specify if sorting is ascending or not. Default: false.
asJsonIf true, returns the comments as serialized JSON. Default: false.
externalIdExternal ID of the document the comments should be retrieved for
orderByThe field the comments should be sorted by. Default: "createdAt"
pageNumberThe number of the page starting with 0
pageSizeThe size of the page.
maxResponsesThe maximum number of responses to fetch for every comment. Default: Interger.MAX_VALUE (i.e. "get all responses")
scopeScope of the result variable
varThe name of the result variable. Type: SimpleUserCommentWithResponsesPageResponse
SimpleUserCommentWithResponsesPageResponse
AttributesDescription
currentPageThe current page
entriesA list of comments. Type: SimpleUserCommentWithResponses
noOfPagesThe total number of pages
noOfResultsThe number of comments on the current page
SimpleUserComment
AttributesDescription
contentContent of the comment
headlineHeadline of the comment
timestampUnix timestamp as long value
externalUserIdThe ID of the user
usernameName of the user
SimpleUserCommentWithResponses
AttributesDescription
...The same attributes as SimpleUserComment
responsesA list of the responses. Type: SimpleUserComment
totalResponseCountTotal number of responses to this comment. Note: This number may be bigger than the size of responses when maxResponses has been set.

getSingleUserComment

Parameters
ParameterDescription
commentIdThe ID of the comment
ascendingboolean parameter to specify if sorting is ascending or not. Default: false.
asJsonIf true, returns the comments as serialized JSON. Default: false.
externalIdExternal ID of the document the comments should be retrieved for
orderByThe field the comments should be sorted by. Default: "createdAt"
pageNumberThe number of the page starting with 0
pageSizeThe size of the page.
scopeScope of the result variable
varThe name of the result variable. Type: SingleSimpleUserCommentPageResponse
SingleSimpleUserCommentPageResponse
AttributesDescription
currentPageThe current page
noOfPagesThe total number of pages
noOfResultsThe number of comments on the current page
commentThe comment with the given ID. Type:
responsesA list of the responses. Type: SimpleUserComment

storeDoubleOptIn

This tag should be used on the site where the user is directed to confirm his submission.

Parameters
ParameterDescription
optInIdID of the opt-in event
scopeScope of the result variable
varThe name of the result variable

storeForm

Stores a form submission to file. Returns true, if successful.

Parameters
ParameterDescription
externalIdExternal ID of the document
formDataMap<String,Object> where the key is the name of the form field and the value is the user input for that field
ipAddressIP address as String
referrerReferrer value of the HTTP request
scopeScope of the result variable
userAgentUser agent value of the HTTP request
varName of the result variable

storeImageUpload

The form data is expected to be of the type multipart/form-data in UTF-8 encoding. This tag extracts the form data from the request directly. The parameters that are expected are:

  • externalUserId: the ID of the user. If the user is identified by his name, the field must be filled with the username.
  • username: the name of the user
  • headline: headline of the comment
  • content: text of the comment
  • imageData: the image to be uploaded (type needs to be file)
  • externalId: the external ID of the document
  • documentType: the primary type or node type of the document
  • categoryExternalId: the external ID of the document describing the category of this comment, only relevant, if categories are used.
Parameters
ParameterDescription
varSet the name of the variable that stores the result.
scopeScope of the result variable
Attributes of the result
AttributeDescription
successboolean property that signals if saving the upload to file was successful
errorEnum of Type ImageUploadError that holds the information on the type of error

storeQuiz

Stores a user submission for a quiz to file. Returns true, if successful.

Parameters
ParameterDescription
externalIdExternal ID of the document
formDataMap<String,Object> where the key is the name of the form field and the value is the user input for that field
ipAddressIP address as String
referrerReferrer value of the HTTP request
scopeScope of the result variable
selectionsMap<String,Object> where the key is the external ID of the question document and the value is the ID of the answer
userAgentUser agent value of the HTTP request
varName of the result variable

storeUserComment

Stores a user comment to file. Returns true, if successful.

Parameters
ParameterDescription
contentContent of the comment
documentTypePrimary type of the document
externalIdExternal ID of the document
parentIdThe id of the parent comment, if this is a response
headlineHeadline of the comment
ipAddressIP address as String
scopeScope of the result variable
serialProgramSerial program of the document, only relevant, if serial programs are used
categoryExternalIdThe external ID of the document describing the category of this comment, only relevant, if categories are used.
externalUserIdThe ID of the user. If the user is identified by his name, the field must be filled with the username.
usernameName of the user
varName of the result variable
typeType of the comment. Values could be "EDITOR", "GUEST" or null (default)

storeUserRating

Stores a user rating to file. Returns true, if successful.

Parameters
ParameterDescription
documentTypePrimary type of the document
externalIdExternal ID of the document
ipAddressIP Address as String
scopeScope of the result variable
serialProgramSerial program of the document, only relevant, if serial programs are used
valueValue of the rating
varName of the result variable

storeVoting

Stores a voting result of a user to file. Returns true, if successful.

Parameters
ParameterDescription
externalIdExternal ID of the document
formDataMap<String,Object> where the key is the name of the form field and the value is the user input for that field
ipAddressIP address as String
referrerReferrer value of the HTTP request
scopeScope of the result variable
selectionsMap<String,Object> where the key is the ID of the voting item and the value is the rank, which the user gave to the item (starting with 1)
userAgentUser agent value of the HTTP request
varName of the result variable

Example usage for a voting:

<jsp:useBean id="formData" class="java.util.HashMap" scope="request" />
<jsp:useBean id="selections" class="java.util.HashMap" scope="request" />
<%-- Here you have to insert the form data and the answers to the map --%>
<c:set var="referrer"><%=request.getHeader("referer")%></c:set> 
<sophora:storeVoting userAgent="${header['User-Agent']}" externalId="${param.votingExternalId}" formData="${formData}" selections="${selections}" ipAddress="<%=request.getRemoteAddr()%>" referrer="${referrer}" />

In this example, the map input should be provided as follows:

  • the keys of the form data map should be the field name, the values should be the values for that field
  • the keys of the selections map should be the UUID of the voting item, the values should be the rank the user has given to this voting item (starting with 1)

Node Functions

The node function gets its information from the webapp and supports caching. I.e., every time the user makes a submission the cache gets invalidated for that document.

Voting: getSimpleReport/getRankingReport

For evaluation of an voting there exists a node function. On the voting document node you invoke depending on the voting type the functions getSimpleReport or getRankingReport to get the report bean. The bean provides you with following information:

PropertyDescription
${report.headline}The headline of the voting document
${report.noOfParticipants}The number of participants in this voting
${report.winner}A bean with the voting item report for the voting item that won the voting
${report.votingItemReports}A list of voting item reports sorted by points

Each voting item has its own report. Each item has the following properties:

PropertyDescription
${item.name}The name of the voting item
${item.uuid}UUID of the item
${item.points}Points that were calculated for this item
${item.votesPerPlace}The votes this item got for each possible place. Only available, if the voting is designed as a ranking between all voting items.

There is a legacy report function called getReport, which returns a ranking report. Please refrain from using this function as it might be removed in future versions.

Rating: getRatingEvaluation

For each document you can get an evaluation of the already submitted Ratings by calling the node function getRatingEvaluation. The bean you get in return has the following properties:

PropertyDescription
${rating.externalId}The external id of the document the rating is for
${rating.numberOfRatings}The number of rating submissions
${rating.average}The average of all submitted ratings

Configuration of the Taglib

sophora.properties
PropertyDescriptionDefault
sophora.usercontent.baseurlSet the address of your adminstration web app. If the administration webapp is using HTTP basic authentication, you may configure this in the form of http://user:password@localhost:9080/.http://localhost:9080/
sophora.delivery.function.packageNamesAppend "com.subshell.sophora.usercontent.functions" to this list for registering the node function
sophora.delivery.model.packageNamesAppend "com.subshell.sophora.usercontent.functions" to this list for registering the node function
sophora.usercontent.maxImageWidthThe maximum width for image uploads (larger images will be scaled)2000
sophora.usercontent.maxImageHeightThe maximum height for image uploads (larger images will be scaled)2000
sophora.usercontent.minImageWidthThe minimum width for image uploads to accept960
sophora.usercontent.minImageHeightThe minimum height for image uploads to accept960
sophora.usercontent.maxImageFileSizeThe maximum file size for image uploads in bytes. Larger files will not be accepted.5242880
sophora.usercontent.clamdHostHost of the clamd service to scan image uploads for viruses
sophora.usercontent.clamdPortPort of the clamd service to scan image uploads for viruses

You should at least configure the URL to your ugc-webapp in the property sophora.usercontent.baseurl to display user generated content in your webapp.

Load Monitoring Configuration

If you want to use the load monitoring feature, you need to start a Spring context in your delivery webapp, that has this functionality. To achieve this you need to add the following in your web.xml:

<context-param> 
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring-usercontent.xml</param-value> 
</context-param> 
<listener> 
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
</listener>

There are several parameters to configure this feature in a file called taglib.properties. This file can be placed in your classpath and in the config folder of the webapp. The properties in the config folder have precedence.

taglib.properties
PropertyDescriptionDefault
sophora.usercontent.loadrestriction.enabledRestrict the number of submissions allowedfalse
sophora.usercontent.loadrestriction.updateintervalThe interval in milliseconds to write a new load information, which will be sent to the ugc-webapp10000
sophora.usercontent.loadrestriction.requestsPerMinuteThe number of requests10000
sophora.usercontent.loadrestriction.email.template.subjectSubject of the notification e-mail\u00dcberlastsituation auf Server
sophora.usercontent.loadrestriction.email.template.textText of the notification e-mailDer Server hat die erlaubten Requests pro Minute \u00fcberschritten
sophora.usercontent.loadrestriction.email.template.fromFrom of the notification e-mail
sophora.usercontent.loadrestriction.email.template.replyToReply-To of the notification e-mail
sophora.usercontent.loadrestriction.email.template.toTo whom shall the notification e-mail be sent.
This field is mandatory
sophora.usercontent.loadrestriction.email.template.ccCC of the notification e-mail
sophora.usercontent.loadrestriction.email.template.bccBCC of the notification e-mail
sophora.usercontent.loadrestriction.email.transport.hostHostname or IP of the SMTP server
sophora.usercontent.loadrestriction.email.transport.portPort of the SMTP server25
sophora.usercontent.loadrestriction.email.transport.userUsername for SMTP server
sophora.usercontent.loadrestriction.email.transport.passwordPassword for SMTP server
sophora.usercontent.loadrestriction.email.transport.starttls.enableUse starttls for the connection to the SMTP servertrue
sophora.usercontent.loadrestriction.minutesBetweenNotficiationsSend notifications only certain number of minutes after the last notification20
sophora.usercontent.loadmonitoring.enabledEnable the reporting of the number of submissions to the ugc-webapptrue

Submitter

<dependency>
  <groupId>com.subshell.sophora</groupId>
  <artifactId>ugc-submitter</artifactId>
  <version>X.X.X</version>
</dependency>

The submitter is responsible to send the serialized JSON files in the json dump to the ugc-webapp. To perform these tasks it runs inside your delivery webapp. If the backend can not be reached, valid JSON files remain in the folder normal. The content will not be lost even if the Submitter is restarted and will be sent when the backend is reachable.

The json dump consists of the following folders:

  1. invalid: these files were marked invalid by the webapp
  2. noHandlersFound: these files couldn't be submitted as the application doesn't know how to handle them
  3. normal: user generated content that still has to be submitted
  4. priority: load information, which will be prioritized, when submitting
  5. skipped: these files were skipped because they are duplicates

Configuring the Submitter in a Webapp

In order for the submitter to be used in your webapp, you need to add a listener to the web.xml of your delivery webapp

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring-submitter.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

If you want to use the load monitoring feature of the taglib as well, you need to use spring-usercontent.xml instead of spring-submitter.xml.

Your context.xml needs to be adapted as well. You need to configure the folder for the json dump with the parameter jsonDump. E.g.:

<?xml version='1.0' encoding='utf-8'?>
<Context>
	<Parameter name="jsonDump" value="/cms/webapps/sophora-demosite/json_dump"/>
	<Parameter name="localConfigDirectory" value="/cms/webapps/sophora-demosite/config"/>
	<Environment name="logPath" value="/cms/webapps/sophora-demosite/logs" type="java.lang.String"/>
</Context>

There is also a file called submitter.properties in either the classpath of your webapp or the config directory of the webapp. It allows to configure central aspects of the behaviour of the submitter.

It is important that you configure the right host and port of the ugc-webapp in sophora.usercontent.submitter.webapp.url. As this is the URL the submissions get sent to.

submitter.properties
PropertyDescriptionDefault
sophora.usercontent.submitter.checkintervalHow often should the json dump be checked for new files? Time in milliseconds.10000
sophora.usercontent.submitter.submissionintervalHow often should the files be submitted? Time in milliseconds.10000
sophora.usercontent.submitter.webapp.urlThe URL of the submission handler at the ugc-webapp. (Replace localhost and 9080 with the hostname and port of your ugc-webapp)http://localhost:9080/rest/submissions
sophora.usercontent.submitter.webapp.usernameIf the URL of the submission handler is protected by HTTP basic authentication, you can configure a username for requests. (since 2.5.11)
sophora.usercontent.submitter.webapp.passwordIf the URL of the submission handler is protected by HTTP basic authentication, you can configure a password for requests. (since 2.5.11)
sophora.usercontent.submitter.webapp.maxNumberOfSubmissionAttemptsIf a submission attempt fails, how often should this attempt be retried?5
sophora.usercontent.submitter.webapp.timeBetweenSubmissionAttemptsIf a submission attempt fails, how many milliseconds should the submitter wait to retry?50
sophora.usercontent.submitter.webapp.cleanUpDelayInDaysRemove files from the invalid folder that are older than X days.30
sophora.usercontent.submitter.webapp.maxNumberOfObjectsPerSubmissionHow many files should be submitted in each submission attempt?100

Configuring the Submitter in an Arbitrary Spring Context

If you don't want to start the submitter in a delivery webapp, it's also possible to use a Java configuration class. The important difference is, that you have to use the property sophora.usercontent.submitter.jsonDump instead of the context parameter to specify the path of the jsonDump. All other parameters listed above can be set in the properties file of your Spring context.

The following example shows, how you could add the configuration class to your Spring java configuration.

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; 
import com.subshell.sophora.usercontent.submitter.EmbeddedSpringConfig;
@Configuration
@Import(EmbeddedSpringConfig.class)
public class YourConfiguration {
 ...
}

Of course, you can use the @ComponentScan annotation to make your Spring context use the submitter configuration.

UGC Webapp

As a separate web application, it handles the task to persist user input. You can start the ugc-webapp by running ./ugc-webapp.sh start. Additionally, it provides a web interface to view the user data and export it. The configuration of the webapp is done with a file called ugc-webapp.json. It's written in the popular JSON format. In this example all values are the default values.

{
  // jvm arguments as a single string
  vmargs : "-Xmx1g",
  // all configuration parameters regarding the connection to the sophora server
  sophoraServer : {
    // the user the ugc webapp should use to login into sophora
    username : "admin",
    // password for this user
    password : "admin",
    // address of the sophora server
    host : "http://localhost:1196",
    // if you want to use a proxy, the hostname or ip address of the proxy
    proxyHost : null,
    // the name of the proxy user
    proxyUser : null,
    // the password of the proxy user
    proxyPassword : null,
    // port of the proxy server
    proxyPort : 0,
    // number of connection retries
    connectRetries : 3,
    // time between connection retries 
    connectRetryInterval : 10,
    // directory of the slaves.xml that holds the information on the sophora cluster
    slavesInfoDir : ""
  },
  
  // configuration of the database connection
  database : {
    // url of the jdbc connection, the last part is the name of the datadase
    url : "jdbc:mysql://localhost:3306/usercontent",
    // username for database
    user : "root",
    // password for database
    password : "rootroot",
    // enable jpa caching
    jpaCache : false
  },
  // configuration of caching
  caching : {
    // url of the cache servlet
    cacheServerletUrl : null,
    // the delivery group for the flush action
    cacheServletGroup : null,
    // password for the cache servlet
    cacheServletPassword : null,
    // delay invalidation tasks by X seconds
    invalidationRequestDelay : 30
  },
  // configuration of the double opt in mechanism
  doubleOptIn : {
    // configuration of the smtp server
    transport : {
      // hostname/address
      host : "localhost",
      // port
      port : 25,
      // user for smtp
      // if the user and password properties are null, no authentication is attempted.
      user : null,
      // password
      // if the user and password properties are null, no authentication is attempted.
      password : null,
      // use starttls
      starttls : true
    },
    // configuration of the template message for the e-mail
    template : {
      // from field
      from : null,
      // subject field
      subject : "Please verify your email address",
      // reply to field
      replyTo : null,
      // cc field
      cc : null,
      // bcc field
      bcc : null
    }
  },
  // configuration of comments
  comment : {
    // list of all content types as string that should support this feature
    primaryTypes : [ ],
    // how many comments should be allowed per ip address in 10 seconds?
    limitPerTenSeconds : 1,
    // how many comments should be allowed per ip address in a minute?
    limitPerMinute : 2,
    // how many comments should be allowed per ip address in a day?
    limitPerDay : 100,
    // is the multiuser feature enabled?
    multiUserEnabled: "true",
    // the image variant of the image of an editor
    avatarVariant: "small", 
    // the primaryType of the editor
    editorNodetype: "sophora-ugc-nt:ugcEditor",
    // the property name of the avatar image of the editor
    editorAvatarProperty: "sophora-ugc:avatar",
    // the property name of the name of the editor
    editorNameProperty = "sophora-ugc:name",
    // the maximal allowed size for the headline. Values between 0 and 65135 are permitted.
    headlineSize: 255,
    // the maximal allowed size for the content.Values between 0 and 65135 are permitted.
    contentSize: 1000,
    // additional space in the field content to edit.Values between 0 and 400 are permitted.
    editTolerance: 0
  },
  // configuration of the spam list that is used for comments
  spamList : {
    // the multi string property of the spam list document with the spam words in it
    spamWordsProperty : "sophora-ugc:spamWords",
    // primary type of the spam list document
    primaryType : "sophora-ugc-nt:spamList",
    // external ID of the spam list document
    externalId : "49bc5b8c-f37e-45a4-9cc3-c16b38f3b569"
  },
  // configuration of form submissions
  form : {
    // the form field that should be treated as e-mail field
    formFieldName_Email : "email",
    // the boolean property that defines if double opt in is active
    doubleOptInProperty : "sophora-ugc:useDoubleOptIn",
    // the string property that is used for the body of the double opt in message
    optInEmailTextProperty : "sophora-ugc:emailText",
    // the string property for the subject of the double opt in message
    optInEmailSubjectProperty : "sophora-ugc:emailSubject",
    // the child node of the form fields
    formFieldsChildnode : "sophora-ugc:formFields",
    // the string property of the field name
    fieldNameProperty : "sophora-extension:formfieldname",
    // the string property of the field style (E-Mail, Text)
    fieldStyleProperty : "sophora-extension:fieldstyle",
    // the string property of the label
    labelProperty : "sophora-extension:question",
    // the boolean property that says if the field is required
    requiredProperty : "sophora-extension:formrequired",
    // string property for the headline of the form
    headlineProperty : "sophora-ugc:title",
    // the node type of the form
    primaryType : "sophora-ugc-nt:form",
	// prefer the form wizard property to the childnode to get the form fields
	useFormWizard: true,
	// string property for the form wizard data
	formWizardProperty: "sophora-ugc:formWizard",
	// blacklist of field types that should not appear in the backend
	blacklistedFieldTypes: ["submit","button","reset"]
  },
  // the regular expression and the associated validation error message for the search 
  formSearch: { 
    formDataSearchRegex: ".+", 
    validationErrorMsg: "Ungültige Eingabe",
    // the search results are cached. The maximum number of results in the cache is configured here. If the cache is full and a different search request is made, the result that has not been used the longest is replaced.
    numberOfResultsCached: 14, 
    // every 15 minutes, all cache entries are deleted that were not used for the time (ms) defined here
    removeAfter: 7200000 // 2h
  },
  // configuration of image uploads
  imageUpload : {
    // list of all content types as string that should support this feature
    primaryTypes : [ ],
    // how many image uploads should be allowed per ip address in 10 seconds?
    limitPerTenSeconds : 1,
    // how many image uploads should be allowed per ip address in a minute?
    limitPerMinute : 2,
    // how many image uploads should be allowed per ip address in a day?
    limitPerDay : 100,
    // the proposal section where newly imported images are listed
    proposalSection : "User-Bilder",
    // node type of images that should be imported into sophora
    imageNodeType : "sophora-extension-nt:image"
  },
  // configuration of ratings
  rating : {
    // list of all content types as string that should support this feature
    primaryTypes : [ ],
    // how many ratings should be allowed per ip address in 10 seconds?
    limitPerTenSeconds : 1,
    // how many ratings should be allowed per ip address in a minute?
    limitPerMinute : 2,
    // how many ratings should be allowed per ip address in a day?
    limitPerDay : 100,
    // for legacy rating: Keep ratings in an internal cache for X seconds
    cachetimeInSec : 240
  },
  // configuration of quizzes
  quiz : {
    // the form field that should be treated as e-mail field
    formFieldName_Email : "email",
    // the string property of the answer text
    answerTextProperty : "sophora-ugc:text",
    // the string property of the answer uuid
    answerUuidProperty : "sophora-ugc:uuid",
    // the long property with the points for an answer
    answerPointsProperty : "sophora-ugc:points",
    // the string property with the description of an answer
    answerCommentProperty : "sophora-ugc:description",
    
    // the boolean property that determines if an answer is correct
    answerIsCorrectProperty : "sophora-ugc:isCorrect",
    // the string property with the text of a question
    questionTextProperty : "sophora-ugc:text",
    // the child node with the answers for a question
    questionAnswersChildnode : "sophora-ugc:answers",
    // string property for the headline of the quiz
    headlineProperty : "sophora-ugc:title",
    // the child node of the quiz questions
    questionsChildnode : "sophora-ugc:questions",
    // the reference property of the form
    formProperty : "sophora-ugc:form",
    // primary type of quizzes
    primaryType : "sophora-ugc-nt:quiz"
  },
  // configuration of votings
  voting : {
    // form field that should be treated as e-mail field
    formFieldName_Email : "email",
    // initial delay in minutes before the first calculation of manipulation attempts
    manipulationInitialDelayInM : 5,
    // number of minutes for periodical manipulation checks
    manipulationPeriodInM : 15,
    // multi string property with manipulation criteria
    manipulationFiltersProperty : "sophora-ugc:manipulationFilters",
    // long property with numbers of entries allowed per interval
    manipulationEntriesAllowedProperty : "sophora-ugc:entriesAllowed",
    // long property with length of the manipulation interval
    manipulationIntervalProperty : "sophora-ugc:manipulationInterval",
    // string property for the time unit of an interval
    manipulationIntervalTypeProperty : "sophora-ugc:manipulationIntervalType",
    // string property for the headline of the voting
    headlineProperty : "sophora-ugc:title",
    // reference property of the form
    formProperty : "sophora-ugc:form",
    // primary type of votings
    primaryType : "sophora-ugc-nt:voting",
    // date property with the date until which is property is valid
    votingToProperty : "sophora-ugc:validTo",
   
    // long property with the number of votes that are allowed
    numberOfVotesProperty : "sophora-ugc:numberOfVotes",
    
    // child node of the voting items
    itemsChildnode : "sophora-ugc:votingItems",
    // string property with the text of a property
    itemTextProperty : "sophora-ugc:text",
    // string property with voting item uuid
    itemUuidProperty : "sophora-ugc:uuid",
    // string property with description of the voting item
    itemDescriptionProperty : "sophora-ugc:description",
    // string property of the voting that declares the voting type
    votingTypeProperty : "sophora-ugc:votingType",
    // the default voting type; possible values: "simple", "ranking"
    defaultVotingType : "simple",
    // number of days after the voting stopped being valid to wait 
    // until the voting report gets persisted and the user submissions get deleted
    timeoutDelay : 30,
    // interval in seconds to update the internal cache for a voting
    cacheUpdateInterval : 240,
    // evict a cache entry of the internal cache after X minutes without any read access
    cacheEvictAfterMinutes : 720,
    // the total maximum of voting reports in the internal cache
    cacheMaxNumberOfCachedReports : 50
  },
  // configure the usage of serial programs
  serialPrograms : {
	// (type: bySelectValue)
    idProperty : "sophora-ugc:serialProgram",
    // property with selectvalue whose content is compared with the value configured in subHierarchydocument (type: bySelectValue)
    broadcastType : "sophora-content:broadcastType",
    // to configure a hierarchy document to be not a serial program. The select value is used (type: bySelectValue)
    subHierarchydocument : "subHierarchydocument",
    // the select value document (type: bySelectValue)
    externalId : "40b38a3c-dfc0-4e3b-abf2-2bcea73d01b6",
	// title for the dropdown menu (type: byDocument)
	titleProperty : "sophora-content:title",
    // allowed nodetypes (type: byDocument). If the list is empty all nodetypes are allowed. Otherwise, only documents of the specified types can be used.
    allowedNodetypes : []
  },
  // configuration of the load monitoring
  loadInformation : {
    // how many load information entries per host should be kept in memory?
    entriesPerHost : 10000,
    // how many days of no new load information until all load data is removed from memory
    daysToDeclareDead : 2
  },
  // configuration of the embedded jetty
  jetty : {
    // http port of the jetty
    httpPort : 9080
  },
  // configuration of some features in the 'Sophora User Feedback Backend'
  // this configuration does not affect the functions of the REST API or Swagger UI
  features : {
    // configure the actions an editor can use on comments
    actions : {
      // enables the option to delete all comments submitted by a particular user
      enableDeleteByUser : false,
      // enables the option to overwrite the username 
      enableOverwriteUsername: false,
      // enables the option to overwrite the externalUserId
      enableOverwriteExternalUserId: false,
      // enables the option to execute bulk operations on filter
      enableActionsByFilter: false,
      // enables the option to execute the delete bulk operation on filter
      enableDeleteByFilter: false
    },
    // configuration of the filters 
    filters : {
      // show/hide the filter options
      documentTypes : true,
      serialPrograms : false,
      editors : true,
      dates : true;
      status : true,
      markers: false
	  // There are currently two types of serial programs / categories to configure: 'byDocument' and 'bySelectValue'. Read the section 'Configure SerialProgram/Category Filter' for detailed information.
	  serialProgramsType: "bySelectValue",
	  // the filter name can be adjusted for the special use case of the filter.
	  serialProgramsName: "Sendungsreihe"
    },
    // Toggle the feature to write notes for individual comments 
    notes: {
        enabled: false
    },
    // Toggle the feature to pin comments 
    pinning : {
        enabled : false
    },
    // To configure the marking options, the array of objects must be populated according to the following scheme:
    // {
 	//	   key: "pr",
    //     label: "Proofreading",
    //     colorValue: "#ffff00" // color specification (in an by css accepted way) 
    // }
    markers: []
  },
  // configure if the app is the main application
  isMainApp : false,
  // the backend fpr comments and image uploads has links to the preview of the documents in sophora. You can configure the preview url here.
  previewUrl : "http://localhost:8080/sophora-demosite/system/preview.jsp?sophoraid=",
  // to get the right property of the title of document that was rated or commented, 
  // use the property that is most commonly used as title or headline property
  simpleDocumentHeadlineProperty : "sophora-content:title",
  // this property defines the time in milliseconds to wait between failed startup attempts
  // Attention: this is not a configuration parameter of the sophora client
  waitingTimeBetweenStartupAttempts : 10000,
  // the hash values of submissions are stored in the webapp 
  // a submission whose hash value is already known is sorted out as a duplicate 
  // this property specifies the maximum number of hashes stored for duplicate checking 
  maxNumberOfSubmissionsSaved : 500
}

The most important configuration is the option isMainApp. If you run more than one ugc webapp, only one should be configured as main app. The main app is responsible for cleanup in general and for the manipulation check of votings.

Configure the SerialProgram/Category Filter

There are two versions of the serialProgram filter. These filters are disabled by default because of the required configuration. This filter can be used to create a filter for categories other than serialPrograms. For this use the label of the selection filter can be adjusted with serialProgramsName property in the ugc-webapp.json.

Each of these two variants uses its own field in the table. Be sure to fill the correct field for the configured filter. More information under the points byDocument and bySelectValue.

byDocument

This version is for an implementation in which a corresponding document exists for each serialProgram or category. This filter uses the categoryExternalId field. To use the filter this field must be filled with the corresponding externalIds of the serialProgram/category documents. The documents used by this filter can be restricted by adding nodetypes to the allowedNodetypes list. If the list is empty (default), all node types are allowed.

You can adjust the title of the filter in the ugc-webapp backend via the serialProgramsName configuration property.

// example configuration
serialPrograms : { 	
	titleProperty : "sophora-content:title",
    allowedNodetypes : []
}, 
features : { 
	filters : { 
		serialPrograms : true, 	 
		serialProgramsType: "byDocument",
		serialProgramsName: "Serialprogram"
	}
}

This filter allows the selection of several values. If the filter will be used via REST interface, the externalIds should be entered individually in the filter map which is included in the page request. Accepted names for the assignment match the regex "category\d*".
Example: "category5" : "categoryExternalId"

bySelectValue

This version uses select values configured in sophora. This filter uses the serialProgram field. To use the filter this field must be filled.

// example configuration 
serialPrograms : { 	
	idProperty : "sophora-ugc:serialProgram",
 	broadcastType : "sophora-content:broadcastType",
 	subHierarchydocument : "subHierarchydocument",
 	externalId : "40b38a3c-dfc0-4e3b-abf2-2bcea73d01b6" 
}, 
features : { 	
	filters : {
 		serialPrograms : true,
 	 	serialProgramsType: "bySelectValue" 	
	}
}

Configure the size of the headline and content

The maximum size of the fields headline and content can be configured in the ugc-webapp.json. Values between 0 and 65135 are permitted. If no size is configured, the default value for headline is 255 characters and for content 1000 characters.

In addition, there is the possibility to give the editor additional space in the content field for editing comments. The maximum size for edited comments is {contentSize + editTolerance} characters. Values between 0 (default) and 400 are allowed.

//example configuration
comment: { 
    ...
    editTolerance: 400, 
    headlineSize: 255, 
    contentSize: 2000
}

Marking example configuration

In order to mark comments and then filter them, the configuration must be adjusted.
Example configuration with a marker option:

features: {
    filters: {
        markers: true
    },
    markers: [{
        key: "pr",
        label: "Proofreading",
        colorValue: "#ffff00"
    }]
},

If a used option is removed and existing comments have the key, the comments are treated as if they had no key. But it is not possible to save comments with an unconfigured key.

Configuration of allowed actions

There are three actions that are not displayed in the webapp by default due to their impact. These actions can still be executed via the rest interface.

features : {
    actions : { 
        enableDeleteByUser : false,
        enableOverwriteUsername: false, 
        enableOverwriteExternalUserId: false,
        enableActionsByFilter: false,
        enableDeleteByFilter: false
    },
}

enableDeleteByUser

Allows editors to delete all comments of all selected users.

enableOverwriteUsername/enableOverwriteExternalUserId

If these actions are unlocked, there is an input field in the edit view for each unlocked field. The username or externalUserId can then be changed via these input fields in the webapp.

Unlocked fields become mandatory fields and must not be empty.

enableActionsByFilter/enableDeleteByFilter

If enableActionsByFilter is set to true it allows the user to execute bulk operations (accept and deny) on a filtered list of comments. If enableDeleteByFilter is also set to true, also the delete option is allowed.

Commenting as an Editor

To comment as an editor, the multiuser feature must be enabled (multiUserEnabled: "true").
Furthermore a valid editorNodetype (editorNodetype) must be provided. The editorNodetype must contain at least a string property for the name (editorNameProperty) and a property for the avatar (editorAvatarProperty). The avatar childnode contains a reference to an image document. For the avatar an image variant (avatarVariant) must be provided. It can be any of the image variants.
To use the feature there must be at least one published document of the editorNodetype.
The mixin sophora-ugc-mix:ugcEditor and the example nodetype sophora-ugc-nt:ugcEditor for the editorNodetype is provided with the imports.

PropertyDescription
multiUserEnabledEnable the multiUser feature (boolean)
avatarVariantName of an valid image variant
editorNodetypePrimary type of the editorNodetype
editorNamePropertyProperty name of the name property defined in the editorNodetype
editorAvatarPropertyProperty name of the avatar property defined in the editorNodetype

Forms

There are two ways to model form fields in Sophora:

  • as childnodes using the form fields input field type or a dynamic table
  • as String property using the form wizard input field type

The ugc webapp needs to be configured to understand how you decided to model forms. The configuration parameter form.formWizardProperty decides which String property is interpreted as form wizard input field. The configuration parameter form.useFormWizard tells the webapp, if this property should be used to get the form fields. If the value of the property is blank or form.useFormWizard is set to false, the ugc webapp tries to get the form fields from the childnode configured in form.formFieldsChildnode.

Voting Items and Quiz Answers

In the configuration there are two configuration parameters that have similar importance:

  • voting.itemUuidProperty
  • quiz.answerUuidProperty

These parameters define properties that represent UUIDs for the quiz answers and the voting items. This means that a property needs to be configured for each of these childnodes. The best way to achieve this is via document changing script that assures that this property is set for each childnode.

Example Script for Voting Items

class SaveVotingItemListener implements IScriptDocumentChangeListener {
   private Logger log;
	public void documentChanging(IScriptDocumentChangeEvent event) {
 		// use the primary type of your voting here
		if (event.getStateChange().equals(DocumentChangedEvent.StateChange.NONE) &amp;&amp; event.getDocument().getPrimaryType().equals("sophora-ugc-nt:voting")) {
			INode node = event.getDocument();
 			// use the name of the cildnode of the voting items here
			for(INode answer : node.getNodesByName("sophora-ugc:votingItems")) {
				// use the uuid property of the voting item childnode here
				if(!answer.hasProperty("sophora-ugc:uuid")) {
					// and here
					answer.setString("sophora-ugc:uuid",UUID.randomUUID().toString());
				}
			}
		}
	}
	public void init(IScriptContext context) {
		log = context.getLogger();
		log.info("Logging started");
	}
	public void destroy() {
		log.info("Logging stopped");
	}
}
return new SaveVotingItemListener();

Example Script for Quiz items

class SaveQuizAnswerListener implements IScriptDocumentChangeListener {
   private Logger log;
	public void documentChanging(IScriptDocumentChangeEvent event) {
		// use the primary type of your quiz here
		if (event.getStateChange().equals(DocumentChangedEvent.StateChange.NONE) &amp;&amp; event.getDocument().getPrimaryType().equals("sophora-ugc-nt:question")) {
			INode node = event.getDocument();
			// use the name of the cildnode of the quiz answers here
			for(INode answer : node.getNodesByName("sophora-ugc:answers")) {
				// use the uuid property of the answer childnode here
				if(!answer.hasProperty("sophora-ugc:uuid")) {
					// and here
					answer.setString("sophora-ugc:uuid",UUID.randomUUID().toString());
				}
			}
		}
	}
	public void init(IScriptContext context) {
		log = context.getLogger();
		log.info("Logging started");
	}
	public void destroy() {
		log.info("Logging stopped");
	}
}
return new SaveQuizAnswerListener();

The regular expression and the associated validation error message for the search can be configured in the ugc-webapp.json.

// Configuration example: limitation on email addresses
formSearch: { 
 formDataSearchRegex: "^[\\w._%+-]+@[\\w.-]+\\.[\\w]{2,6}$",
 validationErrorMsg: "Bitte geben sie eine valide Emailadresse als Suchbegriff ein."
 },

Cleanup

When a document gets deleted the user generated content gets deleted as well.

Votings have a more sophisticated behaviour. On deletion, the voting report gets persisted before the user submissions get deleted. Additionally, you can set a date until which the voting is valid (voting.votingToProperty) and a delay in days (voting.timeoutDelay). The delay specifies the number of days to wait after the valid-to date to delete user submissions and persist the voting report.

Caching

When new user submissions change the comments to be displayed or might have changed the voting report or the rating evaluation, invalidation requests for the cache fragments associated with the user generated content get sent. In order to prevent many requests from damaging the performance of the web application, the invalidation requests get bundled. The administration webapp waits a configurable amount of time (caching.invalidationRequestDelay) before it sends an invalidation request. During this time all other invalidation requests for this document get ignored. In consequence, there is a certain delay before the updated results get visible in your webapp.

To achieve that all fragments on all deliveries get invalidated, Sophora's cache servlet is used. It is crucial that you configure the URL of the cache servlet and the delivery group (caching.cacheServerletUrl and caching.cacheServletGroup). Otherwise, this cache invalidation feature is deactivated.

Additionally, there is an internal cache for votings, which can be configured as well. This means that changes in the voting report might take a while until they get updated - even in the UI of the ugc-webapp.

Manipulation Control

Votings

Manipulation criteria can be configured per voting doucment. There are properties usually configured on a dedicated tab where you can configure the following:

  • criteria that make up a user. IP address, referrer, e-mail address and user agent are the options. The criteria are connected with AND. I.e., all checked criteria have to be equal in submissions to be considered by the same user
  • length of the considered interval for manipulation check
  • interval time unit
  • number of submissions by the same user that are allowed per interval

If one of these values is not set in the voting document, the voting is considered to be without manipulation control.

When manipulation control is active, new submissions aren't considered for the voting report right away. Periodically, the submissions are evaluated on a per interval and document basis. All submissions in an interval with new submissions are rechecked.

When the voting document is published, all voting submissions will be rechecked for being a manipulation attempt or not. This means all submissions are stored in the database and a new check considers even the submissions that were considered a manipulation attempt before.

Comments, Image Uploads, Ratings

For these three features only the IP address is considered to identify malicious attempts. You can limit the number of submissions per:

  • ten seconds
  • minute
  • day

If the number of submissions per IP address and time interval is already exceeded, a new submission for this IP address won't be saved in the database.

Ratings have an additional limit. Only one rating submission is allowed per IP address and rateable document.

Using Double-Opt-In

Double-Opt-In means that a user has to confirm his submission via e-mail. This addon supports this feature for forms, quizzes and votings. For votings usage of this feature has the effect that the submission is not evaluated before the user confirmed the e-mail. For quizzes and forms this only has the effect that the submissions are marked differently in the backend.

The double opt-in process consists of three steps:

  1. The user submits a form, quiz or voting result, including their e-mail address. This is the first opt-in.
  2. The double opt-in asks the user via e-mail to confirm his input. The e-mail contains a link with a unique ID representing the opt-in request.
  3. The user confirms the double opt-in request by clicking on the link in the e-mail. This is the second opt-in.

Each form/quiz/voting document must contain a template for the body of the e-mail. Subject and sender address are configured globally (see doubleOptIn in the configuration section above for details). Optionally, you may set a custom subject and a sender e-mail address for each form/quiz/voting document.
The body text should contain the placeholder string $opt-in-id$, which will be replaced with the actual unique ID used to identify the opt-in request. Typically this placeholder will be put at the end of a URL that denotes the link the user has to click. But the ID can also be given to the user as text, so he can input it anywhere. The submitted content may also be referenced in the body text in the form of $FIELDNAME$.
The generated link should point to a request handler that stores the successful opt-in request. Typically this is done using the JSP tag storeDoubleOptIn.

Admin UI

You can access the backend at the http port configured in the ugc-webapp.json. E.g.: http://localhost:9080.

Sophora allows you to configure browser tabs by document types. These browser tabs make it possible to have an exact view on the user generated content on a per document basis.

URLs for browser tabs
TypeViewURL
CommentsList of all commentshttp://HOSTNAME:PORT/#!/comments/${sophora:externalId}?nav=false
http://HOSTNAME:PORT/#!/comments/${sophora:id}?nav=false&idType=sophoraId
Form submissionsList of participantshttp://HOSTNAME:PORT/#!/participants/forms/${sophora:externalId}/withResetPermission?nav=false
http://HOSTNAME:PORT/#!/participants/forms/${sophora:id}/withResetPermission?nav=false&idType=sophoraId
Image uploadsList of all image uploadshttp://HOSTNAME:PORT/#!/imageUploads/${sophora:externalId}?nav=false
http://HOSTNAME:PORT/#!/imageUploads/${sophora:id}?nav=false&idType=sophoraId
QuizzesList of participantshttp://HOSTNAME:PORT/#!/participants/quizzes/${sophora:externalId}/withResetPermission?nav=false
http://HOSTNAME:PORT/#!/participants/quizzes/${sophora:id}/withResetPermission?nav=false&idType=sophoraId
VotingsList of participantshttp://HOSTNAME:PORT/#!/participants/votings/${sophora:externalId}/withResetPermission?nav=false
http://HOSTNAME:PORT/#!/participants/votings/${sophora:id}/withResetPermission?nav=false&idType=sophoraId
VotingsThe voting reporthttp://HOSTNAME:PORT/#!/votingreport/${sophora:externalId}/withResetPermission?nav=false
http://HOSTNAME:PORT/#!/votingreport/${sophora:id}/withResetPermission?nav=false&idType=sophoraId

Swagger and Swagger UI

UGC-webapp supports Swagger and Swagger UI. Depending on the http port configured in the ugc-webapp.json, e.g. 9080, you can access them under the following URLs:

  • Swagger: http://localhost:9080/rest/v2/api-docs
  • Swagger UI: http://localhost:9080/api-ui

Authentication

If you want to use some sort of authentication within the ugc-webapp backend you have to configure a webserver (e.g. Apache or Nginx) to authenticate the user and forward the request to the backend. If you use the backend in browser tabs in the deskclient you have to take care that the server adds no-cache headers. The username and password can be configured in the tab document.

In the examples below ugc_webapp is the hostname of the ugc backend.

Apache

The basic authentication with an .htpasswd file is done like the following:

<VirtualHost *:80>
    ServerName [servername for the ugc webapp]
    ProxyRequests Off
    ProxyVia On
    <Location / >
                  Options Includes ExecCGI FollowSymLinks
                  AuthType Basic
                  AuthName "Password Required"
                  AuthUserFile /htpasswd
                  Require user [required user]
    </Location>
    <IfModule mod_headers.c>
        Header set Cache-Control "private, no-cache, no-store, proxy-revalidate, no-transform"
        Header set Pragma "no-cache"
    </IfModule>
    ProxyPass / http://ugc_webapp:8080/
    ProxyPassReverse / http://ugc_webapp:8080/
</VirtualHost>

Nginx

server {
	listen 80;
	location / {
		add_header Cache-Control "private, no-cache, no-store, proxy-revalidate, no-transform";
		add_header Pragma "no-cache";
		auth_basic "Protected";
		auth_basic_user_file "/.htpasswd";
		proxy_pass http://ugc_webapp:8080;
	}
}