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 (&lt;a href="http://url"&gt;) 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}