001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.link; 018 019import org.apache.wicket.markup.ComponentTag; 020import org.apache.wicket.markup.head.IHeaderResponse; 021import org.apache.wicket.markup.head.OnEventHeaderItem; 022import org.apache.wicket.model.IModel; 023import org.apache.wicket.model.Model; 024import org.apache.wicket.protocol.http.WebApplication; 025import org.apache.wicket.request.UrlUtils; 026import org.apache.wicket.request.cycle.RequestCycle; 027import org.apache.wicket.request.flow.RedirectToUrlException; 028 029/** 030 * <p> 031 * A simple anchor link (<a href="http://url">) pointing to any URL. Usually this is used for 032 * links to destinations outside of Wicket. 033 * </p> 034 * 035 * <p> 036 * <strong>Note</strong>: in the case when the support for cookies in the browser is disabled the 037 * user's jsessionid will leak in the 'Referrer' header after clicking this link. If this is a 038 * problem for the application then better use a {@link Link} which redirects to a shared resource 039 * (see 040 * {@link WebApplication#mountResource(String, org.apache.wicket.request.resource.ResourceReference)} 041 * , e.g. "/myapp/redirecting-resource?url=...") which on its side redirects to the new URL using 042 * {@link RedirectToUrlException}. Another option is to use <code>rel="noreferrer"</code> attribute 043 * in your markup but this will work only in the modern browsers (supporting HTML5 standard). 044 * 045 * @author Juergen Donnerstag 046 */ 047public class ExternalLink extends AbstractLink 048{ 049 private static final long serialVersionUID = 1L; 050 051 private boolean contextRelative = false; 052 053 /** 054 * The popup specification. If not-null, a javascript on-click event handler will be generated 055 * that opens a new window using the popup properties. 056 */ 057 private PopupSettings popupSettings = null; 058 059 /** 060 * Constructor. 061 * 062 * @param id 063 * See Component 064 * @param href 065 * the href attribute to set 066 * @param label 067 * the label (body) 068 */ 069 public ExternalLink(final String id, final String href, final String label) 070 { 071 super(id); 072 073 setDefaultModel(href != null ? new Model<>(href) : null); 074 setBody(Model.of(label)); 075 } 076 077 /** 078 * Constructor. 079 * 080 * @param id 081 * The name of this component 082 * @param href 083 * the href attribute to set 084 */ 085 public ExternalLink(final String id, final String href) 086 { 087 this(id, Model.of(href)); 088 } 089 090 /** 091 * Constructor. 092 * 093 * @param id 094 * The name of this component 095 * @param href 096 * the href attribute to set 097 */ 098 public ExternalLink(final String id, final IModel<String> href) 099 { 100 this(id, href, null); 101 } 102 103 /** 104 * Constructor. 105 * 106 * @param id 107 * See Component 108 * @param href 109 * the href attribute to set 110 * @param label 111 * the label (body) 112 */ 113 public ExternalLink(final String id, final IModel<String> href, final IModel<?> label) 114 { 115 super(id); 116 117 setDefaultModel(wrap(href)); 118 setBody(label); 119 } 120 121 /** 122 * Gets the popup specification. If not-null, a javascript on-click event handler will be 123 * generated that opens a new window using the popup properties. 124 * 125 * @return the popup specification. 126 */ 127 public final PopupSettings getPopupSettings() 128 { 129 return popupSettings; 130 } 131 132 /** 133 * Sets the popup specification. If not-null, a javascript on-click event handler will be 134 * generated that opens a new window using the popup properties. 135 * 136 * @param popupSettings 137 * the popup specification. 138 * @return This 139 */ 140 public final ExternalLink setPopupSettings(final PopupSettings popupSettings) 141 { 142 this.popupSettings = popupSettings; 143 return this; 144 } 145 146 /** 147 * Processes the component tag. 148 * 149 * @param tag 150 * Tag to modify 151 * @see org.apache.wicket.Component#onComponentTag(org.apache.wicket.markup.ComponentTag) 152 */ 153 @Override 154 protected void onComponentTag(final ComponentTag tag) 155 { 156 super.onComponentTag(tag); 157 158 if (isEnabledInHierarchy() == false) 159 { 160 disableLink(tag); 161 } 162 else if (getDefaultModel() != null) 163 { 164 String url = renderUrl(); 165 // if the tag is an anchor proper 166 if (url != null 167 && (tag.getName().equalsIgnoreCase("a") || tag.getName().equalsIgnoreCase("link") 168 || tag.getName().equalsIgnoreCase("area"))) 169 { 170 // generate the href attribute 171 tag.put("href", url); 172 } 173 } 174 } 175 176 @Override 177 public void renderHead(IHeaderResponse response) 178 { 179 super.renderHead(response); 180 181 if (!isEnabledInHierarchy()) 182 { 183 return; 184 } 185 186 String url = renderUrl(); 187 if (url != null) 188 { 189 if (popupSettings != null) 190 { 191 popupSettings.setTarget("'" + url + "'"); 192 response.render(OnEventHeaderItem.forComponent(this, "click", 193 popupSettings.getPopupJavaScript())); 194 return; 195 } 196 197 ComponentTag tag = getMarkupTag(); 198 // finally, when the tag is not a normal link 199 if (!(tag.getName().equalsIgnoreCase("a") || tag.getName().equalsIgnoreCase("link") 200 || tag.getName().equalsIgnoreCase("area") 201 || tag.getName().equalsIgnoreCase("script") 202 || tag.getName().equalsIgnoreCase("style"))) 203 { 204 // generate an onclick JS handler directly 205 // in firefox when the element is quickly clicked 3 times a second request is 206 // generated during page load. This check ensures that the click is ignored 207 response.render(OnEventHeaderItem.forComponent(this, "click", 208 "var win = this.ownerDocument.defaultView || this.ownerDocument.parentWindow; " 209 + "if (win == window) { window.location.href='" + url 210 + "'; } ;return false")); 211 return; 212 } 213 } 214 } 215 216 /** 217 * @return the URL for this link 218 */ 219 private String renderUrl() 220 { 221 Object hrefValue = getDefaultModelObject(); 222 if (hrefValue == null) 223 { 224 return null; 225 } 226 227 String url = hrefValue.toString(); 228 if (contextRelative) 229 { 230 if (url.length() > 0 && url.charAt(0) == '/') 231 { 232 url = url.substring(1); 233 } 234 url = UrlUtils.rewriteToContextRelative(url, RequestCycle.get()); 235 } 236 return url; 237 } 238 239 /** 240 * @return True if this link is automatically prepended with ../ to make it relative to the 241 * context root. 242 */ 243 public boolean isContextRelative() 244 { 245 return contextRelative; 246 } 247 248 /** 249 * Set to true if this link should be automatically prepended with ../ to make it relative to 250 * the context root. 251 * 252 * @param contextRelative 253 * @return This for chaining 254 */ 255 public ExternalLink setContextRelative(final boolean contextRelative) 256 { 257 this.contextRelative = contextRelative; 258 return this; 259 } 260}