    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.parser;
    020
    021import org.apache.logging.log4j.LogManager;
    022import org.apache.logging.log4j.Logger;
    023import org.apache.oro.text.regex.MatchResult;
    024import org.apache.oro.text.regex.PatternMatcher;
    025import org.apache.oro.text.regex.Perl5Matcher;
    026import org.apache.wiki.InternalWikiException;
    027import org.apache.wiki.api.core.Context;
    028import org.apache.wiki.api.core.Engine;
    029import org.apache.wiki.api.exceptions.PluginException;
    030import org.apache.wiki.api.plugin.ParserStagePlugin;
    031import org.apache.wiki.api.plugin.Plugin;
    032import org.apache.wiki.api.plugin.PluginElement;
    033import org.apache.wiki.plugin.PluginManager;
    034import org.apache.wiki.preferences.Preferences;
    035import org.apache.wiki.variables.VariableManager;
    036import org.jdom2.Text;
    037
    038import java.io.IOException;
    039import java.text.MessageFormat;
    040import java.util.HashMap;
    041import java.util.Map;
    042import java.util.NoSuchElementException;
    043import java.util.ResourceBundle;
    044
    045
    046/**
    047 * Stores the contents of a plugin in a WikiDocument DOM tree.
    048 * <p/>
    049 * If the Context.VAR_WYSIWYG_EDITOR_MODE is set to Boolean.TRUE in the context, then the plugin is rendered as WikiMarkup.
    050 * This allows an HTML editor to work without rendering the plugin each time as well.
    051 * <p/>
    052 * If Context.VAR_EXECUTE_PLUGINS is set to Boolean.FALSE, then the plugin is not executed.
    053 *
    054 * @since 2.4
    055 */
    056public class PluginContent extends Text implements PluginElement {
    057
    058    private static final String BLANK = "";
    059    private static final String CMDLINE = "_cmdline";
    060    private static final String ELEMENT_BR = "<br/>";
    061    private static final String EMITTABLE_PLUGINS = "Image|FormOpen|FormClose|FormInput|FormTextarea|FormSelect";
    062    private static final String LINEBREAK = "\n";
    063    private static final String PLUGIN_START = "[{";
    064    private static final String PLUGIN_END = "}]";
    065    private static final String SPACE = " ";
    066
    067    private static final long serialVersionUID = 1L;
    068    private static final Logger LOG = LogManager.getLogger(PluginContent.class);
    069
    070    private final String m_pluginName;
    071    private final Map< String, String > m_params;
    072
    073    /**
    074     * Creates a new DOM element with the given plugin name and a map of parameters.
    075     *
    076     * @param pluginName The FQN of a plugin.
    077     * @param parameters A Map of parameters.
    078     */
    079    public PluginContent( final String pluginName, final Map< String, String > parameters) {
    080        m_pluginName = pluginName;
    081        m_params = parameters;
    082    }
    083
    084    /**{@inheritDoc}*/
    085    @Override
    086    public String getPluginName() {
    087        return m_pluginName;
    088    }
    089
    090    /**{@inheritDoc}*/
    091    @Override
    092    public String getParameter( final String name) {
    093        return m_params.get( name );
    094    }
    095
    096    /**{@inheritDoc}*/
    097    @Override
    098    public Map< String, String > getParameters() {
    099        return m_params;
    100    }
    101
    102    /**{@inheritDoc}*/
    103    @Override
    104    public String getValue() {
    105        return getText();
    106    }
    107
    108    /**{@inheritDoc}*/
    109    @Override
    110    public String getText() {
    111        final WikiDocument doc = ( WikiDocument )getDocument();
    112        if( doc == null ) {
    113            //
    114            // This element has not yet been attached anywhere, so we simply assume there is no rendering and return the plugin name.
    115            // This is required e.g. when the paragraphify() checks whether the element is empty or not.  We can't of course know
    116            // whether the rendering would result in an empty string or not, but let us assume it does not.
    117            //
    118            return getPluginName();
    119        }
    120
    121        final Context context = doc.getContext();
    122        if( context == null ) {
    123            LOG.info( "WikiContext garbage-collected, cannot proceed" );
    124            return getPluginName();
    125        }
    126
    127        return invoke( context );
    128    }
    129
    130    /**{@inheritDoc}*/
    131    @Override
    132    public String invoke( final Context context ) {
    133        String result;
    134        final Boolean wysiwygVariable = context.getVariable( Context.VAR_WYSIWYG_EDITOR_MODE );
    135        boolean wysiwygEditorMode = false;
    136        if( wysiwygVariable != null ) {
    137            wysiwygEditorMode = wysiwygVariable;
    138        }
    139
    140        try {
    141            //
    142            //  Determine whether we should emit the actual code for this plugin or
    143            //  whether we should execute it.  For some plugins we always execute it,
    144            //  since they can be edited visually.
    145            //
    146            // FIXME: The plugin name matching should not be done here, but in a per-editor resource
    147            if( wysiwygEditorMode && !m_pluginName.matches( EMITTABLE_PLUGINS ) ) {
    148                result = PLUGIN_START + m_pluginName + SPACE;
    149
    150                // convert newlines to <br> in case the plugin has a body.
    151                final String cmdLine = m_params.get( CMDLINE ).replaceAll( LINEBREAK, ELEMENT_BR );
    152                result = result + cmdLine + PLUGIN_END;
    153            } else {
    154                final Boolean b = context.getVariable( Context.VAR_EXECUTE_PLUGINS );
    155                if (b != null && !b ) {
    156                    return BLANK;
    157                }
    158
    159                final Engine engine = context.getEngine();
    160                final Map< String, String > parsedParams = new HashMap<>();
    161
    162                //  Parse any variable instances from the string
    163                for( final Map.Entry< String, String > e : m_params.entrySet() ) {
    164                    String val = e.getValue();
    165                    val = engine.getManager( VariableManager.class).expandVariables( context, val );
    166                    parsedParams.put( e.getKey(), val );
    167                }
    168                final PluginManager pm = engine.getManager( PluginManager.class );
    169                result = pm.execute( context, m_pluginName, parsedParams );
    170            }
    171        } catch( final Exception e ) {
    172            if( wysiwygEditorMode ) {
    173                result = "";
    174            } else {
    175                // LOG.info("Failed to execute plugin",e);
    176                final ResourceBundle rb = Preferences.getBundle( context, Plugin.CORE_PLUGINS_RESOURCEBUNDLE );
    177                result = MarkupParser.makeError( MessageFormat.format( rb.getString( "plugin.error.insertionfailed" ),
    178                                                                       context.getRealPage().getWiki(),
    179                                                                       context.getRealPage().getName(),
    180                                                                       e.getMessage() ) ).getText();
    181            }
    182        }
    183
    184        return result;
    185    }
    186
    187    /**{@inheritDoc}*/
    188    @Override
    189    public void executeParse( final Context context ) throws PluginException {
    190        final PluginManager pm = context.getEngine().getManager( PluginManager.class );
    191        if( pm.pluginsEnabled() ) {
    192            final ResourceBundle rb = Preferences.getBundle( context, Plugin.CORE_PLUGINS_RESOURCEBUNDLE);
    193            final Map< String, String > params = getParameters();
    194            final Plugin plugin = pm.newWikiPlugin( getPluginName(), rb );
    195            try {
    196                if( plugin instanceof ParserStagePlugin ) {
    197                    ( ( ParserStagePlugin )plugin ).executeParser(this, context, params );
    198                }
    199            } catch( final ClassCastException e ) {
    200                throw new PluginException( MessageFormat.format( rb.getString("plugin.error.notawikiplugin"), getPluginName() ), e );
    201            }
    202        }
    203    }
    204
    205    /**
    206     * Parses a plugin invocation and returns a DOM element.
    207     *
    208     * @param context     The WikiContext
    209     * @param commandline The line to parse
    210     * @param pos         The position in the stream parsing.
    211     * @return A DOM element
    212     * @throws PluginException If plugin invocation is faulty
    213     * @since 2.10.0
    214     */
    215    public static PluginContent parsePluginLine( final Context context, final String commandline, final int pos ) throws PluginException {
    216        final PatternMatcher matcher = new Perl5Matcher();
    217
    218        try {
    219            final PluginManager pm = context.getEngine().getManager( PluginManager.class );
    220            if( matcher.contains( commandline, pm.getPluginPattern() ) ) {
    221                final MatchResult res = matcher.getMatch();
    222                final String plugin = res.group( 2 );
    223                final String args = commandline.substring( res.endOffset( 0 ),
    224                                                           commandline.length() - ( commandline.charAt( commandline.length() - 1 ) == '}' ? 1 : 0 ) );
    225                final Map< String, String > arglist = pm.parseArgs( args );
    226
    227                // set wikitext bounds of plugin as '_bounds' parameter, e.g., [345,396]
    228                if( pos != -1 ) {
    229                    final int end = pos + commandline.length() + 2;
    230                    final String bounds = pos + "|" + end;
    231                    arglist.put( PluginManager.PARAM_BOUNDS, bounds );
    232                }
    233
    234                return new PluginContent( plugin, arglist );
    235            }
    236        } catch( final ClassCastException e ) {
    237            LOG.error( "Invalid type offered in parsing plugin arguments.", e );
    238            throw new InternalWikiException( "Oops, someone offered !String!", e );
    239        } catch( final NoSuchElementException e ) {
    240            final String msg = "Missing parameter in plugin definition: " + commandline;
    241            LOG.warn( msg, e );
    242            throw new PluginException( msg );
    243        } catch( final IOException e ) {
    244            final String msg = "Zyrf.  Problems with parsing arguments: " + commandline;
    245            LOG.warn( msg, e );
    246            throw new PluginException( msg );
    247        }
    248
    249        return null;
    250    }
    251
    252}