/*
* ConcourseConnect
* Copyright 2009 Concursive Corporation
* http://www.concursive.com
*
* This file is part of ConcourseConnect, an open source social business
* software and community platform.
*
* Concursive ConcourseConnect is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, version 3 of the License.
*
* Under the terms of the GNU Affero General Public License you must release the
* complete source code for any application that uses any part of ConcourseConnect
* (system header files and libraries used by the operating system are excluded).
* These terms must be included in any work that has ConcourseConnect components.
* If you are developing and distributing open source applications under the
* GNU Affero General Public License, then you are free to use ConcourseConnect
* under the GNU Affero General Public License.
*
* If you are deploying a web site in which users interact with any portion of
* ConcourseConnect over a network, the complete source code changes must be made
* available. For example, include a link to the source archive directly from
* your web site.
*
* For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their
* products, and do not license and distribute their source code under the GNU
* Affero General Public License, Concursive provides a flexible commercial
* license.
*
* To anyone in doubt, we recommend the commercial license. Our commercial license
* is competitively priced and will eliminate any confusion about how
* ConcourseConnect can be used and distributed.
*
* ConcourseConnect is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with ConcourseConnect. If not, see <http://www.gnu.org/licenses/>.
*
* Attribution Notice: ConcourseConnect is an Original Work of software created
* by Concursive Corporation
*/
package com.concursive.connect.web.modules.wiki.utils;
import com.concursive.commons.date.DateUtils;
import com.concursive.commons.db.DatabaseUtils;
import com.concursive.commons.phone.PhoneNumberBean;
import com.concursive.commons.phone.PhoneNumberUtils;
import com.concursive.commons.text.StringUtils;
import com.concursive.connect.web.modules.login.dao.User;
import com.concursive.connect.web.modules.login.utils.UserUtils;
import com.concursive.connect.web.modules.profile.dao.Project;
import com.concursive.connect.web.modules.profile.utils.ProjectUtils;
import com.concursive.connect.web.modules.wiki.dao.*;
import com.concursive.connect.web.utils.HtmlSelect;
import com.concursive.connect.web.utils.HtmlSelectCurrencyCode;
import com.concursive.connect.web.utils.LookupElement;
import com.concursive.connect.web.utils.LookupList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.BufferedReader;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Class to manipulate wiki objects
*
* @author matt rajkowski
* @version $Id$
* @created February 7, 2006
*/
public class WikiToHTMLUtils {
private static Log LOG = LogFactory.getLog(WikiToHTMLUtils.class);
public static String CRLF = "\n";
public static String CONTENT_NEEDS_FORMATTING = "needs_formatting";
public static String CONTENT_PREFORMATTED = "preformatted";
public static String CONTENT_PRE = "pre-preformatted";
public static String CONTENT_CODE = "code-preformatted";
public static String getHTML(WikiToHTMLContext context, Connection db) {
String content = context.getWiki().getContent();
if (content == null) {
return null;
}
// Chunk the content into manageable pieces
LinkedHashMap<String, String> chunks = chunkContent(content, context.isEditMode());
StringBuffer sb = new StringBuffer();
for (String type : chunks.keySet()) {
String chunk = chunks.get(type);
LOG.trace("========= CHUNK [" + type + "] =========");
if (type.endsWith(CONTENT_PREFORMATTED)) {
if (!context.canAppend()) {
continue;
}
LOG.trace(chunk);
if (type.endsWith(CONTENT_CODE + "remove-test")) {
sb.append("<code>");
} else {
sb.append("<pre>");
}
sb.append(chunk);
if (type.endsWith(CONTENT_CODE + "remove-test")) {
sb.append("</code>");
} else {
sb.append("</pre>");
}
sb.append(CRLF);
} else if (type.endsWith(CONTENT_NEEDS_FORMATTING)) {
String formatted = getHTML(context, db, chunk);
LOG.trace(formatted);
sb.append(formatted);
}
}
return sb.toString();
// Tables (With linewraps)
// Header ||
// Cell |
// Ordered List
// *
// **
// Unordered List
// #
// ##
// Links
// [[Wiki link]]
// [[Wiki Link|Renamed]]
// [[http://external]]
// Images
// [[Image:Filename.jpg]]
// [[Image:Filename.jpg|A caption]]
// [[Image:Filename.jpg|link=wiki]]
// [[Image:Filename.jpg|thumb]]
// [[Image:Filename.jpg|right]]
// [[Image:Filename.jpg|left]]
// Videos
// [[Video:http://www.youtube.com/watch?v=3LkNlTNHZzE]]
// [[Video:http://www.ustream.tv/flash/live/1/371673]]
// [[Video:http://www.justin.tv/vasalini]]
// [[Video:http://www.livestream.com/spaceflightnow]]
// [[Video:http://qik.com/zeroio]]
// Forms
// [{form name="wikiForm"}]
// ---
// [{group value="New Group" display="false"}]
// ---
// [{label value="This is a text field" display="false"}]
// [{field type="text" name="cf10" maxlength="30" size="30" value="" required="false"}]
// [{description value="This is the text to display after"}]
// +++
}
public static LinkedHashMap<String, String> chunkContent(String content, boolean editMode) {
// Reduce the content into formatted and unformatted chunks...
LinkedHashMap<String, String> chunks = new LinkedHashMap<String, String>();
int preIndex = -1;
int codeIndex = -1;
int chunkCount = 0;
while ((preIndex = content.indexOf("<pre>")) > -1 ||
(codeIndex = content.indexOf("<code")) > -1) {
int blockStart = -1;
int startIndex = -1;
int endIndex = -1;
int blockEnd = -1;
String type = CONTENT_PREFORMATTED;
if (preIndex > -1 && codeIndex > -1) {
if (preIndex < codeIndex) {
blockStart = preIndex;
startIndex = preIndex + 5;
endIndex = content.indexOf("</pre>", startIndex);
blockEnd = endIndex + 6;
type = CONTENT_PRE;
} else {
blockStart = codeIndex;
startIndex = codeIndex + 6;
endIndex = content.indexOf("</code>", startIndex);
blockEnd = endIndex + 7;
type = CONTENT_CODE;
}
} else if (preIndex > -1) {
blockStart = preIndex;
startIndex = preIndex + 5;
endIndex = content.indexOf("</pre>", startIndex);
blockEnd = endIndex + 6;
type = CONTENT_PRE;
} else {
blockStart = codeIndex;
startIndex = codeIndex + 6;
endIndex = content.indexOf("</code>", startIndex);
blockEnd = endIndex + 7;
type = CONTENT_CODE;
}
// Store non-preformatted chunks in a map for further processing
if (blockStart > 0) {
chunks.put(++chunkCount + CONTENT_NEEDS_FORMATTING, content.substring(0, blockStart));
}
// Process pre-formatted text here...
String preText = content.substring(startIndex, endIndex);
while (preText.startsWith(CRLF)) {
preText = preText.substring(1);
}
while (preText.endsWith(CRLF)) {
preText = preText.substring(0, preText.length() - 1);
}
String text = StringUtils.toHtmlValue(preText, false, false);
text = processMarkupCharacters(text);
if (editMode) {
text = StringUtils.toBasicHtmlChars(text);
}
chunks.put(++chunkCount + type, text);
if (blockEnd < content.length()) {
content = content.substring(blockEnd);
} else {
content = "";
}
}
// Store the rest of the content
if (content.length() > 0) {
chunks.put(++chunkCount + CONTENT_NEEDS_FORMATTING, content);
}
return chunks;
}
public static String getHTML(WikiToHTMLContext context, Connection db, String content) {
return getHTML(context, db, content, false);
}
private static String getHTML(WikiToHTMLContext context, Connection db, String content, boolean inTable) {
boolean inParagraph = false;
boolean unorderedList = false;
int unorderedIndent = 0;
boolean orderedList = false;
int orderedIndent = 0;
boolean header = true;
try {
StringBuffer sb = new StringBuffer();
BufferedReader in = new BufferedReader(new StringReader(content));
String line = null;
while ((line = in.readLine()) != null) {
// Tables
if (line.startsWith("|")) {
if (orderedList) {
for (int i = 0; i < orderedIndent; i++) {
append(context, sb, "</ol>");
}
orderedList = false;
orderedIndent = 0;
append(context, sb, CRLF);
}
if (unorderedList) {
for (int i = 0; i < unorderedIndent; i++) {
append(context, sb, "</ul>");
}
unorderedList = false;
unorderedIndent = 0;
append(context, sb, CRLF);
}
if (inParagraph) {
append(context, sb, "</p>");
append(context, sb, CRLF);
inParagraph = false;
}
// parseTable operates over all the lines that make up the table,
// it will have to look forward so it returns an unparsed line
line = parseTable(context, db, in, line, sb);
if (line == null) {
continue;
}
}
// Forms
if (line.startsWith("[{form")) {
if (orderedList) {
for (int i = 0; i < orderedIndent; i++) {
append(context, sb, "</ol>");
}
orderedList = false;
orderedIndent = 0;
append(context, sb, CRLF);
}
if (unorderedList) {
for (int i = 0; i < unorderedIndent; i++) {
append(context, sb, "</ul>");
}
unorderedList = false;
unorderedIndent = 0;
append(context, sb, CRLF);
}
if (inParagraph) {
append(context, sb, "</p>");
append(context, sb, CRLF);
inParagraph = false;
}
// parseForm operates over all the lines that make up the form,
// it will have to look forward so it returns an unparsed line
parseForm(context, db, in, line, sb);
continue;
}
// Section
if (line.startsWith("=") && line.endsWith("=")) {
if (orderedList) {
for (int i = 0; i < orderedIndent; i++) {
append(context, sb, "</ol>");
}
orderedList = false;
orderedIndent = 0;
append(context, sb, CRLF);
}
if (unorderedList) {
for (int i = 0; i < unorderedIndent; i++) {
append(context, sb, "</ul>");
}
unorderedList = false;
unorderedIndent = 0;
append(context, sb, CRLF);
}
if (inParagraph) {
append(context, sb, "</p>");
append(context, sb, CRLF);
inParagraph = false;
}
int hCount = parseHCount(line, "=");
if (hCount > 6) {
hCount = 6;
}
String section = line.substring(line.indexOf("=") + hCount, line.lastIndexOf("=") - hCount + 1);
header = true;
context.foundHeader(hCount);
String headerAnchor = null;
// Store the h2's with anchors
if (hCount == 2) {
headerAnchor = StringUtils.toHtmlValue(section).replace(" ", "_");
context.getHeaderAnchors().put(headerAnchor, section);
}
append(context, sb, "<h" + hCount + (headerAnchor != null ? " id=\"" + headerAnchor + "\"" : "") + ">");
if (context.canAppend()) {
if (!context.isEditMode() && context.getShowEditSection() && !inTable) {
if (hasUserProjectAccess(db, context.getUserId(), context.getProjectId(), "wiki", "add")) {
sb.append("<span class=\"editsection\"><a href=\"" + context.getServerUrl() + "/modify/" + context.getProject().getUniqueId() + "/wiki" + (StringUtils.hasText(context.getWiki().getSubject()) ? "/" + context.getWiki().getSubjectLink() : "") + "?section=" + context.getSectionIdCount() + "\">edit</a></span>");
}
}
}
if (context.isEditMode()) {
append(context, sb, "<span>");
}
append(context, sb, StringUtils.toHtml(section));
if (context.isEditMode()) {
append(context, sb, "</span>");
}
append(context, sb, "</h" + hCount + ">");
append(context, sb, CRLF);
continue;
}
if (header) {
header = false;
if (line.trim().equals("")) {
// remove the extra space a user may leave after a header
continue;
}
}
// Determine if this is a bulleted list
if (line.startsWith("*")) {
int hCount = parseHCount(line, "*");
if (!unorderedList) {
unorderedList = true;
}
if (hCount != unorderedIndent) {
if (hCount > unorderedIndent) {
append(context, sb, "<ul>");
} else {
for (int i = hCount; i < unorderedIndent; i++) {
append(context, sb, "</ul>");
}
}
unorderedIndent = hCount;
}
append(context, sb, "<li>");
parseLine(context, db, line.substring(hCount).trim(), sb);
append(context, sb, "</li>");
append(context, sb, CRLF);
continue;
} else {
if (unorderedList) {
for (int i = 0; i < unorderedIndent; i++) {
append(context, sb, "</ul>");
}
unorderedList = false;
unorderedIndent = 0;
append(context, sb, CRLF);
}
}
// Determine if this is a numbered list
if (line.startsWith("#")) {
int hCount = parseHCount(line, "#");
if (!orderedList) {
orderedList = true;
}
if (hCount != orderedIndent) {
if (hCount > orderedIndent) {
append(context, sb, "<ol>");
} else {
for (int i = hCount; i < orderedIndent; i++) {
append(context, sb, "</ol>");
}
}
orderedIndent = hCount;
}
append(context, sb, "<li>");
parseLine(context, db, line.substring(hCount).trim(), sb);
append(context, sb, "</li>");
append(context, sb, CRLF);
continue;
} else {
if (orderedList) {
for (int i = 0; i < orderedIndent; i++) {
append(context, sb, "</ol>");
}
orderedList = false;
orderedIndent = 0;
append(context, sb, CRLF);
}
}
if (inParagraph && line.length() != 0) {
append(context, sb, "<br />");
}
// Parse the line
if (!inParagraph && line.length() > 0) {
append(context, sb, "<p>");
inParagraph = true;
}
if (line.length() == 0) {
if (inParagraph) {
append(context, sb, "</p>");
append(context, sb, CRLF);
inParagraph = false;
}
} else {
boolean hasReturn = parseLine(context, db, line, sb);
if (hasReturn) {
//append(context, sb, "<br />");
} else {
append(context, sb, CRLF);
}
}
}
// Cleanup now that the lines are finished
if (orderedList) {
for (int i = 0; i < orderedIndent; i++) {
append(context, sb, "</ol>");
}
append(context, sb, CRLF);
}
if (unorderedList) {
for (int i = 0; i < unorderedIndent; i++) {
append(context, sb, "</ul>");
}
append(context, sb, CRLF);
}
if (inParagraph) {
append(context, sb, "</p>");
append(context, sb, CRLF);
}
in.close();
return sb.toString();
} catch (Exception e) {
LOG.error("getHTML", e);
}
LOG.warn("Could not convert wiki markup");
return "Content cannot be displayed due to a parsing error, use markup mode to find the error";
}
protected static int parseHCount(String line, String id) {
int count = 0;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (id.equals(String.valueOf(c))) {
++count;
} else {
return count;
}
}
return 1;
}
protected static String parseTable(WikiToHTMLContext context, Connection db, BufferedReader in, String line, StringBuffer sb) throws Exception {
if (line == null) {
return line;
}
// Implement tables as...
// ||header col1||header col2
// !continued||
// |colA1|colA2
// !continued
// !continued|
// |colB1|colB2 [[Security, Registration, Invitation|Installation Options]]|
append(context, sb, "<table class=\"wikiTable\">");
append(context, sb, CRLF);
int rowHighlight = 0;
int rowCount = 0;
// Keep track of the table's custom styles
HashMap<Integer, String> cStyle = new HashMap<Integer, String>();
while (line != null && (line.startsWith("|") || line.startsWith("!"))) {
// Build a complete line
String lineToParse = line;
while (!line.endsWith("|")) {
line = in.readLine();
if (line == null) {
// there is an error in the line to process
return null;
}
if (line.startsWith("!")) {
lineToParse += CRLF + line.substring(1);
}
}
line = lineToParse;
// Determine if the row can output
boolean canOutput = true;
// Track the row being processed
++rowCount;
String cellType = null;
Scanner sc = null;
if (line.startsWith("||") && line.endsWith("||")) {
cellType = "th";
sc = new Scanner(line).useDelimiter("[|][|]");
// sc = new Scanner(line.substring(2, line.length() - 2)).useDelimiter("[|][|]");
} else if (line.startsWith("|")) {
cellType = "td";
sc = new Scanner(line.substring(1, line.length() - 1)).useDelimiter("\\|(?=[^\\]]*(?:\\[|$))");
}
if (sc != null) {
// Determine the column span
int colSpan = 1;
// Determine the cell being output
int cellCount = 0;
while (sc.hasNext()) {
String cellData = sc.next();
if (cellData.length() == 0) {
++colSpan;
continue;
}
// Track the cell count being output
++cellCount;
if (rowCount == 1) {
// Parse and validate the style input
LOG.debug("Checking style value: " + cellData);
if (cellData.startsWith("{") && cellData.endsWith("}")) {
String[] style = cellData.substring(1, cellData.length() - 1).split(":");
String attribute = style[0].trim();
String value = style[1].trim();
if ("width".equals(attribute)) {
// Validate the width style
if (StringUtils.hasAllowedOnly("0123456789%.", value)) {
cStyle.put(cellCount, attribute + ": " + value + ";");
}
} else {
LOG.debug("Unsupported style: " + cellData);
}
canOutput = false;
}
}
// Output the header
if (canOutput) {
if (cellCount == 1) {
// This is the table header row
if ("td".equals(cellType)) {
rowHighlight = (rowHighlight != 1 ? 1 : 2);
append(context, sb, "<tr class=\"row" + rowHighlight + "\">");
} else {
append(context, sb, "<tr>");
}
}
// Start the cell output
append(context, sb, "<" + cellType);
// Output the colspan
if (colSpan > 1) {
append(context, sb, " colspan=\"" + colSpan + "\"");
}
// Output any style
if (cStyle.size() > 0 && rowCount == 2) {
String style = cStyle.get(cellCount);
if (style != null) {
append(context, sb, " style=\"" + style + "\"");
}
}
// End the cell output
append(context, sb, ">");
// Output the data
if (" ".equals(cellData) || " ".equals(cellData)) {
// Put a blank space in blank cells for output consistency
append(context, sb, " ");
} else {
// Output the cell as a complete wiki
String formatted = getHTML(context, db, cellData, true).trim();
LOG.trace(formatted);
sb.append(formatted);
}
// Close the cell
append(context, sb, "</" + cellType + ">");
}
}
if (canOutput) {
append(context, sb, "</tr>");
append(context, sb, CRLF);
}
}
// read another line to see if it's part of the table
line = in.readLine();
}
append(context, sb, "</table>");
append(context, sb, CRLF);
return line;
}
public static CustomForm retrieveForm(BufferedReader in, String line) throws Exception {
// Forms
// [{form name="wikiForm"}]
// ---
// [{group value="New Group" display="false"}]
// ---
// [{label value="This is a text field" display="false"}]
// [{field type="text" name="cf10" maxlength="30" size="30" value="" required="false"}]
// [{description value="This is the text to display after"}]
// xxx
// Convert wiki to objects...
CustomForm form = new CustomForm();
form.setName(extractValue("name", line));
LOG.debug("Form name: " + form.getName());
CustomFormGroup currentGroup = null;
while (line != null && !line.startsWith("+++") && (line = in.readLine()) != null) {
if (line.startsWith("[{group")) {
LOG.debug("found group");
// Process the line as a group
currentGroup = new CustomFormGroup();
currentGroup.setName(extractValue("value", line));
currentGroup.setDisplay(extractValue("display", line));
form.add(currentGroup);
} else if (line.startsWith("[{label")) {
LOG.debug("found a field label");
// Process this block as a field
CustomFormField field = new CustomFormField();
field.setLabel(extractValue("value", line));
field.setLabelDisplay(extractValue("display", line));
while (!line.startsWith("---") && !line.startsWith("___") && !line.startsWith("+++") && (line = in.readLine()) != null) {
if (line.startsWith("[{field")) {
LOG.debug(" found field");
field.setType(extractValue("type", line));
field.setName(extractValue("name", line));
field.setRequired(extractValue("required", line));
field.setDefaultValue(extractValue("value", line));
field.setSize(extractValue("size", line));
field.setMaxLength(extractValue("maxlength", line));
field.setColumns(extractValue("cols", line));
field.setRows(extractValue("rows", line));
field.setOptions(extractValue("options", line));
} else if (line.startsWith("[{description")) {
LOG.debug(" found description");
field.setAdditionalText(extractValue("value", line));
} else if (line.startsWith("[{entry")) {
LOG.debug(" found entry");
field.setValue(extractValue("value", line));
field.setValueCurrency(extractValue("currency", line));
}
}
if (currentGroup == null) {
currentGroup = new CustomFormGroup();
currentGroup.setDisplay(false);
}
currentGroup.add(field);
}
}
return form;
}
protected static String parseForm(WikiToHTMLContext context, Connection db, BufferedReader in, String line, StringBuffer sb) throws Exception {
if (line == null) {
return line;
}
CustomForm form = retrieveForm(in, line);
context.foundForm(form);
if (context.canAppend()) {
// Output the form based on editmode
if (context.isEditMode()) {
LOG.debug("parseForm: editMode");
// Construct an HTML form for filling out
int groupCount = 0;
for (CustomFormGroup group : form) {
++groupCount;
if (groupCount > 1) {
sb.append("<br />");
}
sb.append("<table cellpadding=\"4\" cellspacing=\"0\" width=\"100%\" class=\"pagedList\">");
if (StringUtils.hasText(group.getName())) {
sb.append("<tr><th colspan=\"2\">").append(StringUtils.toHtml(group.getName())).append("</th></tr>");
}
for (CustomFormField field : group) {
// Determine if there is a property supplied for this field's value
if (context.getFormPropertyMap() != null) {
Map<String, String> propertyMap = context.getFormPropertyMap();
LOG.debug("Looking up propertyMap for name: " + field.getName());
if (propertyMap.containsKey(field.getName())) {
String thisValue = propertyMap.get(field.getName());
if (StringUtils.hasText(thisValue)) {
LOG.debug(" Setting value: " + thisValue);
field.setValue(thisValue);
}
}
}
sb.append("<tr class=\"containerBody\">" +
"<td valign=\"top\" class=\"formLabel\">").append(StringUtils.toHtml(field.getLabel())).append("</td>" +
"<td valign=\"top\">").append(toHtmlFormField(field, context));
if (StringUtils.hasText(field.getAdditionalText())) {
sb.append(" ").append(StringUtils.toHtml(field.getAdditionalText()));
}
sb.append("</td></tr>");
}
sb.append("</table>");
}
sb.append(CRLF);
} else {
LOG.debug("parseForm: displayMode");
// Construct HTML output for viewing the form data
LOG.debug("constructing html output...");
boolean dataOutput = false;
sb.append("<div class=\"infobox\">");
sb.append("<table class=\"pagedList\">");
for (CustomFormGroup group : form) {
LOG.debug(" group...");
if (group.getDisplay() && StringUtils.hasText(group.getName())) {
if (!dataOutput) {
dataOutput = true;
}
sb.append("<tr><th colspan=\"2\">").append(StringUtils.toHtml(group.getName())).append("</th></tr>");
}
for (CustomFormField field : group) {
LOG.debug(" field...");
if (field.hasValue()) {
if (!dataOutput) {
dataOutput = true;
}
sb.append("<tr class=\"containerBody\">");
if (field.getLabelDisplay()) {
LOG.debug(" output w/label");
sb.append("<td class=\"formLabel\">").append(StringUtils.toHtml(field.getLabel())).append("</td>");
sb.append("<td>");
sb.append(toHtml(field, context.getWiki(), context.getServerUrl()));
sb.append("</td>");
} else {
LOG.debug(" output");
sb.append("<td colspan=\"2\">");
sb.append("<center>");
sb.append(toHtml(field, context.getWiki(), context.getServerUrl()));
sb.append("</center>");
sb.append("</td>");
}
sb.append("</tr>");
}
}
}
// Show the group names to the user if there are no fields to show
if (!dataOutput) {
LOG.debug("!dataOutput");
sb.append("<tr><td colspan=\"2\" align=\"center\">");
int count = 0;
for (CustomFormGroup group : form) {
++count;
if (count > 1) {
sb.append("<br />");
}
sb.append(StringUtils.toHtml(group.getName()));
}
sb.append("</td></tr>");
}
LOG.debug("Check permissions");
if (context.getProject() != null && hasUserProjectAccess(db, context.getUserId(), context.getProject().getId(), "wiki", "add")) {
sb.append("<tr><td colspan=\"2\" align=\"center\">");
sb.append("<a href=\"" + context.getServerUrl() + "/modify/" + context.getProject().getUniqueId() + "/wiki" + (StringUtils.hasText(context.getWiki().getSubject()) ? "/" + context.getWiki().getSubjectLink() : "") + "?form=1\">Fill out this form</a>");
sb.append("</td></tr>");
}
sb.append("</table>");
sb.append("</div>");
}
}
LOG.debug("finished with form.");
context.foundFormEnd();
return null;
}
protected static boolean parseLine(WikiToHTMLContext context, Connection db, String line, StringBuffer main) throws Exception {
if (!context.canAppend()) {
return false;
}
boolean needsCRLF = true;
boolean bold = false;
boolean italic = false;
boolean bolditalic = false;
boolean underline = false;
StringBuffer subject = new StringBuffer();
StringBuffer sb = new StringBuffer();
StringBuffer data = new StringBuffer();
int linkL = 0;
int linkR = 0;
int attr = 0;
int underlineAttr = 0;
// parse characters
for (int i = 0; i < line.length(); i++) {
char c1 = line.charAt(i);
String c = String.valueOf(c1);
// False attr/links
if (!"'".equals(c) && attr == 1) {
data.append("'").append(c);
attr = 0;
continue;
}
if (!"_".equals(c) && underlineAttr == 1) {
data.append("_").append(c);
underlineAttr = 0;
continue;
}
if (!"[".equals(c) && linkL == 1) {
data.append("[").append(c);
linkL = 0;
continue;
}
if (!"]".equals(c) && linkR == 1) {
data.append("]").append(c);
linkR = 0;
continue;
}
// Links
if ("[".equals(c)) {
++linkL;
continue;
}
if ("]".equals(c)) {
++linkR;
if (linkL == 2 && linkR == 2) {
flushData(data, sb);
// Different type of links...
String link = subject.toString();
if (link.startsWith("Image:") || link.startsWith("image:")) {
// Image link
WikiImageLink wikiImageLink = new WikiImageLink(link, context.getProjectId(), context.getImageList(), (i + 1 == line.length()), context.isEditMode(), context.getServerUrl());
sb.append(wikiImageLink.getValue());
needsCRLF = wikiImageLink.getNeedsCRLF();
} else if (link.startsWith("Video:") || link.startsWith("video:")) {
// Video link
WikiVideoLink wikiVideoLink = new WikiVideoLink(link, context.isEditMode(), context.getServerUrl());
sb.append(wikiVideoLink.getValue());
needsCRLF = wikiVideoLink.getNeedsCRLF();
} else {
// Any other kind of link
// Parser for inter-project wiki links
WikiLink wikiLink = new WikiLink(link, context.getProjectId());
// Place a wiki link
String cssClass = "wikiLink";
String url = null;
if (WikiLink.REFERENCE.equals(wikiLink.getStatus())) {
sb.append("<a class=\"wikiLink external\" target=\"_blank\" href=\"" + wikiLink.getEntity() + "\">" + StringUtils.toHtml(wikiLink.getName()) + "</a>");
} else {
LOG.debug("Found wiki link: " + wikiLink.toString());
Project thisProject = null;
if (wikiLink.getProjectId() > -1) {
LOG.debug("Loading wiki link project: " + wikiLink.getProjectId());
thisProject = ProjectUtils.loadProject(wikiLink.getProjectId());
} else {
thisProject = new Project();
}
// Links...
if ("profile".equalsIgnoreCase(wikiLink.getArea())) {
// Links to a profile page
cssClass = "wikiLink external";
url = context.getServerUrl() + "/show/" + thisProject.getUniqueId();
} else if ("badge".equalsIgnoreCase(wikiLink.getArea())) {
// Links to a badge
cssClass = "wikiLink external";
url = context.getServerUrl() + "/badge/" + wikiLink.getEntityId();
} else if ("wiki".equalsIgnoreCase(wikiLink.getArea())) {
// Links to another wiki page
if (StringUtils.hasText(wikiLink.getEntity())) {
url = context.getServerUrl() + "/show/" + thisProject.getUniqueId() + "/wiki/" + wikiLink.getEntityTitle();
} else {
url = context.getServerUrl() + "/show/" + thisProject.getUniqueId() + "/wiki";
}
// Check to see if this is an external wiki
if (context.getProjectId() > -1 && context.getProjectId() != thisProject.getId()) {
cssClass = "wikiLink external";
}
// Check to see if the target wiki exists to draw the wiki entry differently
if (!WikiList.checkExistsBySubject(db, wikiLink.getEntity(), wikiLink.getProjectId())) {
cssClass = "wikiLink newWiki";
// If user has access to edit, then use an edit link
if (hasUserProjectAccess(db, context.getUserId(), wikiLink.getProjectId(), wikiLink.getArea(), "edit")) {
String wikiSubject = StringUtils.hasText(wikiLink.getEntity()) ? "/" + wikiLink.getEntityTitle() : "";
url = context.getServerUrl() + "/modify/" + thisProject.getUniqueId() + "/wiki" + wikiSubject;
}
}
} else {
cssClass = "wikiLink external";
url = context.getServerUrl() + "/show/" + thisProject.getUniqueId() + "/" + wikiLink.getArea().toLowerCase() + (StringUtils.hasText(wikiLink.getEntity()) ? "/" + wikiLink.getEntityId() : "");
}
// Display the resulting URL
if (wikiLink.getProjectId() == -1 ||
wikiLink.getProjectId() == context.getProjectId() ||
hasUserProjectAccess(db, context.getUserId(), wikiLink.getProjectId(), wikiLink.getPermissionArea(), "view")) {
String rel = "";
if ("app".equals(wikiLink.getArea())) {
// open apps in a panel
rel = " rel=\"shadowbox\"";
}
sb.append("<a class=\"" + cssClass + "\" href=\"" + url + "\"" + rel + ">" + StringUtils.toHtml(wikiLink.getName().trim()) + "</a>");
if (wikiLink.getName().endsWith(" ")) {
sb.append(" ");
}
} else {
LOG.debug("USING DENIED LINK");
cssClass = "wikiLink denied";
sb.append("<a class=\"" + cssClass + "\" href=\"#\" onmouseover=\"window.status='" + StringUtils.jsStringEscape(url) + ";'\">" + StringUtils.toHtml(wikiLink.getName()) + "</a>");
}
}
}
subject.setLength(0);
linkL = 0;
linkR = 0;
}
continue;
}
if (!"[".equals(c) && linkL == 2 && !"]".equals(c)) {
subject.append(c);
continue;
}
// Attribute properties
// TODO: Handle when there are more than 5 '''''
if ("'".equals(c)) {
++attr;
continue;
}
if ("_".equals(c)) {
++underlineAttr;
continue;
}
if (!"_".equals(c) && underlineAttr == 2) {
if (!underline) {
flushData(data, sb);
sb.append("<span style=\"text-decoration: underline;\">");
data.append(c);
underline = true;
} else {
flushData(data, sb);
sb.append("</span>");
data.append(c);
underline = false;
}
underlineAttr = 0;
continue;
}
if (!"'".equals(c) && attr > 1) {
if (attr == 2) {
if (!italic) {
flushData(data, sb);
sb.append("<em>");
data.append(c);
italic = true;
} else {
flushData(data, sb);
sb.append("</em>");
data.append(c);
italic = false;
}
attr = 0;
continue;
}
if (attr == 3) {
if (!bold) {
flushData(data, sb);
sb.append("<strong>");
data.append(c);
bold = true;
} else {
flushData(data, sb);
sb.append("</strong>");
data.append(c);
bold = false;
}
attr = 0;
continue;
}
if (attr >= 5) {
if (!bolditalic) {
flushData(data, sb);
sb.append("<strong><em>");
data.append(c);
bolditalic = true;
} else {
flushData(data, sb);
sb.append("</em></strong>");
data.append(c);
bolditalic = false;
}
attr = attr - 5;
// TODO: if attr > 0 then need to set bold/itals cout
continue;
}
}
data.append(c);
}
for (int x = 0; x < linkR; x++) {
data.append("]");
}
for (int x = 0; x < linkL; x++) {
data.append("[");
}
if (attr == 1) {
data.append("'");
}
if (underlineAttr == 1) {
data.append("_");
}
flushData(data, sb);
if (italic) {
sb.append("</em>");
}
if (underline) {
sb.append("</span>");
}
if (bold) {
sb.append("</strong>");
}
if (bolditalic) {
sb.append("</em></strong>");
}
String newLine = sb.toString();
// handle strikethrough
newLine = StringUtils.replace(newLine, StringUtils.toHtmlValue("<s>"), "<span style=\"text-decoration: line-through;\">");
newLine = StringUtils.replace(newLine, StringUtils.toHtmlValue("</s>"), "</span>");
newLine = StringUtils.replace(newLine, "\n", "<br />");
if (" ".equals(newLine)) {
newLine = " ";
}
main.append(newLine);
return needsCRLF;
}
protected static void append(WikiToHTMLContext context, StringBuffer sb, String content) {
if (context.canAppend()) {
String value = content;
value = processMarkupCharacters(value);
sb.append(value);
}
}
protected static void flushData(StringBuffer data, StringBuffer sb) {
if (data.length() > 0) {
String value = data.toString();
value = processMarkupCharacters(value);
sb.append(StringUtils.toHtmlValue(value, false, false));
data.setLength(0);
}
}
private static boolean hasUserProjectAccess(Connection db, int userId, int projectId, String section, String permissionAction) throws SQLException {
if (db == null) {
LOG.error("hasUserProjectAccess: failed - database is null");
return false;
}
// Load the user (will not be found if a guest)
User thisUser = null;
try {
thisUser = UserUtils.loadUser(userId);
} catch (Exception notAUser) {
LOG.warn("hasUserProjectAccess: userId error - " + userId);
}
// Setup the user as a guest
if (thisUser == null) {
LOG.debug("hasUserProjectAccess: userId not found - " + userId + " - using a guest user");
thisUser = UserUtils.createGuestUser();
}
// Check access
String permission = "project-" + section.toLowerCase(Locale.ENGLISH) + "-" + permissionAction;
return ProjectUtils.hasAccess(projectId, thisUser, permission);
}
public static String extractValue(String param, String line) {
int index = line.indexOf(" " + param + "=\"");
if (index == -1) {
return null;
}
int start = index + 1 + param.length() + 2;
int end = line.indexOf("\"", start);
if (end == -1) {
return null;
}
return line.substring(start, end);
}
public static String toHtmlFormField(CustomFormField field, WikiToHTMLContext context) {
// Set a default value
if (field.getValue() == null) {
field.setValue(field.getDefaultValue());
}
// Protect against any arbitrary input
String fieldName = StringUtils.toHtmlValue(field.getName());
// Return output based on type
switch (field.getType()) {
case CustomFormField.TEXTAREA:
String textAreaValue = StringUtils.replace(field.getValue(), "^", CRLF);
return ("<textarea cols=\"" + field.getColumns() + "\" rows=\"" + field.getRows() + "\" name=\"" + fieldName + "\">" + StringUtils.toString(textAreaValue) + "</textarea>");
case CustomFormField.SELECT:
LookupList lookupList = field.getLookupList();
int selectedItemId = -1;
for (LookupElement thisElement : lookupList) {
if (field.getValue().equals(thisElement.getDescription())) {
selectedItemId = thisElement.getCode();
}
}
return lookupList.getHtmlSelect(fieldName, selectedItemId);
case CustomFormField.CHECKBOX:
return ("<input type=\"checkbox\" name=\"" + fieldName + "\" value=\"ON\" " + ("true".equals(field.getValue()) ? "checked" : "") + ">");
case CustomFormField.CALENDAR:
String calendarValue = field.getValue();
if (StringUtils.hasText(calendarValue)) {
try {
String convertedDate = DateUtils.getUserToServerDateTimeString(null, DateFormat.SHORT, DateFormat.LONG, field.getValue());
Timestamp timestamp = DatabaseUtils.parseTimestamp(convertedDate);
Locale locale = Locale.getDefault();
int dateFormat = DateFormat.SHORT;
SimpleDateFormat dateFormatter = (SimpleDateFormat) SimpleDateFormat.getDateInstance(dateFormat, locale);
calendarValue = dateFormatter.format(timestamp);
} catch (Exception e) {
LOG.error("toHtmlFormField calendar", e);
}
}
// Output with a calendar control
String language = System.getProperty("LANGUAGE");
String country = System.getProperty("COUNTRY");
return ("<input type=\"text\" name=\"" + fieldName + "\" id=\"" + fieldName + "\" size=\"10\" value=\"" + StringUtils.toHtmlValue(calendarValue) + "\" > " +
"<a href=\"javascript:popCalendar('inputForm', '" + fieldName + "','" + language + "','" + country + "');\">" + "<img src=\"" + context.getServerUrl() + "/images/icons/stock_form-date-field-16.gif\" " + "border=\"0\" align=\"absmiddle\" height=\"16\" width=\"16\"/></a>");
case CustomFormField.PERCENT:
return ("<input type=\"text\" name=\"" + fieldName + "\" size=\"5\" value=\"" + StringUtils.toHtmlValue(field.getValue()) + "\"> " + "%");
case CustomFormField.INTEGER:
// Determine the value to display in the field
String integerValue = StringUtils.toHtmlValue(field.getValue());
if (StringUtils.hasText(integerValue)) {
try {
NumberFormat formatter = NumberFormat.getInstance();
integerValue = formatter.format(StringUtils.getIntegerNumber(field.getValue()));
} catch (Exception e) {
LOG.warn("Could not format integer: " + field.getValue());
}
}
return ("<input type=\"text\" name=\"" + fieldName + "\" size=\"8\" value=\"" + integerValue + "\"> ");
case CustomFormField.FLOAT:
// Determine the value to display in the field
String decimalValue = StringUtils.toHtmlValue(field.getValue());
if (StringUtils.hasText(decimalValue)) {
try {
NumberFormat formatter = NumberFormat.getInstance();
decimalValue = formatter.format(StringUtils.getDoubleNumber(field.getValue()));
} catch (Exception e) {
LOG.warn("Could not decimal format: " + field.getValue());
}
}
return ("<input type=\"text\" name=\"" + fieldName + "\" size=\"8\" value=\"" + decimalValue + "\"> ");
case CustomFormField.CURRENCY:
// Use a currencyCode for formatting
String currencyCode = field.getValueCurrency();
if (currencyCode == null) {
currencyCode = field.getCurrency();
}
if (!StringUtils.hasText(currencyCode)) {
currencyCode = "USD";
}
HtmlSelect currencyCodeList = HtmlSelectCurrencyCode.getSelect(fieldName + "Currency", currencyCode);
// Determine the valut to display in the field
String currencyValue = StringUtils.toHtmlValue(field.getValue());
if (StringUtils.hasText(currencyValue)) {
try {
NumberFormat formatter = NumberFormat.getNumberInstance();
formatter.setMaximumFractionDigits(2);
currencyValue = formatter.format(StringUtils.getDoubleNumber(field.getValue()));
} catch (Exception e) {
LOG.warn("Could not currencyCode format: " + field.getValue());
}
}
return (currencyCodeList.getHtml() + "<input type=\"text\" name=\"" + fieldName + "\" size=\"8\" value=\"" + currencyValue + "\"> ");
case CustomFormField.EMAIL:
return ("<input type=\"text\" " + "name=\"" + fieldName + "\" maxlength=\"255\" size=\"40\" value=\"" + StringUtils.toHtmlValue(field.getValue()) + "\" />");
case CustomFormField.PHONE:
return ("<input type=\"text\" " + "name=\"" + fieldName + "\" maxlength=\"60\" size=\"20\" value=\"" + StringUtils.toHtmlValue(field.getValue()) + "\" />");
case CustomFormField.URL:
String value = StringUtils.toHtmlValue(field.getValue());
if (StringUtils.hasText(value)) {
if (!value.contains("://")) {
value = "http://" + field.getValue();
}
}
return ("<input type=\"text\" " + "name=\"" + fieldName + "\" maxlength=\"255\" size=\"40\" value=\"" + StringUtils.toHtmlValue(value) + "\" />");
default:
int maxlength = field.getMaxLength();
int size = -1;
if (maxlength > -1) {
if (maxlength > 40) {
size = 40;
} else {
size = maxlength;
}
}
return ("<input type=\"text\" " + "name=\"" + fieldName + "\" " + (maxlength == -1 ? "" : "maxlength=\"" + maxlength + "\" ") + (size == -1 ? "" : "size=\"" + size + "\" ") + "value=\"" + StringUtils.toHtmlValue(field.getValue()) + "\" />");
}
}
public static String toHtml(CustomFormField field, Wiki wiki, String contextPath) {
// Return output based on type
switch (field.getType()) {
case CustomFormField.TEXTAREA:
String textAreaValue = StringUtils.replace(field.getValue(), "^", CRLF);
return StringUtils.toHtml(textAreaValue);
case CustomFormField.SELECT:
return StringUtils.toHtml(field.getValue());
case CustomFormField.CHECKBOX:
if ("true".equals(field.getValue())) {
return "Yes";
} else {
return "No";
}
case CustomFormField.CALENDAR:
String calendarValue = field.getValue();
if (StringUtils.hasText(calendarValue)) {
try {
String convertedDate = DateUtils.getUserToServerDateTimeString(null, DateFormat.SHORT, DateFormat.LONG, field.getValue());
Timestamp timestamp = DatabaseUtils.parseTimestamp(convertedDate);
Locale locale = Locale.getDefault();
int dateFormat = DateFormat.SHORT;
SimpleDateFormat dateFormatter = (SimpleDateFormat) SimpleDateFormat.getDateInstance(dateFormat, locale);
calendarValue = dateFormatter.format(timestamp);
} catch (Exception e) {
LOG.error("toHtml calendar", e);
}
}
return StringUtils.toHtml(calendarValue);
case CustomFormField.PERCENT:
return StringUtils.toHtml(field.getValue()) + "%";
case CustomFormField.INTEGER:
// Determine the value to display in the field
String integerValue = StringUtils.toHtmlValue(field.getValue());
if (StringUtils.hasText(integerValue)) {
try {
NumberFormat formatter = NumberFormat.getInstance();
integerValue = formatter.format(StringUtils.getIntegerNumber(field.getValue()));
} catch (Exception e) {
LOG.warn("Could not integer format: " + field.getValue());
}
}
return integerValue;
case CustomFormField.FLOAT:
// Determine the value to display in the field
String decimalValue = StringUtils.toHtmlValue(field.getValue());
if (StringUtils.hasText(decimalValue)) {
try {
NumberFormat formatter = NumberFormat.getInstance();
decimalValue = formatter.format(StringUtils.getDoubleNumber(field.getValue()));
} catch (Exception e) {
LOG.warn("Could not decimal format: " + field.getValue());
}
}
return decimalValue;
case CustomFormField.CURRENCY:
// Use a currency for formatting
String currencyCode = field.getValueCurrency();
if (currencyCode == null) {
currencyCode = field.getCurrency();
}
if (!StringUtils.hasText(currencyCode)) {
currencyCode = "USD";
}
try {
NumberFormat formatter = NumberFormat.getCurrencyInstance();
if (currencyCode != null) {
Currency currency = Currency.getInstance(currencyCode);
formatter.setCurrency(currency);
}
return (StringUtils.toHtml(formatter.format(StringUtils.getDoubleNumber(field.getValue()))));
} catch (Exception e) {
LOG.error("toHtml currency", e);
}
return StringUtils.toHtml(field.getValue());
case CustomFormField.EMAIL:
return StringUtils.toHtml(field.getValue());
case CustomFormField.PHONE:
PhoneNumberBean phone = new PhoneNumberBean();
phone.setNumber(field.getValue());
PhoneNumberUtils.format(phone, Locale.getDefault());
return StringUtils.toHtml(phone.toString());
case CustomFormField.URL:
String value = StringUtils.toHtmlValue(field.getValue());
if (StringUtils.hasText(value)) {
if (!value.contains("://")) {
value = "http://" + value;
}
if (value.contains("://")) {
return ("<a href=\"" + StringUtils.toHtml(value) + "\">" + StringUtils.toHtml(value) + "</a>");
}
}
return StringUtils.toHtml(value);
case CustomFormField.IMAGE:
String image = StringUtils.toHtmlValue(field.getValue());
if (StringUtils.hasText(image)) {
Project project = ProjectUtils.loadProject(wiki.getProjectId());
return ("<img src=\"" + contextPath + "/show/" + project.getUniqueId() + "/wiki-image/" + image + "\"/>");
}
return StringUtils.toHtml(image);
default:
return StringUtils.toHtml(field.getValue());
}
}
/**
* When round-tripping wiki markup, these characters are escaped for proper
* parsing.
*
* @param text
* @return
*/
public static String processMarkupCharacters(String text) {
text = StringUtils.replace(text, "\\*", "*");
text = StringUtils.replace(text, "\\#", "#");
text = StringUtils.replace(text, "\\=", "=");
text = StringUtils.replace(text, "\\|", "|");
text = StringUtils.replace(text, "\\{", "[");
text = StringUtils.replace(text, "\\}", "]");
return text;
}
}