001: /**
002: * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the latest version of the GNU Lesser General
006: * Public License as published by the Free Software Foundation;
007: *
008: * This program is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
011: * GNU Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public License
014: * along with this program (LICENSE.txt); if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
016: */package org.jamwiki.utils;
017:
018: import java.io.File;
019: import org.apache.commons.io.FilenameUtils;
020: import org.apache.commons.lang.StringEscapeUtils;
021: import org.apache.commons.lang.StringUtils;
022: import org.jamwiki.Environment;
023: import org.jamwiki.WikiBase;
024: import org.jamwiki.model.Topic;
025: import org.jamwiki.model.WikiFile;
026: import org.jamwiki.model.WikiImage;
027:
028: /**
029: * General utility methods for handling both wiki topic links such as
030: * "Topic#Section?query=param", as well as HTML links of the form
031: * http://example.com/.
032: */
033: public class LinkUtil {
034:
035: private static final WikiLogger logger = WikiLogger
036: .getLogger(LinkUtil.class.getName());
037:
038: /**
039: *
040: */
041: private LinkUtil() {
042: }
043:
044: /**
045: * Build a query parameter. If root is empty, this method returns
046: * "?param=value". If root is not empty this method returns root +
047: * "&param=value". Note that param and value will be URL encoded,
048: * and if "query" does not start with a "?" then one will be pre-pended.
049: *
050: * @param query The existing query parameter, if one is available. If the
051: * query parameter does not start with "?" then one will be pre-pended.
052: * @param param The name of the query parameter being appended. This
053: * value will be URL encoded.
054: * @param value The value of the query parameter being appended. This
055: * value will be URL encoded.
056: * @return The full query string generated using the input parameters.
057: */
058: public static String appendQueryParam(String query, String param,
059: String value) {
060: String url = "";
061: if (!StringUtils.isBlank(query)) {
062: if (!query.startsWith("?")) {
063: query = "?" + query;
064: }
065: url = query + "&";
066: } else {
067: url = "?";
068: }
069: if (StringUtils.isBlank(param)) {
070: return query;
071: }
072: url += Utilities.encodeForURL(param) + "=";
073: if (!StringUtils.isBlank(value)) {
074: url += Utilities.encodeForURL(value);
075: }
076: return url;
077: }
078:
079: /**
080: * Utility method for building a URL link to a wiki edit page for a
081: * specified topic.
082: *
083: * @param context The servlet context for the link that is being created.
084: * @param virtualWiki The virtual wiki for the link that is being created.
085: * @param topic The name of the topic for which an edit link is being
086: * created.
087: * @param query Any existing query parameters to append to the edit link.
088: * This value may be either <code>null</code> or empty.
089: * @param section The section defined by the name parameter within the
090: * HTML page for the topic being edited. If provided then the edit link
091: * will allow editing of only the specified section.
092: * @return A url that links to the edit page for the specified topic.
093: * Note that this method returns only the URL, not a fully-formed HTML
094: * anchor tag.
095: * @throws Exception Thrown if any error occurs while builing the link URL.
096: */
097: public static String buildEditLinkUrl(String context,
098: String virtualWiki, String topic, String query, int section)
099: throws Exception {
100: query = LinkUtil.appendQueryParam(query, "topic", topic);
101: if (section > 0) {
102: query += "&section=" + section;
103: }
104: WikiLink wikiLink = new WikiLink();
105: // FIXME - hard coding
106: wikiLink.setDestination("Special:Edit");
107: wikiLink.setQuery(query);
108: return LinkUtil.buildInternalLinkUrl(context, virtualWiki,
109: wikiLink);
110: }
111:
112: /**
113: * Utility method for building an anchor tag that links to an image page
114: * and includes the HTML image tag to display the image.
115: *
116: * @param context The servlet context for the link that is being created.
117: * @param virtualWiki The virtual wiki for the link that is being created.
118: * @param topicName The name of the image for which a link is being
119: * created.
120: * @param frame Set to <code>true</code> if the image should display with
121: * a frame border.
122: * @param thumb Set to <code>true</code> if the image should display as a
123: * thumbnail.
124: * @param align Indicates how the image should horizontally align on the
125: * page. Valid values are "left", "right" and "center".
126: * @param caption An optional text caption to display for the image. If
127: * no caption is used then this value should be either empty or
128: * <code>null</code>.
129: * @param maxDimension A value in pixels indicating the maximum width or
130: * height value allowed for the image. Images will be resized so that
131: * neither the width or height exceeds this value.
132: * @param suppressLink If this value is <code>true</code> then the
133: * generated HTML will include the image tag without a link to the image
134: * topic page.
135: * @param style The CSS class to use with the img HTML tag. This value
136: * can be <code>null</code> or empty if no custom style is used.
137: * @param escapeHtml Set to <code>true</code> if the caption should be
138: * HTML escaped. This value should be <code>true</code> in any case
139: * where the caption is not guaranteed to be free from potentially
140: * malicious HTML code.
141: * @return The full HTML required to display an image enclosed within an
142: * HTML anchor tag that links to the image topic page.
143: * @throws Exception Thrown if any error occurs while builing the image
144: * HTML.
145: */
146: public static String buildImageLinkHtml(String context,
147: String virtualWiki, String topicName, boolean frame,
148: boolean thumb, String align, String caption,
149: int maxDimension, boolean suppressLink, String style,
150: boolean escapeHtml) throws Exception {
151: Topic topic = WikiBase.getDataHandler().lookupTopic(
152: virtualWiki, topicName, false, null);
153: if (topic == null) {
154: WikiLink uploadLink = LinkUtil
155: .parseWikiLink("Special:Upload");
156: return LinkUtil.buildInternalLinkHtml(context, virtualWiki,
157: uploadLink, topicName, "edit", null, true);
158: }
159: WikiFile wikiFile = WikiBase.getDataHandler().lookupWikiFile(
160: virtualWiki, topicName);
161: if (topic.getTopicType() == Topic.TYPE_FILE) {
162: // file, not an image
163: if (StringUtils.isBlank(caption)) {
164: caption = topicName
165: .substring(NamespaceHandler.NAMESPACE_IMAGE
166: .length() + 1);
167: }
168: String url = FilenameUtils.normalize(Environment
169: .getValue(Environment.PROP_FILE_DIR_RELATIVE_PATH)
170: + "/" + wikiFile.getUrl());
171: url = FilenameUtils.separatorsToUnix(url);
172: return "<a href=\"" + url + "\">"
173: + StringEscapeUtils.escapeHtml(caption) + "</a>";
174: }
175: String html = "";
176: WikiImage wikiImage = ImageUtil.initializeImage(wikiFile,
177: maxDimension);
178: if (caption == null) {
179: caption = "";
180: }
181: if (frame || thumb || !StringUtils.isBlank(align)) {
182: html += "<div class=\"";
183: if (thumb || frame) {
184: html += "imgthumb ";
185: }
186: if (align != null && align.equalsIgnoreCase("left")) {
187: html += "imgleft ";
188: } else if (align != null
189: && align.equalsIgnoreCase("center")) {
190: html += "imgcenter ";
191: } else if ((align != null && align
192: .equalsIgnoreCase("right"))
193: || thumb || frame) {
194: html += "imgright ";
195: } else {
196: // default alignment
197: html += "image ";
198: }
199: html = html.trim() + "\">";
200: }
201: if (wikiImage.getWidth() > 0) {
202: html += "<div style=\"width:" + (wikiImage.getWidth() + 2)
203: + "px\">";
204: }
205: if (!suppressLink) {
206: html += "<a class=\"wikiimg\" href=\""
207: + LinkUtil.buildInternalLinkUrl(context,
208: virtualWiki, topicName) + "\">";
209: }
210: if (StringUtils.isBlank(style)) {
211: style = "wikiimg";
212: }
213: html += "<img class=\"" + style + "\" src=\"";
214: String url = new File(Environment
215: .getValue(Environment.PROP_FILE_DIR_RELATIVE_PATH),
216: wikiImage.getUrl()).getPath();
217: url = FilenameUtils.separatorsToUnix(url);
218: html += url;
219: html += "\"";
220: html += " width=\"" + wikiImage.getWidth() + "\"";
221: html += " height=\"" + wikiImage.getHeight() + "\"";
222: html += " alt=\"" + StringEscapeUtils.escapeHtml(caption)
223: + "\"";
224: html += " />";
225: if (!suppressLink) {
226: html += "</a>";
227: }
228: if (!StringUtils.isBlank(caption)) {
229: html += "<div class=\"imgcaption\">";
230: if (escapeHtml) {
231: html += StringEscapeUtils.escapeHtml(caption);
232: } else {
233: html += caption;
234: }
235: html += "</div>";
236: }
237: if (wikiImage.getWidth() > 0) {
238: html += "</div>";
239: }
240: if (frame || thumb || !StringUtils.isBlank(align)) {
241: html += "</div>";
242: }
243: return html;
244: }
245:
246: /**
247: * Build the HTML anchor link to a topic page for a given WikLink object.
248: *
249: * @param context The servlet context for the link that is being created.
250: * @param virtualWiki The virtual wiki for the link that is being created.
251: * @param wikiLink The WikiLink object containing all relevant information
252: * about the link being generated.
253: * @param text The text to display as the link content.
254: * @param style The CSS class to use with the anchor HTML tag. This value
255: * can be <code>null</code> or empty if no custom style is used.
256: * @param target The anchor link target, or <code>null</code> or empty if
257: * no target is needed.
258: * @param escapeHtml Set to <code>true</code> if the link caption should
259: * be HTML escaped. This value should be <code>true</code> in any case
260: * where the caption is not guaranteed to be free from potentially
261: * malicious HTML code.
262: * @return An HTML anchor link that matches the given input parameters.
263: * @throws Exception Thrown if any error occurs while builing the link
264: * HTML.
265: */
266: public static String buildInternalLinkHtml(String context,
267: String virtualWiki, WikiLink wikiLink, String text,
268: String style, String target, boolean escapeHtml)
269: throws Exception {
270: String url = LinkUtil.buildInternalLinkUrl(context,
271: virtualWiki, wikiLink);
272: String topic = wikiLink.getDestination();
273: if (StringUtils.isBlank(text)) {
274: text = topic;
275: }
276: if (!StringUtils.isBlank(topic) && StringUtils.isBlank(style)) {
277: if (InterWikiHandler.isInterWiki(virtualWiki)) {
278: style = "interwiki";
279: } else if (!LinkUtil.isExistingArticle(virtualWiki, topic)) {
280: style = "edit";
281: }
282: }
283: if (!StringUtils.isBlank(style)) {
284: style = " class=\"" + style + "\"";
285: } else {
286: style = "";
287: }
288: if (!StringUtils.isBlank(target)) {
289: target = " target=\"" + target + "\"";
290: } else {
291: target = "";
292: }
293: String html = "<a title=\""
294: + StringEscapeUtils.escapeHtml(text) + "\" href=\""
295: + url + "\"" + style + target + ">";
296: if (escapeHtml) {
297: html += StringEscapeUtils.escapeHtml(text);
298: } else {
299: html += text;
300: }
301: html += "</a>";
302: return html;
303: }
304:
305: /**
306: * Build a URL to the topic page for a given topic.
307: *
308: * @param context The servlet context path. If this value is
309: * <code>null</code> then the resulting URL will NOT include context path,
310: * which breaks HTML links but is useful for servlet redirection URLs.
311: * @param virtualWiki The virtual wiki for the link that is being created.
312: * @param topic The topic name for the URL that is being generated.
313: * @throws Exception Thrown if any error occurs while builing the link
314: * URL.
315: */
316: public static String buildInternalLinkUrl(String context,
317: String virtualWiki, String topic) throws Exception {
318: if (StringUtils.isBlank(topic)) {
319: return null;
320: }
321: WikiLink wikiLink = LinkUtil.parseWikiLink(topic);
322: return LinkUtil.buildInternalLinkUrl(context, virtualWiki,
323: wikiLink);
324: }
325:
326: /**
327: * Build a URL to the topic page for a given topic.
328: *
329: * @param context The servlet context path. If this value is
330: * <code>null</code> then the resulting URL will NOT include context path,
331: * which breaks HTML links but is useful for servlet redirection URLs.
332: * @param virtualWiki The virtual wiki for the link that is being created.
333: * @param wikiLink The WikiLink object containing all relevant information
334: * about the link being generated.
335: * @throws Exception Thrown if any error occurs while builing the link
336: * URL.
337: */
338: public static String buildInternalLinkUrl(String context,
339: String virtualWiki, WikiLink wikiLink) throws Exception {
340: String topic = wikiLink.getDestination();
341: String section = wikiLink.getSection();
342: String query = wikiLink.getQuery();
343: if (StringUtils.isBlank(topic) && !StringUtils.isBlank(section)) {
344: return "#" + Utilities.encodeForURL(section);
345: }
346: if (!LinkUtil.isExistingArticle(virtualWiki, topic)) {
347: return LinkUtil.buildEditLinkUrl(context, virtualWiki,
348: topic, query, -1);
349: }
350: String url = "";
351: if (context != null) {
352: url += context;
353: }
354: // context never ends with a "/" per servlet specification
355: url += "/";
356: // get the virtual wiki, which should have been set by the parent servlet
357: url += Utilities.encodeForURL(virtualWiki);
358: url += "/";
359: url += Utilities.encodeForURL(topic);
360: if (!StringUtils.isBlank(section)) {
361: if (!section.startsWith("#")) {
362: url += "#";
363: }
364: url += Utilities.encodeForURL(section);
365: }
366: if (!StringUtils.isBlank(query)) {
367: if (!query.startsWith("?")) {
368: url += "?";
369: }
370: url += query;
371: }
372: return url;
373: }
374:
375: /**
376: * Generate the HTML for an interwiki anchor link.
377: *
378: * @param wikiLink The WikiLink object containing all relevant information
379: * about the link being generated.
380: * @return The HTML anchor tag for the interwiki link.
381: */
382: public static String interWiki(WikiLink wikiLink) {
383: // remove namespace from link destination
384: String destination = wikiLink.getDestination();
385: String namespace = wikiLink.getNamespace();
386: destination = destination.substring(wikiLink.getNamespace()
387: .length()
388: + NamespaceHandler.NAMESPACE_SEPARATOR.length());
389: String url = InterWikiHandler.formatInterWiki(namespace,
390: destination);
391: String text = (!StringUtils.isBlank(wikiLink.getText())) ? wikiLink
392: .getText()
393: : wikiLink.getDestination();
394: return "<a class=\"interwiki\" rel=\"nofollow\" title=\""
395: + text + "\" href=\"" + url + "\">" + text + "</a>";
396: }
397:
398: /**
399: * Utility method for determining if an article name corresponds to a valid
400: * wiki link. In this case an "article name" could be an existing topic, a
401: * "Special:" page, a user page, an interwiki link, etc. This method will
402: * return true if the given name corresponds to a valid special page, user
403: * page, topic, or other existing article.
404: *
405: * @param virtualWiki The virtual wiki for the topic being checked.
406: * @param articleName The name of the article that is being checked.
407: * @return <code>true</code> if there is an article that exists for the given
408: * name and virtual wiki.
409: * @throws Exception Thrown if any error occurs during lookup.
410: */
411: public static boolean isExistingArticle(String virtualWiki,
412: String articleName) throws Exception {
413: if (StringUtils.isBlank(virtualWiki)
414: || StringUtils.isBlank(articleName)) {
415: return false;
416: }
417: if (PseudoTopicHandler.isPseudoTopic(articleName)) {
418: return true;
419: }
420: if (InterWikiHandler.isInterWiki(articleName)) {
421: return true;
422: }
423: if (StringUtils.isBlank(Environment
424: .getValue(Environment.PROP_BASE_FILE_DIR))
425: || !Environment
426: .getBooleanValue(Environment.PROP_BASE_INITIALIZED)) {
427: // not initialized yet
428: return false;
429: }
430: Topic topic = WikiBase.getDataHandler().lookupTopic(
431: virtualWiki, articleName, false, null);
432: return (topic != null);
433: }
434:
435: /**
436: * Parse a topic name of the form "Topic#Section?Query", and return a WikiLink
437: * object representing the link.
438: *
439: * @param raw The raw topic link text.
440: * @return A WikiLink object that represents the link.
441: */
442: public static WikiLink parseWikiLink(String raw) {
443: // note that this functionality was previously handled with a regular
444: // expression, but the expression caused CPU usage to spike to 100%
445: // with topics such as "Urnordisch oder Nordwestgermanisch?"
446: String processed = raw.trim();
447: WikiLink wikiLink = new WikiLink();
448: if (StringUtils.isBlank(processed)) {
449: return new WikiLink();
450: }
451: // first see if the link ends with a query param - "?..."
452: int queryPos = processed.indexOf('?', 1);
453: if (queryPos != -1 && queryPos < processed.length()) {
454: String queryString = processed.substring(queryPos + 1);
455: wikiLink.setQuery(queryString);
456: processed = processed.substring(0, queryPos);
457: }
458: // now look for a section param - "#..."
459: int sectionPos = processed.indexOf('#', 1);
460: if (sectionPos != -1 && sectionPos < processed.length()) {
461: String sectionString = processed.substring(sectionPos + 1);
462: wikiLink.setSection(sectionString);
463: processed = processed.substring(0, sectionPos);
464: }
465: // since we're having so much fun, let's find a namespace (default empty).
466: String namespaceString = "";
467: int namespacePos = processed.indexOf(':', 1);
468: if (namespacePos != -1 && namespacePos < processed.length()) {
469: namespaceString = processed.substring(0, namespacePos);
470: }
471: wikiLink.setNamespace(namespaceString);
472: String topic = processed;
473: if (namespacePos > 0) {
474: topic = processed.substring(namespacePos + 1);
475: }
476: wikiLink.setArticle(Utilities.decodeFromURL(topic, true));
477: // destination is namespace + topic
478: wikiLink.setDestination(Utilities
479: .decodeFromURL(processed, true));
480: return wikiLink;
481: }
482: }
|