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.plugin;
020
021import org.apache.wiki.api.core.Context;
022import org.apache.wiki.api.core.ContextEnum;
023import org.apache.wiki.api.core.Engine;
024import org.apache.wiki.api.core.Page;
025import org.apache.wiki.api.exceptions.PluginException;
026import org.apache.wiki.api.exceptions.ProviderException;
027import org.apache.wiki.api.plugin.Plugin;
028import org.apache.wiki.auth.AuthorizationManager;
029import org.apache.wiki.auth.permissions.PermissionFactory;
030import org.apache.wiki.pages.PageManager;
031import org.apache.wiki.preferences.Preferences;
032import org.apache.wiki.render.RenderingManager;
033import org.apache.wiki.util.HttpUtil;
034import org.apache.wiki.util.TextUtil;
035
036import java.util.ArrayList;
037import java.util.List;
038import java.util.Map;
039import java.util.Objects;
040import java.util.ResourceBundle;
041
042
043/**
044 *  Inserts page contents.  Muchos thanks to Scott Hurlbert for the initial code.
045 *
046 *  <p>Parameters : </p>
047 *  <ul>
048 *  <li><b>page</b> - the name of the page to be inserted</li>
049 *  <li><b>style</b> - the style to use</li>
050 *  <li><b>maxlength</b> - the maximum length of the page to be inserted (page contents)</li>
051 *  <li><b>class</b> - the class to use</li>
052 *  <li><b>section</b> - the section of the page that has to be inserted (separated by "----"</li>
053 *  <li><b>default</b> - the text to insert if the requested page does not exist</li>
054 *  </ul>
055 *
056 *  @since 2.1.37
057 */
058public class InsertPage implements Plugin {
059
060    /** Parameter name for setting the page.  Value is <tt>{@value}</tt>. */
061    public static final String PARAM_PAGENAME  = "page";
062    /** Parameter name for setting the style.  Value is <tt>{@value}</tt>. */
063    public static final String PARAM_STYLE     = "style";
064    /** Parameter name for setting the maxlength.  Value is <tt>{@value}</tt>. */
065    public static final String PARAM_MAXLENGTH = "maxlength";
066    /** Parameter name for setting the class.  Value is <tt>{@value}</tt>. */
067    public static final String PARAM_CLASS     = "class";
068    /** Parameter name for setting the show option.  Value is <tt>{@value}</tt>. */
069    public static final String PARAM_SHOW   = "show";
070    /** Parameter name for setting the section.  Value is <tt>{@value}</tt>. */
071    public static final String PARAM_SECTION   = "section";
072    /** Parameter name for setting the default.  Value is <tt>{@value}</tt>. */
073    public static final String PARAM_DEFAULT   = "default";
074
075    private static final String DEFAULT_STYLE = "";
076
077    private static final String ONCE_COOKIE = "JSPWiki.Once.";
078
079    /** This attribute is stashed in the WikiContext to make sure that we don't have circular references. */
080    public static final String ATTR_RECURSE    = "org.apache.wiki.plugin.InsertPage.recurseCheck";
081
082    /**
083     *  {@inheritDoc}
084     */
085    @Override @SuppressWarnings("unchecked")
086    public String execute( final Context context, final Map<String, String> params ) throws PluginException {
087        final Engine engine = context.getEngine();
088
089        final StringBuilder res = new StringBuilder();
090
091        final String clazz        = TextUtil.replaceEntities(params.get( PARAM_CLASS ));
092        final String includedPage = TextUtil.replaceEntities(params.get( PARAM_PAGENAME ));
093        String style              = TextUtil.replaceEntities(params.get( PARAM_STYLE ));
094        final boolean showOnce    = "once".equals( params.get( PARAM_SHOW ) );
095        final String defaultstr   = params.get( PARAM_DEFAULT );
096        final int section         = TextUtil.parseIntParameter(params.get( PARAM_SECTION ), -1 );
097        int maxlen                = TextUtil.parseIntParameter(params.get( PARAM_MAXLENGTH ), -1 );
098
099        final ResourceBundle rb = Preferences.getBundle( context, Plugin.CORE_PLUGINS_RESOURCEBUNDLE );
100
101        if( style == null ) {
102            style = DEFAULT_STYLE;
103        }
104
105        if( maxlen == -1 ) {
106            maxlen = Integer.MAX_VALUE;
107        }
108
109        if( includedPage != null ) {
110            final Page page;
111            try {
112                final String pageName = engine.getFinalPageName( includedPage );
113                page = engine.getManager(PageManager.class).getPage(Objects.requireNonNullElse(pageName, includedPage));
114            } catch( final ProviderException e ) {
115                res.append( "<span class=\"error\">Page could not be found by the page provider.</span>" );
116                return res.toString();
117            }
118
119            if( page != null ) {
120                //  Check for recursivity
121                List<String> previousIncludes = context.getVariable( ATTR_RECURSE );
122
123                if( previousIncludes != null ) {
124                    if( previousIncludes.contains( page.getName() ) ) {
125                        return "<span class=\"error\">Error: Circular reference - you can't include a page in itself!</span>";
126                    }
127                } else {
128                    previousIncludes = new ArrayList<>();
129                }
130
131                // Check for permissions
132                final AuthorizationManager mgr = engine.getManager( AuthorizationManager.class );
133
134                if( !mgr.checkPermission( context.getWikiSession(), PermissionFactory.getPagePermission( page, "view") ) ) {
135                    res.append("<span class=\"error\">You do not have permission to view this included page.</span>");
136                    return res.toString();
137                }
138
139                // Show Once
140                // Check for page-cookie, only include page if cookie is not yet set
141                String cookieName = "";
142
143                if( showOnce ) {
144                    cookieName = ONCE_COOKIE + TextUtil.urlEncodeUTF8( page.getName() ).replaceAll( "\\+", "%20" );
145
146                    if( HttpUtil.retrieveCookieValue( context.getHttpRequest(), cookieName ) != null ) {
147                        return "";  //silent exit
148                    }
149
150                }
151
152                // move here, after premature exit points (permissions, page-cookie)
153                previousIncludes.add( page.getName() );
154                context.setVariable( ATTR_RECURSE, previousIncludes );
155
156                /**
157                 *  We want inclusion to occur within the context of
158                 *  its own page, because we need the links to be correct.
159                 */
160
161                final Context includedContext = context.clone();
162                includedContext.setPage( page );
163
164                String pageData = engine.getManager( PageManager.class ).getPureText( page );
165                String moreLink = "";
166
167                if( section != -1 ) {
168                    try {
169                        pageData = TextUtil.getSection( pageData, section );
170                    } catch( final IllegalArgumentException e ) {
171                        throw new PluginException( e.getMessage() );
172                    }
173                }
174
175                if( pageData.length() > maxlen ) {
176                    pageData = pageData.substring( 0, maxlen )+" ...";
177                    moreLink = "<p><a href=\""+context.getURL( ContextEnum.PAGE_VIEW.getRequestContext(),includedPage)+"\">"+rb.getString("insertpage.more")+"</a></p>";
178                }
179
180                res.append("<div class=\"inserted-page ");
181                if( clazz != null ) res.append( clazz );
182                if( !style.equals(DEFAULT_STYLE) ) res.append( "\" style=\"" ).append( style );
183                if( showOnce ) res.append( "\" data-once=\"" ).append( cookieName );
184                res.append("\" >");
185
186                res.append( engine.getManager( RenderingManager.class ).textToHTML( includedContext, pageData ) );
187                res.append( moreLink );
188
189                res.append("</div>");
190
191                //
192                //  Remove the name from the stack; we're now done with this.
193                //
194                previousIncludes.remove( page.getName() );
195                context.setVariable( ATTR_RECURSE, previousIncludes );
196            } else {
197                if( defaultstr != null ) {
198                    res.append( defaultstr );
199                } else {
200                    res.append( "There is no page called '" ).append( includedPage ).append( "'.  Would you like to " );
201                    res.append( "<a href=\"" ).append( context.getURL( ContextEnum.PAGE_EDIT.getRequestContext(), includedPage ) ).append( "\">create it?</a>" );
202                }
203            }
204        } else {
205            res.append( "<span class=\"error\">" );
206            res.append( "You have to define a page!" );
207            res.append( "</span>" );
208        }
209        return res.toString();
210    }
211
212}