/*
* 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.text.StringUtils;
import com.concursive.connect.Constants;
import com.concursive.connect.web.modules.badges.dao.ProjectBadge;
import com.concursive.connect.web.modules.blog.dao.BlogPost;
import com.concursive.connect.web.modules.calendar.dao.Meeting;
import com.concursive.connect.web.modules.classifieds.dao.Classified;
import com.concursive.connect.web.modules.discussion.dao.Forum;
import com.concursive.connect.web.modules.discussion.dao.Topic;
import com.concursive.connect.web.modules.documents.dao.FileFolder;
import com.concursive.connect.web.modules.documents.dao.FileItem;
import com.concursive.connect.web.modules.lists.dao.TaskCategory;
import com.concursive.connect.web.modules.profile.beans.ProjectFormBean;
import com.concursive.connect.web.modules.profile.dao.Project;
import com.concursive.connect.web.modules.profile.utils.ProjectUtils;
import com.concursive.connect.web.modules.promotions.dao.Ad;
import com.concursive.connect.web.modules.reviews.dao.ProjectRating;
import com.concursive.connect.web.modules.wiki.dao.Wiki;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Splits a wiki link into inter-project link properties. This class was
* contributed to ConcourseConnect.
*
* @author sitsiliya
* @author matt rajkowski
* @created April 11, 2007
*/
public class WikiLink {
private static Log LOG = LogFactory.getLog(WikiLink.class);
// constants
public static final String SIMPLE = "simple";
public static final String COMPLEX = "complex";
public static final String REFERENCE = "reference";
// properties
private String project = "";
private String area = "";
private String entity = "";
private String name = "";
private String status = "";
public static final String rgex = "[|:]";
// resulting url value
private String url = "";
/**
* Method syntactic parsing of inter-project wiki links:
* [[Wiki Article]]
* [[|Wiki Home]]
* [[Wiki Article|Display Text]]
* [[|{project id}:{area}|{entity id}|{alternate name}]]
* [[|{project unique id}:{area}|{entity id}|{alternate name}]]
* [[|177:wiki|10|Some name]]
* [[|177:ticket|4736|Some Ticket]]
* Empty {project id} means the current project. Otherwise is project id field.
* {area} is project, wiki, documents, tickets, etc.
* {entity id} is an identifier, for wiki article name, tickets - ticket number, etc., can be null
* {alternate name} self-explanatory
*
* @param projectId the default project id (the project id of this wiki entry for substitution)
* @param wikiString link
*/
public WikiLink(String wikiString, int projectId) {
// Use the specified id
if (projectId != -1) {
project = String.valueOf(projectId);
}
// Parse the link
parseLink(wikiString);
}
/**
* When a WikiLink is needed by a WikiImage, it uses this method which uses
* the projectId first
*
* @param projectId
* @param wikiString
*/
public WikiLink(int projectId, String wikiString) {
// Use the specified id
project = String.valueOf(projectId);
// Parse the link
parseLink(wikiString);
}
public WikiLink(String wikiString) {
parseLink(wikiString);
}
private void parseLink(String wikiString) {
wikiString = wikiString.replaceAll("(|[\\S\\s]+)\\[", "");
int ind1 = wikiString.indexOf("]");
if (ind1 != -1) {
wikiString = wikiString.substring(0, ind1);
}
String rgex_ = "(http|https|ftp)://\\S+[\\S\\s]+";
String rgex_0 = "(mailto):\\S+@[\\S\\s]+";
String rgex_1 = "(/)\\S+[\\S\\s]+";
String rgex_2 = "[\\S\\s]+(|\\|)[\\S\\s]+";
String rgex_3 = "\\|(|[\\S\\s])+:[\\S\\s]+";
boolean match = wikiString.matches(rgex_) || wikiString.matches(rgex_0) || wikiString.matches(rgex_1);
boolean match2 = wikiString.matches(rgex_2) && !wikiString.startsWith("|");
boolean match3 = wikiString.matches(rgex_3) || wikiString.startsWith("|");
// References an external site - rgex_
if (match) {
// [[http://external]]
LOG.debug("match condition");
linkRgex_(wikiString, " ");
status = REFERENCE;
}
// Link to another wiki entry - rgex_2
if (match2 && !match && !match3) {
// [[Wiki link]]
// [[Wiki Link|Renamed]]
LOG.debug("match2 condition");
linkRgex_(wikiString, "|");
status = SIMPLE;
}
// Wiki link to any profile - rgex_3
if (match3) {
LOG.debug("match3 condition");
linkRgex_3(wikiString);
status = COMPLEX;
}
if (!status.equals(REFERENCE) && !StringUtils.hasText(area)) {
area = "wiki";
}
// Check to see if the project's unique id is being used instead of a number
if (StringUtils.hasText(project)) {
if (!StringUtils.isNumber(project)) {
project = String.valueOf(ProjectUtils.retrieveProjectIdFromUniqueId(project));
}
}
// Save the URL
if (WikiLink.REFERENCE.equals(this.getStatus())) {
url = this.getEntity();
} else {
Project thisProject = null;
if (this.getProjectId() > -1) {
thisProject = ProjectUtils.loadProject(this.getProjectId());
} else {
thisProject = new Project();
}
// Links...
if ("profile".equalsIgnoreCase(this.getArea())) {
// Links to a profile page
url = "/show/" + thisProject.getUniqueId();
} else if ("badge".equalsIgnoreCase(this.getArea())) {
// Links to a badge
url = "/badge/" + this.getEntityId();
} else if ("wiki".equalsIgnoreCase(this.getArea())) {
// Links to another wiki page
if (StringUtils.hasText(this.getEntity())) {
url = "/show/" + thisProject.getUniqueId() + "/wiki/" + this.getEntityTitle();
} else {
url = "/show/" + thisProject.getUniqueId() + "/wiki";
}
// @todo link to the edit page if the page doesn't exist and user has access to modify the page
} else {
url = "/show/" + thisProject.getUniqueId() + "/" + this.getArea().toLowerCase() + (StringUtils.hasText(this.getEntity()) ? "/" + this.getEntityId() : "");
}
}
}
/**
* Method syntactic parsing link type simple and reference
*
* @param str link
* @param st type link
*/
void linkRgex_(String str, String st) {
int ind = str.indexOf(st);
if (ind != -1) {
entity = str.substring(0, ind);
name = str.substring(ind + 1);
} else {
name = str;
entity = str;
}
}
/**
* Method syntactic parsing link type complex.
* Ex. |177:issue|4736|Some Ticket
* Ex. |:issue|4736|Some Ticket
* Ex. |:issue|4736|
* Ex. |:issue|4736
* Ex. |4075:wiki||
* Ex. |3995:calendar|
* Ex. |3995:calendar||See our calendar
* Ex. |Wiki Home
*
* @param str link
*/
void linkRgex_3(String str) {
String[] tokens = str.substring(1).split("[|]");
// [[|9999999:topic|3353|info]]
// First token
int projectIdx = tokens[0].indexOf(":");
if (projectIdx > 0) {
// use the specified id, otherwise none was specified
project = tokens[0].substring(0, projectIdx);
}
LOG.debug("Tokens: " + tokens.length);
if (tokens.length == 1) {
// Determine if an area is specified, else use wiki as the default
if (projectIdx > -1) {
// [[9999999:topic]]
area = tokens[0].substring(projectIdx + 1);
} else {
// [[|hello]]
area = "wiki";
name = tokens[0];
}
entity = "";
if (projectIdx > -1 && StringUtils.hasText(tokens[0].substring(projectIdx + 1))) {
name = tokens[0].substring(projectIdx + 1);
}
} else {
// First token
area = tokens[0].substring(projectIdx + 1);
// Second token
entity = tokens[1];
// Third token
if (tokens.length > 2 && StringUtils.hasText(tokens[2])) {
name = tokens[2];
} else {
// @note this is set by linkRgex_; think about overriding
//name = area;
}
}
}
public int getProjectId() {
try {
return Integer.parseInt(project);
}
catch (NumberFormatException ex) {
// Do nothing
}
return -1;
}
public void setProject(String project) {
this.project = project;
}
public String getArea() {
return area;
}
public void setArea(String area) {
this.area = area;
}
public String getEntity() {
return entity;
}
public String getEntityId() {
try {
return String.valueOf(Integer.parseInt(entity));
}
catch (NumberFormatException ex) {
if ("app".equals(area)) {
return entity;
}
}
return "-1";
}
public String getEntityTitle() {
if (!StringUtils.hasText(entity)) {
return "";
}
String content = StringUtils.replace(StringUtils.jsEscape(entity), "%20", "+");
if (content.contains("'")) {
// This is an invalid HTML character that is being introduced
content = StringUtils.replace(content, "'", "%27");
}
return content;
}
public void setEntity(String entity) {
this.entity = entity;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getUrl() {
return url;
}
public String getUrl(String contextPath) {
if (url != null && url.startsWith("/") && !url.startsWith(contextPath)) {
return contextPath + url;
} else {
return url;
}
}
public void setUrl(String url) {
this.url = url;
}
public String toString() {
StringBuffer sb = new StringBuffer("============ WikiLink ============\n");
sb.append("project: " + project + "\n");
sb.append("area: " + area + "\n");
sb.append("entity: " + entity + "\n");
sb.append("entityId: " + getEntityId() + "\n");
sb.append("name: " + name + "\n");
sb.append("status: " + status + "\n");
sb.append("url: " + url + "\n");
return sb.toString();
}
/**
* For checking permissions, the area must be mapped to a project permission
*
* @return
*/
public String getPermissionArea() {
if ("profile".equals(area) || "app".equals(area)) {
return "profile";
} else if ("wiki".equals(area)) {
return area;
} else if ("list".equals(area)) {
return "lists";
} else if ("blog".equals(area)) {
return "news";
} else if ("post".equals(area)) {
return "news";
} else if ("event".equals(area)) {
return "calendar";
} else if ("discussion".equals(area)) {
return "discussion-forums";
} else if ("forum".equals(area)) {
return "discussion-forums";
} else if ("topic".equals(area)) {
return "discussion-topics";
} else if ("promotion".equals(area)) {
return "ads";
} else if ("classified-ad".equals(area)) {
return "classifieds";
} else if ("file".equals(area)) {
return "documents";
} else if ("folder".equals(area)) {
return "documents";
} else if ("review".equals(area)) {
return "reviews";
} else {
return area;
}
}
public static String generateLink(Object object) {
StringBuffer link = new StringBuffer("[[|");
if (object instanceof Project) {
Project project = (Project) object;
link.append(project.getId());
link.append(":profile||");
link.append(project.getTitle());
} else if (object instanceof ProjectFormBean) {
ProjectFormBean projectBean = (ProjectFormBean) object;
link.append(projectBean.getProjectId());
link.append(":profile||");
link.append(projectBean.getProjectTitle());
} else if (object instanceof TaskCategory) {
TaskCategory taskCategory = (TaskCategory) object;
if (taskCategory.getLinkModuleId() == Constants.TASK_CATEGORY_PROJECTS) {
link.append(taskCategory.getLinkItemId());
link.append(":list|");
link.append(taskCategory.getId());
link.append("|");
link.append(taskCategory.getDescription());
}
} else if (object instanceof BlogPost) {
BlogPost blog = (BlogPost) object;
link.append(blog.getProjectId());
link.append(":post|");
link.append(blog.getId());
link.append("|");
link.append(blog.getSubject());
} else if (object instanceof Meeting) {
Meeting meeting = (Meeting) object;
link.append(meeting.getProjectId());
link.append(":event|");
link.append(meeting.getId());
link.append("|");
link.append(meeting.getTitle());
} else if (object instanceof Wiki) {
Wiki wiki = (Wiki) object;
link.append(wiki.getProjectId());
link.append(":wiki|");
link.append(wiki.getSubject());
link.append("|");
link.append(wiki.getSubject());
} else if (object instanceof Forum) {
Forum forum = (Forum) object;
link.append(forum.getProjectId());
link.append(":forum|");
link.append(forum.getId());
link.append("|");
link.append(forum.getSubject());
} else if (object instanceof Topic) {
Topic topic = (Topic) object;
link.append(topic.getProjectId());
link.append(":topic|");
link.append(topic.getId());
link.append("|");
link.append(topic.getSubject());
} else if (object instanceof Ad) {
Ad ad = (Ad) object;
link.append(ad.getProjectId());
link.append(":promotion|");
link.append(ad.getId());
link.append("|");
link.append(ad.getHeading());
} else if (object instanceof Classified) {
Classified classified = (Classified) object;
link.append(classified.getProjectId());
link.append(":classified-ad|");
link.append(classified.getId());
link.append("|");
link.append(classified.getTitle());
} else if (object instanceof FileItem) {
FileItem file = (FileItem) object;
if (file.getLinkModuleId() == Constants.PROJECTS_FILES) {
link.append(file.getLinkItemId());
link.append(":file|");
link.append(file.getId());
link.append("|");
link.append(file.getSubject());
}
} else if (object instanceof FileFolder) {
FileFolder folder = (FileFolder) object;
if (folder.getLinkModuleId() == Constants.PROJECTS_FILES) {
link.append(folder.getLinkItemId());
link.append(":folder|");
link.append(folder.getId());
link.append("|");
link.append(folder.getSubject());
}
} else if (object instanceof ProjectBadge) {
ProjectBadge badge = (ProjectBadge) object;
link.append(badge.getProjectId());
link.append(":badge|");
link.append(badge.getBadgeId());
link.append("|");
link.append(badge.getBadge().getTitle());
} else if (object instanceof ProjectRating) {
ProjectRating rating = (ProjectRating) object;
link.append(rating.getProjectId());
link.append(":review|");
link.append(rating.getId());
link.append("|");
link.append(rating.getTitle());
}
link.append("]]");
if (object instanceof Meeting) {
Meeting meeting = (Meeting) object;
if (meeting.getIsWebcast()) {
link.append(" ([[|");
link.append(meeting.getProjectId());
link.append(":webcasts|");
link.append("|");
link.append("Webcast");
link.append("]])");
}
}
return link.toString().trim();
}
}