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