    001/*
    002    Licensed to the Apache Software Foundation (ASF) under one
    003    or more contributor license agreements.  See the NOTICE file
    004    distributed with this work for additional information
    005    regarding copyright ownership.  The ASF licenses this file
    006    to you under the Apache License, Version 2.0 (the
    007    "License"); you may not use this file except in compliance
    008    with the License.  You may obtain a copy of the License at
    009
    010       http://www.apache.org/licenses/LICENSE-2.0
    011
    012    Unless required by applicable law or agreed to in writing,
    013    software distributed under the License is distributed on an
    014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015    KIND, either express or implied.  See the License for the
    016    specific language governing permissions and limitations
    017    under the License.
    018 */
    019package org.apache.wiki.tags;
    020
    021import org.apache.logging.log4j.LogManager;
    022import org.apache.logging.log4j.Logger;
    023import org.apache.wiki.api.core.Attachment;
    024import org.apache.wiki.api.core.ContextEnum;
    025import org.apache.wiki.api.core.Engine;
    026import org.apache.wiki.api.core.Page;
    027import org.apache.wiki.api.exceptions.ProviderException;
    028import org.apache.wiki.api.providers.WikiProvider;
    029import org.apache.wiki.attachment.AttachmentManager;
    030import org.apache.wiki.pages.PageManager;
    031import org.apache.wiki.parser.LinkParsingOperations;
    032import org.apache.wiki.parser.MarkupParser;
    033import org.apache.wiki.util.TextUtil;
    034
    035import jakarta.servlet.jsp.JspWriter;
    036import jakarta.servlet.jsp.tagext.BodyContent;
    037import jakarta.servlet.jsp.tagext.BodyTag;
    038import java.util.HashMap;
    039import java.util.Iterator;
    040import java.util.Map;
    041
    042/**
    043 *  Provides a generic link tag for all kinds of linking purposes.
    044 *  <p>
    045 *  If parameter <i>jsp</i> is defined, constructs a URL pointing to the specified JSP page, under the baseURL known by the Engine.
    046 *  Any ParamTag name-value pairs contained in the body are added to this URL to provide support for arbitrary JSP calls.
    047 *  <p>
    048 *  @since 2.3.50
    049 */
    050public class LinkTag extends WikiLinkTag implements ParamHandler, BodyTag {
    051
    052    private static final long serialVersionUID = 0L;
    053    private static final Logger LOG = LogManager.getLogger( LinkTag.class );
    054
    055    private String m_version;
    056    private String m_cssClass;
    057    private String m_style;
    058    private String m_title;
    059    private String m_target;
    060    private String m_compareToVersion;
    061    private String m_rel;
    062    private String m_jsp;
    063    private String m_ref;
    064    private String m_context = ContextEnum.PAGE_VIEW.getRequestContext();
    065    private String m_accesskey;
    066    private String m_tabindex;
    067    private String m_templatefile;
    068
    069    private Map<String, String> m_containedParams;
    070
    071    private BodyContent m_bodyContent;
    072
    073    @Override
    074    public void initTag() {
    075        super.initTag();
    076        m_version = m_cssClass = m_style = m_title = m_target = m_compareToVersion = m_rel = m_jsp = m_ref = m_accesskey = m_templatefile = null;
    077        m_context = ContextEnum.PAGE_VIEW.getRequestContext();
    078        m_containedParams = new HashMap<>();
    079    }
    080
    081    public void setTemplatefile( final String key )
    082    {
    083        m_templatefile = key;
    084    }
    085
    086    public void setAccessKey( final String key )
    087    {
    088        m_accesskey = key;
    089    }
    090
    091    public String getVersion()
    092    {
    093        return m_version;
    094    }
    095
    096    public void setVersion( final String arg )
    097    {
    098        m_version = arg;
    099    }
    100
    101    public void setCssClass( final String arg )
    102    {
    103        m_cssClass = arg;
    104    }
    105
    106    public void setStyle( final String style )
    107    {
    108        m_style = style;
    109    }
    110
    111    public void setTitle( final String title )
    112    {
    113        m_title = title;
    114    }
    115
    116    public void setTarget( final String target )
    117    {
    118        m_target = target;
    119    }
    120
    121    public void setTabindex( final String tabindex )
    122    {
    123        m_tabindex = tabindex;
    124    }
    125
    126    public void setCompareToVersion( final String ver )
    127    {
    128        m_compareToVersion = ver;
    129    }
    130
    131    public void setRel( final String rel )
    132    {
    133        m_rel = rel;
    134    }
    135
    136    public void setRef( final String ref )
    137    {
    138        m_ref = ref;
    139    }
    140
    141    public void setJsp( final String jsp )
    142    {
    143        m_jsp = jsp;
    144    }
    145
    146    public void setContext( final String context )
    147    {
    148        m_context = context;
    149    }
    150
    151    /**
    152     * Support for ParamTag supplied parameters in body.
    153     */
    154    @Override
    155    public void setContainedParameter( final String name, final String value ) {
    156        if( name != null ) {
    157            if( m_containedParams == null ) {
    158                m_containedParams = new HashMap<>();
    159            }
    160            m_containedParams.put( name, value );
    161        }
    162    }
    163
    164
    165    /**
    166     *  This method figures out what kind of an URL should be output.  It mirrors heavily on JSPWikiMarkupParser.handleHyperlinks();
    167     *
    168     * @return the URL
    169     * @throws ProviderException
    170     */
    171    private String figureOutURL() throws ProviderException {
    172        String url = null;
    173        final Engine engine = m_wikiContext.getEngine();
    174
    175        if( m_pageName == null ) {
    176            final Page page = m_wikiContext.getPage();
    177            if( page != null ) {
    178                m_pageName = page.getName();
    179            }
    180        }
    181
    182        if( m_templatefile != null ) {
    183            final String params = addParamsForRecipient( null, m_containedParams );
    184            final String template = engine.getTemplateDir();
    185            url = engine.getURL( ContextEnum.PAGE_NONE.getRequestContext(), "templates/"+template+"/"+m_templatefile, params );
    186        } else if( m_jsp != null ) {
    187            final String params = addParamsForRecipient( null, m_containedParams );
    188            //url = m_wikiContext.getURL( ContextEnum.PAGE_NONE.getRequestContext(), m_jsp, params );
    189            url = engine.getURL( ContextEnum.PAGE_NONE.getRequestContext(), m_jsp, params );
    190        } else if( m_ref != null ) {
    191            final int interwikipoint;
    192            if( new LinkParsingOperations( m_wikiContext ).isExternalLink(m_ref) ) {
    193                url = m_ref;
    194            } else if( ( interwikipoint = m_ref.indexOf( ":" ) ) != -1 ) {
    195                final String extWiki = m_ref.substring( 0, interwikipoint );
    196                final String wikiPage = m_ref.substring( interwikipoint+1 );
    197
    198                url = engine.getInterWikiURL( extWiki );
    199                if( url != null ) {
    200                    url = TextUtil.replaceString( url, "%s", wikiPage );
    201                }
    202            } else if( m_ref.startsWith("#") ) {
    203                // Local link
    204            } else if( TextUtil.isNumber(m_ref) ) {
    205                // Reference
    206            } else {
    207                final int hashMark;
    208
    209                final String parms = (m_version != null) ? "version="+getVersion() : null;
    210
    211                //  Internal wiki link, but is it an attachment link?
    212                final Page p = engine.getManager( PageManager.class ).getPage( m_pageName );
    213                if( p instanceof Attachment ) {
    214                    url = m_wikiContext.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), m_pageName );
    215                } else if( (hashMark = m_ref.indexOf('#')) != -1 ) {
    216                    // It's an internal Wiki link, but to a named section
    217
    218                    final String namedSection = m_ref.substring( hashMark+1 );
    219                    String reallink     = m_ref.substring( 0, hashMark );
    220                    reallink = MarkupParser.cleanLink( reallink );
    221
    222                    String matchedLink;
    223                    String sectref = "";
    224                    if( ( matchedLink = engine.getFinalPageName( reallink ) ) != null ) {
    225                        sectref = "section-" + engine.encodeName( matchedLink ) + "-" + namedSection;
    226                        sectref = "#" + sectref.replace( '%', '_' );
    227                    } else {
    228                        matchedLink = reallink;
    229                    }
    230
    231                    url = makeBasicURL( m_context, matchedLink, parms ) + sectref;
    232                } else {
    233                    final String reallink = MarkupParser.cleanLink( m_ref );
    234                    url = makeBasicURL( m_context, reallink, parms );
    235                }
    236            }
    237        } else if( m_pageName != null && !m_pageName.isEmpty() ) {
    238            final Page p = engine.getManager( PageManager.class ).getPage( m_pageName );
    239
    240            String parms = (m_version != null) ? "version="+getVersion() : null;
    241
    242            parms = addParamsForRecipient( parms, m_containedParams );
    243
    244            if( p instanceof Attachment ) {
    245                String ctx = m_context;
    246                // Switch context appropriately when attempting to view an
    247                // attachment, but don't override the context setting otherwise
    248                if( m_context == null || m_context.equals( ContextEnum.PAGE_VIEW.getRequestContext() ) ) {
    249                    ctx = ContextEnum.PAGE_ATTACH.getRequestContext();
    250                }
    251                url = engine.getURL( ctx, m_pageName, parms );
    252                //url = m_wikiContext.getURL( ctx, m_pageName, parms );
    253            } else {
    254                url = makeBasicURL( m_context, m_pageName, parms );
    255            }
    256        } else {
    257            final String page = engine.getFrontPage();
    258            url = makeBasicURL( m_context, page, null );
    259        }
    260
    261        return url;
    262    }
    263
    264    private String addParamsForRecipient( final String addTo, final Map< String, String > params ) {
    265        if( params == null || params.isEmpty()) {
    266            return addTo;
    267        }
    268        final StringBuilder buf = new StringBuilder();
    269        final Iterator< Map.Entry< String, String > > it = params.entrySet().iterator();
    270        while( it.hasNext() ) {
    271            final Map.Entry< String, String > e = it.next();
    272            final String n = e.getKey();
    273            final String v = e.getValue();
    274            buf.append( n );
    275            buf.append( "=" );
    276            buf.append( v );
    277            if( it.hasNext() ) {
    278                buf.append( "&amp;" );
    279            }
    280        }
    281        if( addTo == null ) {
    282            return buf.toString();
    283        }
    284        if( !addTo.endsWith( "&amp;" ) ) {
    285            return addTo + "&amp;" + buf;
    286        }
    287        return addTo + buf;
    288    }
    289
    290    private String makeBasicURL( final String context, final String page, String parms ) {
    291        final Engine engine = m_wikiContext.getEngine();
    292
    293        if( context.equals( ContextEnum.PAGE_DIFF.getRequestContext() ) ) {
    294            int r1;
    295            int r2;
    296
    297            if( DiffLinkTag.VER_LATEST.equals( getVersion() ) ) {
    298                final Page latest = engine.getManager( PageManager.class ).getPage( page, WikiProvider.LATEST_VERSION );
    299
    300                r1 = latest.getVersion();
    301            } else if( DiffLinkTag.VER_PREVIOUS.equals(getVersion()) ) {
    302                r1 = m_wikiContext.getPage().getVersion() - 1;
    303                r1 = Math.max( r1, 1 );
    304            } else if( DiffLinkTag.VER_CURRENT.equals(getVersion()) ) {
    305                r1 = m_wikiContext.getPage().getVersion();
    306            } else {
    307                r1 = Integer.parseInt( getVersion() );
    308            }
    309
    310            if( DiffLinkTag.VER_LATEST.equals(m_compareToVersion) ) {
    311                final Page latest = engine.getManager( PageManager.class ).getPage( page, WikiProvider.LATEST_VERSION );
    312
    313                r2 = latest.getVersion();
    314            } else if( DiffLinkTag.VER_PREVIOUS.equals(m_compareToVersion) ) {
    315                r2 = m_wikiContext.getPage().getVersion() - 1;
    316                r2 = Math.max( r2, 1 );
    317            } else if( DiffLinkTag.VER_CURRENT.equals(m_compareToVersion) ) {
    318                r2 = m_wikiContext.getPage().getVersion();
    319            } else {
    320                r2 = Integer.parseInt( m_compareToVersion );
    321            }
    322
    323            parms = "r1="+r1+"&amp;r2="+r2;
    324        }
    325
    326        return engine.getURL( m_context, m_pageName, parms );
    327    }
    328
    329    @Override
    330    public int doWikiStartTag() throws Exception {
    331        return EVAL_BODY_BUFFERED;
    332    }
    333
    334    @Override
    335    public int doEndTag() {
    336        try {
    337            final Engine engine = m_wikiContext.getEngine();
    338            final JspWriter out = pageContext.getOut();
    339            final String url = figureOutURL();
    340
    341            final StringBuilder sb = new StringBuilder( 20 );
    342
    343            sb.append( (m_cssClass != null)   ? "class=\""+m_cssClass+"\" " : "" );
    344            sb.append( (m_style != null)   ? "style=\""+m_style+"\" " : "" );
    345            sb.append( (m_target != null ) ? "target=\""+m_target+"\" " : "" );
    346            sb.append( (m_title != null )  ? "title=\""+m_title+"\" " : "" );
    347            sb.append( (m_rel != null )    ? "rel=\""+m_rel+"\" " : "" );
    348            sb.append( (m_accesskey != null) ? "accesskey=\""+m_accesskey+"\" " : "" );
    349            sb.append( (m_tabindex != null) ? "tabindex=\""+m_tabindex+"\" " : "" );
    350
    351            if( engine.getManager( PageManager.class ).getPage( m_pageName ) instanceof Attachment ) {
    352                sb.append( engine.getManager( AttachmentManager.class ).forceDownload( m_pageName ) ? "download " : "" );
    353            }
    354
    355            switch( m_format ) {
    356              case URL:
    357                out.print( url );
    358                break;
    359              default:
    360              case ANCHOR:
    361                out.print("<a "+ sb +" href=\""+url+"\">");
    362                break;
    363            }
    364
    365            // Add any explicit body content. This is not the intended use of LinkTag, but happens to be the way it has worked previously.
    366            if( m_bodyContent != null ) {
    367                final String linktext = m_bodyContent.getString().trim();
    368                out.write( linktext );
    369            }
    370
    371            //  Finish off by closing opened anchor
    372            if( m_format == ANCHOR ) out.print("</a>");
    373        } catch( final Exception e ) {
    374            // Yes, we want to catch all exceptions here, including RuntimeExceptions
    375            LOG.error( "Tag failed", e );
    376        }
    377
    378        return EVAL_PAGE;
    379    }
    380
    381    @Override
    382    public void setBodyContent( final BodyContent bc )
    383    {
    384        m_bodyContent = bc;
    385    }
    386
    387    @Override
    388    public void doInitBody() {
    389    }
    390
    391}