@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);
  }
예제 #2
0
  @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;
      }
    }
  }