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