@Override public List<Block> execute(Object parameters, String content, MacroTransformationContext context) throws MacroExecutionException { XDOM parsedDom; // get a parser for the desired syntax identifier Parser parser = getSyntaxParser(context.getSyntax().toIdString()); try { // parse the content of the wiki macro that has been injected by the component manager the // content of the macro call itself is ignored. parsedDom = parser.parse(new StringReader(content)); } catch (ParseException e) { throw new MacroExecutionException( "Failed to parse content [" + content + "] with Syntax parser [" + parser.getSyntax() + "]", e); } List<MacroBlock> potentialCells = parsedDom.getChildrenByType(MacroBlock.class, false); int count = this.countCells(potentialCells); if (count == 0) { throw new MacroExecutionException( "TableRow macro expect at least one cell macro as first-level children"); } // Make the actual cells, injecting <td> tags around cell macros this.makeCells(potentialCells); return Collections.singletonList((Block) parsedDom); }
@Test(expected = MacroExecutionException.class) public void testRestrictedByContext() throws Exception { VelocityMacroParameters params = new VelocityMacroParameters(); MacroTransformationContext context = new MacroTransformationContext(); context.setSyntax(Syntax.XWIKI_2_0); context.setCurrentMacroBlock( new MacroBlock("velocity", Collections.<String, String>emptyMap(), false)); context.setId("page1"); // Restrict the transformation context. context.getTransformationContext().setRestricted(true); when(authorizationManager.hasAccess(Right.SCRIPT)).thenReturn(true); mocker .getComponentUnderTest() .execute(params, "#macro(testMacrosAreLocal)mymacro#end", context); }
/** {@inheritDoc} */ public List<Block> execute( SectionMacroParameters parameters, String content, MacroTransformationContext context) throws MacroExecutionException { XDOM parsedDom; // get a parser for the desired syntax identifier Parser parser = getSyntaxParser(context.getSyntax().toIdString()); try { // parse the content of the wiki macro that has been injected by the // component manager // the content of the macro call itself is ignored. parsedDom = parser.parse(new StringReader(content)); } catch (ParseException e) { throw new MacroExecutionException( "Failed to parse content [" + content + "] with Syntax parser [" + parser.getSyntax() + "]", e); } List<MacroBlock> potentialColumns = parsedDom.getChildrenByType(MacroBlock.class, false); int count = this.countColumns(potentialColumns); if (count == 0) { throw new MacroExecutionException( "Section macro expect at least one column macro as first-level children"); } double computedColumnWidth = ((TOTAL_WIDTH - COLUMN_RIGHT_PADDING_RATE * (count - 1)) / count); // Make the actual columns injecting divs around column macros this.makeColumns(potentialColumns, computedColumnWidth); // finally, clear the floats introduced by the columns Map<String, String> clearFloatsParams = new HashMap<String, String>(); clearFloatsParams.put(PARAMETER_STYLE, STYLE_CLEAR_BOTH); parsedDom.addChild(new GroupBlock(clearFloatsParams)); Map<String, String> sectionParameters = new HashMap<String, String>(); if (parameters.isJustify()) { sectionParameters.put(PARAMETER_STYLE, STYLE_TEXT_ALIGN_JUSTIFY); } Block sectionRoot = new GroupBlock(sectionParameters); sectionRoot.addChildren(parsedDom.getChildren()); return Collections.singletonList(sectionRoot); }
/** * Method to overwrite to indicate the script engine name. * * @param parameters the macro parameters. * @param context the context of the macro transformation. * @return the name of the script engine to use. */ protected String getScriptEngineName(P parameters, MacroTransformationContext context) { return context.getCurrentMacroBlock().getId().toLowerCase(); }
@Override public void transform(Block rootBlock, TransformationContext context) throws TransformationException { // Create a macro execution context with all the information required for macros. MacroTransformationContext macroContext = new MacroTransformationContext(context); macroContext.setTransformation(this); // Counter to prevent infinite recursion if a macro generates the same macro for example. for (int recursions = 0; recursions < this.maxRecursions; ) { // 1) Get highest priority macro PriorityMacroBlockMatcher priorityMacroBlockMatcher = new PriorityMacroBlockMatcher(context.getSyntax()); rootBlock.getFirstBlock(priorityMacroBlockMatcher, Block.Axes.DESCENDANT); // 2) Apply macros lookup errors if (priorityMacroBlockMatcher.errors != null) { for (MacroLookupExceptionElement error : priorityMacroBlockMatcher.errors) { if (error.exception instanceof MacroNotFoundException) { // Macro cannot be found. Generate an error message instead of the macro execution // result. // TODO: make it internationalized this.macroErrorManager.generateError( error.macroBlock, String.format("Unknown macro: %s.", error.macroBlock.getId()), String.format( "The \"%s\" macro is not in the list of registered macros. Verify the spelling or " + "contact your administrator.", error.macroBlock.getId())); this.logger.debug( "Failed to locate the [{}] macro. Ignoring it.", error.macroBlock.getId()); } else { // TODO: make it internationalized this.macroErrorManager.generateError( error.macroBlock, String.format("Invalid macro: %s.", error.macroBlock.getId()), error.exception); this.logger.debug( "Failed to instantiate the [{}] macro. Ignoring it.", error.macroBlock.getId()); } } } MacroBlock macroBlock = priorityMacroBlockMatcher.block; if (macroBlock == null) { // Nothing left to do return; } Macro<?> macro = priorityMacroBlockMatcher.blockMacro; boolean incrementRecursions = macroBlock.getParent() instanceof MacroMarkerBlock; List<Block> newBlocks; try { // 3) Verify if we're in macro inline mode and if the macro supports it. If not, send an // error. if (macroBlock.isInline()) { macroContext.setInline(true); if (!macro.supportsInlineMode()) { // The macro doesn't support inline mode, raise a warning but continue. // The macro will not be executed and we generate an error message instead of the macro // execution result. this.macroErrorManager.generateError( macroBlock, "This is a standalone macro only and it cannot be used inline", "This macro generates standalone content. As a consequence you need to make sure to use a " + "syntax that separates your macro from the content before and after it so that it's on a " + "line by itself. For example in XWiki Syntax 2.0+ this means having 2 newline characters " + "(a.k.a line breaks) separating your macro from the content before and after it."); this.logger.debug("The [{}] macro doesn't support inline mode.", macroBlock.getId()); continue; } } else { macroContext.setInline(false); } // 4) Execute the highest priority macro macroContext.setCurrentMacroBlock(macroBlock); ((MutableRenderingContext) this.renderingContext).setCurrentBlock(macroBlock); // Populate and validate macro parameters. Object macroParameters = macro.getDescriptor().getParametersBeanClass().newInstance(); try { this.beanManager.populate(macroParameters, macroBlock.getParameters()); } catch (Throwable e) { // One macro parameter was invalid. // The macro will not be executed and we generate an error message instead of the macro // execution result. this.macroErrorManager.generateError( macroBlock, String.format( "Invalid macro parameters used for the \"%s\" macro.", macroBlock.getId()), e); this.logger.debug( "Invalid macro parameter for the [{}] macro. Internal error: [{}].", macroBlock.getId(), e.getMessage()); continue; } newBlocks = ((Macro) macro).execute(macroParameters, macroBlock.getContent(), macroContext); } catch (Throwable e) { // The Macro failed to execute. // The macro will not be executed and we generate an error message instead of the macro // execution result. // Note: We catch any Exception because we want to never break the whole rendering. this.macroErrorManager.generateError( macroBlock, String.format("Failed to execute the [%s] macro.", macroBlock.getId()), e); this.logger.debug( "Failed to execute the [{}] macro. Internal error [{}].", macroBlock.getId(), e.getMessage()); continue; } finally { ((MutableRenderingContext) this.renderingContext).setCurrentBlock(null); } // We wrap the blocks generated by the macro execution with MacroMarker blocks so that // listeners/renderers // who wish to know the group of blocks that makes up the executed macro can. For example this // is useful for // the XWiki Syntax renderer so that it can reconstruct the macros from the transformed XDOM. Block resultBlock = wrapInMacroMarker(macroBlock, newBlocks); // 5) Replace the MacroBlock by the Blocks generated by the execution of the Macro macroBlock.getParent().replaceChild(resultBlock, macroBlock); if (incrementRecursions) { ++recursions; } } }
@Override public List<Block> execute( IncludeMacroParameters parameters, String content, MacroTransformationContext context) throws MacroExecutionException { // Step 1: Perform checks. if (parameters.getReference() == null) { throw new MacroExecutionException( "You must specify a 'reference' parameter pointing to the entity to include."); } DocumentReference includedReference = resolve(context.getCurrentMacroBlock(), parameters); checkRecursiveInclusion(context.getCurrentMacroBlock(), includedReference); if (!this.documentAccessBridge.isDocumentViewable(includedReference)) { throw new MacroExecutionException( String.format( "Current user [%s] doesn't have view rights on document [%s]", this.documentAccessBridge.getCurrentUserReference(), this.defaultEntityReferenceSerializer.serialize(includedReference))); } Context parametersContext = parameters.getContext(); // Step 2: Retrieve the included document. DocumentModelBridge documentBridge; try { documentBridge = this.documentAccessBridge.getDocument(includedReference); } catch (Exception e) { throw new MacroExecutionException( "Failed to load Document [" + this.defaultEntityReferenceSerializer.serialize(includedReference) + "]", e); } // Step 3: Display the content of the included document. // Check the value of the "context" parameter. // // If CONTEXT_NEW then display the content in an isolated execution and transformation context. // // if CONTEXT_CURRENT then display the content without performing any transformations (we don't // want any Macro // to be executed at this stage since they should be executed by the currently running Macro // Transformation. DocumentDisplayerParameters displayParameters = new DocumentDisplayerParameters(); displayParameters.setContentTransformed(parametersContext == Context.NEW); displayParameters.setExecutionContextIsolated(displayParameters.isContentTransformed()); displayParameters.setSectionId(parameters.getSection()); displayParameters.setTransformationContextIsolated(displayParameters.isContentTransformed()); displayParameters.setTransformationContextRestricted( context.getTransformationContext().isRestricted()); displayParameters.setTargetSyntax(context.getTransformationContext().getTargetSyntax()); Stack<Object> references = this.inclusionsBeingExecuted.get(); if (parametersContext == Context.NEW) { if (references == null) { references = new Stack<Object>(); this.inclusionsBeingExecuted.set(references); } references.push(includedReference); } XDOM result; try { result = this.documentDisplayer.display(documentBridge, displayParameters); } catch (Exception e) { throw new MacroExecutionException(e.getMessage(), e); } finally { if (parametersContext == Context.NEW) { references.pop(); } } // Step 4: Wrap Blocks in a MetaDataBlock with the "source" meta data specified so that we know // from where the // content comes and "base" meta data so that reference are properly resolved MetaDataBlock metadata = new MetaDataBlock(result.getChildren(), result.getMetaData()); String source = this.defaultEntityReferenceSerializer.serialize(includedReference); metadata.getMetaData().addMetaData(MetaData.SOURCE, source); if (parametersContext == Context.NEW) { metadata.getMetaData().addMetaData(MetaData.BASE, source); } return Arrays.<Block>asList(metadata); }