Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/delete.vm
### 代码内容：
## ---------------------------------------------------------------------------
## See the NOTICE file distributed with this work for additional
## information regarding copyright ownership.
##
## This is free software; you can redistribute it and/or modify it
## under the terms of the GNU Lesser General Public License as
## published by the Free Software Foundation; either version 2.1 of
## the License, or (at your option) any later version.
##
## This software is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
## Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public
## License along with this software; if not, write to the Free
## Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
## 02110-1301 USA, or see the FSF site: http://www.fsf.org.
## ---------------------------------------------------------------------------
#######################################################
###              DELETE PAGE ACTION
###              ------------------
###
#######################################################
##
#######################################################
##                     GLOBALS
#######################################################
#set($fullmsg = '')
#set($quote = '"')
#set($hasInlinks = false)
#######################################################
##                     DISPLAY
#######################################################
#set ($discard = $xwiki.jsfx.use('js/xwiki/delete.js'))
#template('refactoringStatus_macros.vm')
#template('refactoring_macros.vm')
#template('xwikivars.vm')

#controller()
#macro(displayContent $deletecontent)
#template("startpage.vm")

    #template("hierarchy.vm")
    #define($title)$!escapetool.xml($doc.plainTitle)#end
    #set($titleToDisplay = $services.localization.render('core.delete.title', [$title]))

      #template('contentheader.vm')
      $deletecontent

 ## xcontent

## mainContentArea

## main
#template("endpage.vm")
#end
#######################################################
##                    CONTROLLER
#######################################################
#macro(controller)
  #if("$!{request.jobId}" != '')
    #set ($deleteJobStatus = $services.job.getJobStatus($request.jobId.split('/')))
    ## A delete job is running
    #if ($xcontext.action == 'get')
      #outputDeleteJobStatusJSON($deleteJobStatus)
    #else
      #set ($content = "#displayDeleteJobStatus($deleteJobStatus)")
      #displayContent($content)
    #end
  #else
    #if("$!{request.id}" != '')
      ## request.id means that we are deleting a document from the recycle bin
      #set ($content = "#displayCompletelyDeleteConfirmMessage()")
      #displayContent($content)
    #else
      #set ($content = "#displayConfirmationPage()")
      #displayContent($content)
    #end
  #end
#end

#macro (outputDeleteJobStatusJSON $deleteJobStatus)
  #getJobStatusJSON($deleteJobStatus $json)
  $response.setContentType('application/json')
  $jsontool.serialize($json)
#end

#######################################################
##            DISPLAY RUNNING JOB
#######################################################
#macro (displayDeleteJobStatus $deleteJobStatus)
  #set ($discard = $xwiki.jsfx.use('uicomponents/job/job.js'))
  ## TODO: allows question templates to inject the web resources they need
  #set ($discard = $xwiki.linkx.use($services.webjars.url('org.xwiki.platform:xwiki-platform-tree-webjar', 'tree.min.css',
    {'evaluate': true}), {'type': 'text/css', 'rel': 'stylesheet'}))
  #set ($discard = $xwiki.jsfx.use("uicomponents/job/question/ExtensionBreakingQuestion.js", true))
  #set ($discard = $xwiki.jsfx.use("uicomponents/job/question/XClassBreakingQuestion.js", true))
  #set ($deleteJobState = $deleteJobStatus.state)
  #set ($finished = $deleteJobState == 'FINISHED')
  #set ($deleteJobRequest = $deleteJobStatus.request)
  #set ($jobStatusURL = $doc.getURL('get', $escapetool.url({
    'xpage': 'delete',
    'jobId': $request.jobId,
    'outputSyntax': 'plain'
  })))

$services.localization.render('core.delete.waitmessage')

  #if (!$finished)
    #displayJobProgressBar($deleteJobStatus)
    #displayJobQuestion($deleteJobStatus)
  #else
    #displayJobFinishedMessage($deleteJobStatus)
  #end
  #if ($isAdvancedUser || $isSuperAdmin)
    #displayJobStatusLog($deleteJobStatus true)
  #end

#end

#######################################################
##            DISPLAY CONFIRMATION PAGE
#######################################################
#macro(displayConfirmationPage)
  <form id="delete" class="xform" action="$doc.getURL('delete', "$!{languageparams}")" method="post">
    #getChildren()
    #displayNewTargetOption()
    #getChildren_legacy()
    #displayConfirmMessage()
  </form>
#end
#######################################################
##                  DISPLAY PANEL
#######################################################
#macro(displayPanel $panelId $panelClass $heading $body)

      $heading

      $body

#end
#######################################################
##                    CHILDREN
#######################################################
#macro(getChildren)
  #if ($doc.documentReference.name == 'WebHome') ## terminal documents have no child
    #set($childCountXWQL = "WHERE doc.fullName LIKE :space AND doc.fullName <> :fullName")
    #set ($query = $services.query.xwql($childCountXWQL))
    #set ($query = $query.bindValue('space').literal("${doc.space}.").anyChars().query())
    #set ($query = $query.bindValue('fullName', $doc.fullName))
    ## Note: we don't use any filter since we display all documents including translations and hidden documents in the
    ## table below.
    #set($childCount = $query.count())
    #if ($childCount > 0)
      ## Check if we should propose by default to affect children when the only remaining children (after the delete) would be the WebPreferences document.
      #set ($webPreferencesDocumentReference = $services.model.createDocumentReference('WebPreferences', $doc.documentReference.lastSpaceReference))
      #if ($childCount == 1 && $xwiki.exists($webPreferencesDocumentReference))
        #set ($forceAffectChildren = " checked='checked'")
      #end
      #define($heading)
        <input name="affectChildren" id="affectChildren" type="checkbox" $!{forceAffectChildren}/>$services.localization.render('core.delete.affectChildren') ($childCount)
      #end
      #define($body)
        ##
        ## List the documents to be deleted using a livetable
        ##
        #set ($collist = ['doc.title', 'doc.location', 'doc.date', 'doc.author'])
        #set ($colprops = {
          'doc.title'    : { 'link' : 'view' },
          'doc.location' : { 'type': 'text', 'filterable': true, 'html': true},
          'doc.author'   : { 'link' : 'author' }
        })
        ## Note: we also display translations in the table since we want to show to the user all the pages that will
        ## be deleted (including translations and hidden pages).
        #set ($urlParameters = "xpage=getdocuments&childrenOf=$escapetool.url($doc.space)&queryFilters=unique")
        #set ($options = {
          'url' : "$doc.getURL('get', $urlParameters)",
          'translationPrefix' : 'platform.index.',
          'outputOnlyHtml' : true
        })
        #livetable('deleteSpaceIndex' $collist $colprops $options)
      #end
      #displayPanel('panel-children', 'panel-default', $heading, $body)
    #end
  #end
#end
#######################################################
##                 LEGACY CHILDREN
##
## Get the children document based on the legacy
## parent/child relationship.
#######################################################
#macro(getChildren_legacy)
  #if($services.parentchild.isParentChildMechanismEnabled())
    #set($childrenStatement = 'where doc.fullName <> :parentFullName and (doc.parent = :parentFullName or (doc.parent = :parentName and doc.space = :parentSpace))')
    #set($childrenQuery = $services.query.xwql($childrenStatement))
    #set($discard = $childrenQuery.bindValue('parentFullName', $doc.fullName).bindValue('parentName', $doc.documentReference.name).bindValue('parentSpace', $doc.space))
    #set($children = $childrenQuery.addFilter('unique').execute())
    #if($children && $children.size() > 0)
      #define($heading)
        Orphans ($children.size())
      #end
      #define($message)

          #foreach($docname in $children)
            #set($rdoc = $xwiki.getDocument($docname).getTranslatedDocument())

$escapetool.xml($rdoc.getPlainTitle())

          #end

      #end
      #set($body = $services.localization.render('core.delete.orphansWarning', [$message]))
      #displayPanel('panel-legacy-children', 'panel-default', $heading, $body)
    #end
  #end
#end
#######################################################
##                 DISPLAY NEW TARGET OPTION
##
## Display the possibility to select a new target for existing backlinks and / or to create an automatic redirect to a
## different page.
#######################################################
#macro(displayNewTargetOption)
  #set($links = $doc.getBacklinks())
  #set ($hasInlinks = $links && $links.size() > 0)
  #define($heading)
    $escapetool.xml($services.localization.render('core.delete.backlinks')) ($links.size())
  #end
  #define($body)

          $escapetool.xml($services.localization.render('core.delete.backlinkTarget.label'))

        $escapetool.xml($services.localization.render('core.delete.backlinkTarget.hint'))

        #set ($pagePickerParams = {
          'id': 'newBacklinkTarget',
          'name': 'newBacklinkTarget'
        })
        #pagePicker($pagePickerParams)

      ##------------
      ## Links field
      ##------------
      #displayLinksCheckbox({
        'label': 'core.delete.updateLinks.label',
        'hint': 'core.delete.updateLinks.hint',
        'disabled': true
      })
      #displayAutoRedirectCheckbox({
        'label': 'core.delete.autoRedirect.label',
        'hint': 'core.delete.autoRedirect.hint',
        'disabled': true
      })

  #end
  $body
#end
#######################################################
##              DISPLAY CONFIRM MESSAGE
#######################################################
#macro(displayConfirmMessage)
  #if($xwiki.hasRecycleBin())
    #if($services.refactoring.isRecycleBinSkippingAllowed())

            $escapetool.xml($services.localization.render('core.recycleBin.shouldSkip.label'))

              <input name="shouldSkipRecycleBin" type="radio" value="false" checked/>
              $escapetool.xml($services.localization.render('core.recycleBin.shouldSkip.no'))

              <input name="shouldSkipRecycleBin" type="radio" value="true"/>
              $escapetool.xml($services.localization.render('core.recycleBin.shouldSkip.yes'))

    #else
      #if($hasInlinks)

$escapetool.xml($services.localization.render('core.recyclebin.confirmWithInlinks'))

      #else

$escapetool.xml($services.localization.render('core.recyclebin.confirm'))

      #end
    #end
  #else
    #if($hasInlinks)

$escapetool.xml($services.localization.render('core.delete.confirmWithInlinks'))

    #else

$escapetool.xml($services.localization.render('core.delete.confirm'))

    #end
  #end

    <input type="hidden" name="form_token" value="$!{services.csrf.getToken()}" />
    <input type="hidden" name="confirm" value="1"/>
    <input type="hidden" name="async" value="true"/>
    #if("$!{request.xredirect}" != '')
      <input type="hidden" name="xredirect" value="$!{escapetool.xml($request.xredirect)}"/>
    #end

  <button class="btn btn-danger confirm">$escapetool.xml($services.localization.render('delete'))</button>
  #if("$!{request.xredirect}" != '')
    #getSanitizedURLAttributeValue('a','href',$request.xredirect,$doc.getURL(),$cancelUrl)
  #else
    #set($cancelUrl = $escapetool.xml($doc.getURL()))
  #end
  $escapetool.xml($services.localization.render('cancel'))
#end
#######################################################
##      DISPLAY DELETE COMPLETELY CONFIRM MESSAGE
##
## Display a confirmation message when the user wants
## to delete a page from the recycle bin.
#######################################################
#macro(displayCompletelyDeleteConfirmMessage)
  #xwikimessageboxstart($services.localization.render('core.delete') $services.localization.render('core.recyclebin.completelyDeleteConfirm'))
    <form action="$xwiki.relativeRequestURL" method="post">

        ## CSRF prevention
        <input type="hidden" name="form_token" value="$!{services.csrf.getToken()}" />
        <input type="hidden" name="confirm" value="1"/>
        <input type="hidden" name="id" value="$!{escapetool.xml($request.id)}"/>
        #if("$!{request.xredirect}" != '')
          <input type="hidden" name="xredirect" value="$!{escapetool.xml($request.xredirect)}"/>
        #end

      <input type="submit" class="btn btn-primary" value="$services.localization.render('yes')"/>
      #if("$!{request.xredirect}" != '')
        #getSanitizedURLAttributeValue('a','href',$request.xredirect,$doc.getURL(),$cancelUrl)
      #else
        #set($cancelUrl = $escapetool.xml($doc.getURL()))
      #end
      $services.localization.render('no')
    </form>
  #xwikimessageboxend()
#end

Output:
【漏洞类型】路径遍历漏洞
【严重性】High
【漏洞摘要】这是一个路径遍历漏洞。问题在于`$request.xredirect`参数没有经过充分的验证和过滤，直接被插入到HTML的`href`属性中。攻击者可以利用这个漏洞通过构造恶意的`xredirect`参数来访问服务器上的任意文件或页面，从而获取敏感信息或执行未经授权的操作。
【关键代码模式】`$escapetool.xml($request.xredirect)`，`<a class="btn btn-default cancel" href="$!{escapetool.xml(${cancelUrl})}">$escapetool.xml($services.localization.render('cancel'))</a>`，`<input type="hidden" name="xredirect" value="$!{escapetool.xml($request.xredirect)}"/>`
【修复方法说明】修复方法通过移除对`$request.xredirect`参数的XML转义，直接使用其值。这样可以避免路径遍历漏洞，但仍然需要确保`xredirect`参数的安全性，例如通过白名单验证允许的URL模式，或者限制用户的输入。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-ui/pom.xml
### 代码内容：
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.xwiki.platform</groupId>
    <artifactId>xwiki-platform-appwithinminutes</artifactId>
    <version>15.1-SNAPSHOT</version>
  </parent>
  <artifactId>xwiki-platform-appwithinminutes-ui</artifactId>
  <name>XWiki Platform - App Within Minutes - UI</name>
  <packaging>xar</packaging>
  <properties>

    <xwiki.extension.features>org.xwiki.platform:xwiki-platform-appwithinminutes</xwiki.extension.features>

    <xwiki.extension.name>App Within Minutes Application</xwiki.extension.name>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-sheet-ui</artifactId>
      <version>${project.version}</version>
      <type>xar</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-livetable-ui</artifactId>
      <version>${project.version}</version>
      <type>xar</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>

      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-uiextension-ui</artifactId>
      <version>${project.version}</version>
      <type>xar</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>

      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-icon-ui</artifactId>
      <version>${project.version}</version>
      <type>xar</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>

      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-administration-ui</artifactId>
      <version>${project.version}</version>
      <type>xar</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>

      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-xclass-ui</artifactId>
      <version>${project.version}</version>
      <type>xar</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-localization-source-wiki</artifactId>
      <version>${project.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-rendering-macro-include</artifactId>
      <version>${project.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-rendering-macro-velocity</artifactId>
      <version>${project.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.platform</groupId>
      <artifactId>xwiki-platform-rendering-macro-groovy</artifactId>
      <version>${project.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.rendering</groupId>
      <artifactId>xwiki-rendering-macro-box</artifactId>
      <version>${rendering.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.rendering</groupId>
      <artifactId>xwiki-rendering-macro-html</artifactId>
      <version>${rendering.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.xwiki.rendering</groupId>
      <artifactId>xwiki-rendering-macro-message</artifactId>
      <version>${rendering.version}</version>
      <scope>runtime</scope>
    </dependency>

    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>scriptaculous</artifactId>
    </dependency>
  </dependencies>
</project>

## 内容2
### 文件路径：
xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-ui/src/main/resources/AppWithinMinutes/ClassEditSheet.xml
### 代码内容：
<?xml version="1.1" encoding="UTF-8"?>

<xwikidoc version="1.5" reference="AppWithinMinutes.ClassEditSheet" locale="">
  <web>AppWithinMinutes</web>
  <name>ClassEditSheet</name>
  <language/>
  <defaultLanguage/>
  <translation>0</translation>
  <creator>xwiki:XWiki.Admin</creator>
  <parent>AppWithinMinutes.WebHome</parent>
  <author>xwiki:XWiki.Admin</author>
  <contentAuthor>xwiki:XWiki.Admin</contentAuthor>
  <version>1.1</version>
  <title>#if ("$!appTitle" != '')$appTitle#else$doc.pageReference.name#end</title>
  <comment/>
  <minorEdit>false</minorEdit>
  <syntaxId>xwiki/2.1</syntaxId>
  <hidden>true</hidden>
  <content>{{include reference="AppWithinMinutes.VelocityMacros" /}}

{{groovy}}
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.api.Context;
import com.xpn.xwiki.api.Object;
import com.xpn.xwiki.api.PropertyClass;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;

/**
 * Used to preview class fields that have a custom display associated, before they are actually added/saved to the
 * class. For instance, when the user drags a Date field from the palette to the field canvas the class editor needs to
 * display that Date field as if the user would be editing an object with this Date field in "Inline form" edit mode.
 * This means that if the Date field has a custom display, the custom display should be used (e.g. using a Date picker).
 */
class PropertyCustomDisplayer
{
    private XWikiContext context;

    public PropertyCustomDisplayer(Context context)
    {
        this.context = context.getContext();
    }

    public String display(PropertyClass property, String prefix, com.xpn.xwiki.api.Object object)
    {
        HashMap<String, Object> backup = new HashMap<String, Object>();
        try {
            XWikiDocument.backupContext(backup, this.context);
            return this.displayInternal(property.getPropertyClass(), prefix, object.getXWikiObject());
        } finally {
            XWikiDocument.restoreContext(backup, this.context);
        }
    }

    private String displayInternal(com.xpn.xwiki.objects.classes.PropertyClass property, String prefix, BaseObject object)
    {
        StringBuffer result = new StringBuffer();
        property.displayCustom(result, property.getName(), prefix, "edit", object, this.context);
        return result.toString();
    }
}
xcontext.put('propertyCustomDisplayer', new PropertyCustomDisplayer(xcontext))
{{/groovy}}

{{velocity output="false"}}
#**
 * Constants
 *#
## Magic date used to mark in AWM that the date field is not set for the current entry. See https://jira.xwiki.org/browse/XWIKI-10296
#set($MAGIC_DATE = $datetool.toDate('yyyy-MM-dd', '9999-12-31'))

#**
 * Displays the field palette.
 *#
#macro (displayFieldPalette)

$services.localization.render('platform.appwithinminutes.classEditorPaletteTitle')

$services.localization.render('platform.appwithinminutes.classEditorPaletteHint')

    ## List all form field types, grouped by category.
    #set ($formFieldDocs = [])
    #set ($formFieldClassName = 'AppWithinMinutes.FormFieldClass')
    #set ($categoryListStatement = 'from doc.object(AppWithinMinutes.FormFieldCategoryClass) as category order by category.priority')

    #foreach ($category in $services.query.xwql($categoryListStatement).execute())
      #set ($categoryDoc = $xwiki.getDocument($category))

$categoryDoc.plainTitle

        #set ($formFieldsForCategoryStatement = "from doc.object($formFieldClassName) as field where field.category = :category order by field.priority")
        #set ($formFieldsForCategoryQuery = $services.query.xwql($formFieldsForCategoryStatement).bindValue('category', $category))

        #foreach ($formField in $formFieldsForCategoryQuery.execute())
          #set ($formFieldDoc = $xwiki.getDocument($formField))
          #set ($discard = $formFieldDocs.add($formFieldDoc))
          #set ($formFieldIcon = $formFieldDoc.getObject($formFieldClassName).getProperty('icon').value)
          #set ($formFieldIconRendered = $services.icon.renderHTML($formFieldIcon))
          #if ("$!formFieldIconRendered" == "")
            #if ($formFieldIcon.contains('/'))
              #set ($formFieldIconURL = $xwiki.getSkinFile($formFieldIcon))
            #else
              #set ($formFieldIconURL = $formFieldDoc.getAttachmentURL($formFieldIcon))
            #end
            #set ($formFieldIconRendered = "")
          #end

            $formFieldIconRendered
            $escapetool.xml($formFieldDoc.plainTitle)
            ## FIXME: We should use the 'get' action instead to prevent the stats module from recording this AJAX request.
            ## The 'edit' action is a temporary solution until the sheet module is modified to allow a sheet to be enforced through
            ## the query string even if it doesn't match the action (e.g. the 'get' action).
            ## The sheet parameter is required when editing a new class because the request will be made to a document that doesn't exist.
            ## FIXME2: In the future don't force the text editor type and instead use the default editor. This means
            ## that if the WYSIWYG editor is used, we'll need to convert the HTML into the target syntax so that the
            ## Template in #updateAndSaveTemplate is saved with target syntax and not HTML.
            ## See https://jira.xwiki.org/browse/XWIKI-13789
            #set ($fieldURL = $doc.getURL('edit', $escapetool.url({
              'xpage': 'plain',
              'sheet': 'AppWithinMinutes.ClassEditSheet',
              'template': 'AppWithinMinutes.ClassTemplate',
              'field': $formFieldDoc.fullName,
              'xeditmode': 'text'
            })))
            <input type="hidden" value="$fieldURL" class="data"/>

        #end

    #end

#end

#**
 * Displays the field canvas.
 *#
#macro (displayFieldCanvas)
  #set ($propertyType2FormField = {})
  #foreach ($formFieldDoc in $formFieldDocs)
    ## Use the type of the field template.
    #set ($type = $formFieldDoc.getxWikiClass().properties.get(0).classType)
    #set ($discard = $propertyType2FormField.put($type, $formFieldDoc))
  #end

      $services.localization.render('platform.appwithinminutes.classEditorCanvasHint')

      #set ($unknownFields = [])
      #foreach ($field in $doc.getxWikiClass().properties)
        #set ($formFieldDoc = $propertyType2FormField.get($field.classType))
        #if ($formFieldDoc)

#displayField($field $formFieldDoc)

        #else
          #set($discard = $unknownFields.add($field))
        #end
      #end

      ## Output the field meta data even if the field is not supported to preserve it when the class is saved.
      #foreach ($field in $unknownFields)
        #displayFieldMetaData($field)
      #end

#end

#**
 * Display the options to create/update the class template, the class sheet and the class translation bundle.
 *#
#macro (displayClassOptions)
  #set ($className = $stringtool.removeEnd($doc.fullName, 'Class'))
  #set ($templateReference = $services.model.resolveDocument("${className}Template"))
  #set ($translationsReference = $services.model.resolveDocument("${className}Translations"))
  #set ($classSheets = $services.sheet.getClassSheets($doc))
  #set ($sheetReference = $null)
  #if ($classSheets.isEmpty())
    #set ($sheetReference = $services.model.resolveDocument("${className}Sheet"))
  #elseif ($classSheets.size() == 1)
    #set ($sheetReference = $classSheets.get(0))
  #end
  ## Hide the options if neither the sheet nor the template nor the translation bundle exists. They don't have to be
  ## updated, they have to be created.

        <input type="checkbox" id="updateClassTemplate" name="updateClassTemplate" checked="checked" />
        $services.localization.render('platform.appwithinminutes.classEditorUpdateTemplateLabel')

        $services.localization.render('platform.appwithinminutes.classEditorUpdateTemplateHint',
          ["#pageLink($templateReference)"])

        <input type="checkbox" id="updateClassSheet" name="updateClassSheet"
          #if ($sheetReference)checked="checked" #{else}disabled="disabled" #end/>
        $services.localization.render('platform.appwithinminutes.classEditorUpdateSheetLabel')

    #if ($sheetReference)

        $services.localization.render('platform.appwithinminutes.classEditorUpdateSheetHint',
          ["#pageLink($sheetReference)"])

    #else

        $services.localization.render('platform.appwithinminutes.classEditorMultipleSheetsWarning')

    #end

        <input type="checkbox" id="updateClassTranslations" name="updateClassTranslations" checked="checked" />
        $services.localization.render('platform.appwithinminutes.classEditorUpdateTranslationsLabel')

        $services.localization.render('platform.appwithinminutes.classEditorUpdateTranslationsHint',
          ["#pageLink($translationsReference)"])

#end

#macro (pageLink $reference)
  #set ($class = 'wikilink')
  #set ($action = 'view')
  #set ($params = {})
  #if (!$xwiki.exists($reference))
    #set ($class = 'wikicreatelink')
    #set ($action = 'create')
    #set ($discard = $params.put('parent', $doc.fullName))
  #end
  $escapetool.xml($reference.name)##
#end

#**
 * Display a form field.
 *#
#macro (displayField $field $formFieldDoc)
  #if ($formFieldDoc.getObject('XWiki.StyleSheetExtension'))
    #set ($discard = $xwiki.ssx.use($formFieldDoc.fullName))
  #end
  #if ($formFieldDoc.getObject('XWiki.JavaScriptExtension'))
    #set ($discard = $xwiki.jsx.use($formFieldDoc.fullName))
  #end

    #displayFieldMetaData($field)
    ## We need this information to avoid querying and loading all FormField documents twice.
    ## NOTE: We use a different ID format to avoid collisions with the field meta properties.
    <input type="hidden" id="template-$field.name" name="template-$field.name"
      value="$escapetool.xml($formFieldDoc.fullName)"
      data-propertyName="$escapetool.xml($formFieldDoc.getxWikiClass().propertyNames[0])" />

  #set ($className = $stringtool.removeEnd($doc.fullName, 'Class'))
  #set ($templateRef = $services.model.resolveDocument("${className}Template"))
  #set ($templateDoc = $xwiki.getDocument($templateRef))
  ## Simulate the editing of the class instance from the template document.
  ## Note that we can't simply call display on the template document because $field could be a new field that hasn't
  ## been added to the class yet (so the object from the template doesn't have this field yet).

    #displayFieldProperty($field "${doc.fullName}_0_" $templateDoc.getObject($doc.fullName, true))

  #set ($propertyNames = ['name', 'prettyName', 'number', 'required', 'hint'])
  #set ($formFieldObj = $formFieldDoc.getObject('AppWithinMinutes.FormFieldClass'))
  #set ($customPropertyNames = $formFieldObj.getProperty('properties').value.split('\s+'))
  #set ($discard = $customPropertyNames.removeAll($propertyNames))
  #set ($discard = $propertyNames.addAll($customPropertyNames.subList(0, $customPropertyNames.size())))

    #foreach ($propertyName in $propertyNames)
      #set ($propertyDefinition = $field.xWikiClass.get($propertyName))
      #if ($propertyDefinition)
        #displayFieldProperty($propertyDefinition "field-${field.name}_" $field)
      #end
    #end

#end

#**
 * Display the field meta data. This is needed to preserve the field when its type is not supported by the editor.
 *#
#macro (displayFieldMetaData $field)
  <input type="hidden" id="type-$field.name" name="type-$field.name" value="$field.classType" />
#end

#**
 * Displays a configuration property of a class field. This macro can also be used to display a property of an object.
 *#
#macro (displayFieldProperty $property $prefix $field)
  #set ($displayFormType = $property.getProperty('displayFormType'))
  #if ($property.classType == 'Boolean' && (!$displayFormType || $displayFormType.value == 'checkbox'))

        #displayPropertyEditInput($property, $prefix, $field)$escapetool.xml($property.prettyName)

  #else

$escapetool.xml($property.prettyName)

#displayPropertyEditInput($property, $prefix, $field)

  #end
#end

#**
 * Displays the input used to edit the specified property of the given object. The given object can be either an
 * instance of an XWiki class or a class field. In the first case the property represents an object field and in the
 * second case the property represents a field meta property. We currently don't use custom display for metaproperty,
 * so in that case we fallback on displayEdit.
 *#
#macro (displayPropertyEditInput $property $prefix $object)
  #set ($wrappedProperty = $property.propertyClass)
  #if ($wrappedProperty.isCustomDisplayed($xcontext.context))
    #set ($customDisplayer = $!xcontext.get('propertyCustomDisplayer').display($property, $prefix, $object))
    #if ((! $customDisplayer) && ("$!customDisplayer" == ""))
      $doc.displayEdit($property, $prefix, $object)
    #else
      $customDisplayer
    #end
  #else
    $doc.displayEdit($property, $prefix, $object)
  #end
#end

#**
 * Called when a new form field is added via AJAX.
 *#
#macro (displayNewField)
  ## Output the SkinExtension hooks to allow field displayers to pull JavaScript/CSS resources.
  ## Output also the LinkExtension hook because $xwiki.linkx.use() is used to load CSS files from WebJars.
  ## The class editor moves this resource includes in the HTML page head.

  #skinExtensionHooks
  #set ($formFieldDoc = $xwiki.getDocument($request.field))
  #set ($formFieldDocClassFields = $formFieldDoc.getxWikiClass().getXWikiClass().properties)
  #if ($formFieldDocClassFields.size() > 0)
    ## Clone the field template.
    #set ($field = $formFieldDocClassFields.get(0).clone())
    #if ("$!field.prettyName" == '')
      #set ($discard = $field.setPrettyName($formFieldDoc.title))
    #end
    #set ($xclass = $doc.getxWikiClass().getXWikiClass())
    #set ($discard = $xclass.addField($field.name, $field))
    #set ($discard = $field.setObject($xclass))
    #displayField($doc.getxWikiClass().get($field.name) $formFieldDoc)
  #else
    Unsupported form field.
  #end
#end

#**
 * Preview a class field (requires Programming Right).
 *#
#macro (previewField)
  ## Find the request parameter that specifies the field template.
  #foreach ($paramName in $request.getParameterMap().keySet())
    #if ($paramName.startsWith('template-'))
      #set ($fieldName = $paramName.substring(9))
      #set ($fieldTemplateDoc = $xwiki.getDocument($request.getParameter($paramName)))
      #break
    #end
  #end
  ##
  ## Clone the field template.
  #set ($field = $fieldTemplateDoc.getxWikiClass().getXWikiClass().properties.get(0).clone())
  ##
  ## Update the field meta properties based on the submitted data.
  #set ($valuesFromRequest = $xcontext.context.getForm().getObject("field-$fieldName"))
  #set ($discard = $field.getxWikiClass().fromMap($valuesFromRequest, $field))
  ##
  ## Don't rename the field (ignore the submitted name).
  #set ($discard = $field.setName($fieldName))
  ##
  ## We have to add the field to the class before setting its value.
  ## (otherwise the field value from the request is ignored).
  #set ($xclass = $doc.getxWikiClass().getXWikiClass())
  #set ($discard = $xclass.addField($fieldName, $field))
  #set ($discard = $field.setObject($xclass))
  ##
  ## Create an object that has this field and set its value from request.
  #set ($object = $fieldTemplateDoc.getObject($doc.fullName, true))
  ##
  ## Filter empty values from the request, otherwise the update method could try to select an invalid value.
  #set ($values = [])
  #foreach ($value in $request.getParameterValues("${doc.fullName}_0_$fieldName"))
    #if ($value != '')
      #set ($discard = $values.add($value))
    #end
  #end
  #if ($values.size() > 0)
    #set ($stringArray = $request.getParameterValues("template-$fieldName"))
    #set ($discard = $xclass.fromMap({$fieldName: $values.toArray($stringArray)}, $object.getXWikiObject()))
  #end
  ##
  ## Display the field.
  #set ($field = $doc.getxWikiClass().get($fieldName))
  #displayPropertyEditInput($field, "${doc.fullName}_0_", $object)
#end

#**
 * Display the edit class form.
 *#
#macro (displayEditForm)
  #set ($discard = $xwiki.jsx.use('AppWithinMinutes.ClassEditSheet'))
  #set ($discard = $xwiki.ssx.use('AppWithinMinutes.ClassEditSheet'))
  #set ($discard = $xwiki.ssx.use('AppWithinMinutes.ClassSheetGenerator'))
  #if ("$!request.wizard" == 'true')
    #appWizardHeader('structure')
  #end
  #displayFieldPalette()
  #displayFieldCanvas()
  #displayClassOptions()
  #if("$!request.wizard" == 'true')
    #appWizardFooter('structure')
  #end

#end

#**
 * Displays either the edit class form or a new form field. The later is used when adding a new form field via AJAX.
 *#
#macro (doEdit)
  #if ("$!request.field" != '')
    #displayNewField()
  #elseif ("$!request.preview" == 'true')
    #previewField()
  #else
    ## Make sure that only the sheet content is rendered when the class is saved using AJAX.

      <input type="hidden" name="xpage" value="plain" />
      #if ($request.wizard == 'true')
        ## Preserve the wizard mode.
        <input type="hidden" name="wizard" value="true" />
      #end
      ## Compute the application title to be used as the wizard step title.
      #getAppTitle

    #displayEditForm()
  #end
#end

#**
 * Create the home page of the application code space, if it doesn't exist already.
 *#
#macro (maybeCreateCodeSpace)
  #set ($codeHomePageReference = $services.model.resolveDocument('', 'default', $doc.documentReference.parent))
  #if (!$xwiki.exists($codeHomePageReference))
    #set ($codeSpaceTemplate = $services.model.resolveDocument('AppWithinMinutes.CodeSpaceTemplate'))
    #set ($copyAsJob = $services.refactoring.copyAs($codeSpaceTemplate, $codeHomePageReference))
    #try()
      #set ($discard = $copyAsJob.join())
      #set ($copyAsJobStatus = $services.job.getJobStatus($copyAsJob.request.id))
      #set ($errorMessage = $copyAsJobStatus.logTail.getFirstLogEvent('ERROR').toString())
    #end
  #end
#end

#**
 * Updates and saves the class definition based on the submitted data.
 *#
#macro(updateAndSaveClass)
  #set($class = $doc.xWikiClass)
  #set($xclass = $class.getXWikiClass().clone())
  #set($xdoc = $doc.document)
  ##
  ## Handle new fields and field type changes.
  ##
  #set($fieldNames = [])
  #foreach($paramName in $request.getParameterMap().keySet())
    #if($paramName.startsWith('type-'))
      #set($fieldName = $paramName.substring(5))
      #set($fieldType = $request.getParameter($paramName))
      #set($field = $class.get($fieldName))
      #if(!$field || $field.classType != $fieldType)
        #if($field)
          ## The field type has changed. Remove the field and add a new one with the proper type.
          #set($discard = $xclass.removeField($fieldName))
        #end
        ## Add a new class field with the specified type.
        #set($fieldTemplateRef = $request.getParameter("template-$fieldName"))
        #if("$!fieldTemplateRef" != '')
          #set($fieldTemplateDoc = $xwiki.getDocument($fieldTemplateRef))
          #set($field = $fieldTemplateDoc.getxWikiClass().getXWikiClass().properties.get(0).clone())
          #set($discard = $field.setObject($xclass))
          #set($discard = $xclass.addField($fieldName, $field))
          #set($discard = $fieldNames.add($fieldName))
          #set($discard = $xdoc.setMetaDataDirty(true))
        #end
      #else
        #set($discard = $fieldNames.add($fieldName))
      #end
    #end
  #end
  ##
  ## Handle deleted fields.
  ##
  #foreach($field in $class.properties)
    #if(!$fieldNames.contains($field.name))
      #set($discard = $xclass.removeField($field.name))
    #end
  #end
  ##
  ## Handle field updates.
  ##
  #set($fieldsToRename = {})
  #foreach($fieldName in $xclass.propertyNames)
    #set($field = $xclass.get($fieldName))
    #set($valuesFromRequest = $xcontext.context.getForm().getObject("field-$fieldName"))
    #set($discard = $field.getxWikiClass().fromMap($valuesFromRequest, $field))
    #if($field.name.matches('^[a-zA-Z_][\w:\-\.]*$'))
      #if($fieldName != $field.name)
        ## The field name has changed.
        #if($xclass.get($field.name))
          ## There is already a field with the same name.
          #set($errorMessage = $services.localization.render('platform.appwithinminutes.classEditorDuplicateFieldNameError', [$field.name]))
          #break
        #else
          #set($discard = $xclass.removeField($fieldName))
          #set($discard = $xclass.addField($field.name, $field))
          #set($originalField = $class.get($fieldName))
          #if($originalField)
            ## This is not a new field.
            #set($discard = $fieldsToRename.put($fieldName, $field.name))
            #set($discard = $xclass.addPropertyForRemoval($originalField.propertyClass))
          #end
        #end
      #end
    #else
      #set($errorMessage = $services.localization.render('propertynamenotcorrect'))
      #break
    #end
  #end
  ##
  ## Save
  ##
  #if(!$errorMessage)
    #set($discard = $xdoc.setXClass($xclass))
    #set($discard = $xdoc.renameProperties($doc.documentReference, $fieldsToRename))
    #set($discard = $xdoc.setHidden(true))
    #set($discard = $xdoc.setMetaDataDirty(true))
    #set($discard = $doc.save($services.localization.render('core.comment.updateClassProperty'), $minorEdit))
  #end
  ##
  ## Handle field renames.
  ##
  #if(!$errorMessage && !$fieldsToRename.isEmpty())
    ## We need to load all documents (except the class and template, which we handle below) that have objects of this class and rename their properties.
    ## If we don`t skip the template, we can not control the behaviour of emptyIsToday for date fields, which we want to handle in #updateAndSaveTemplate only once.
    ##
    ## FIXME: even if it is not a good practice to have an object in the class document, it is still possible. We should handle field renames for the class document
    ## as well. Note that there is a possibility that objects in the class' document are automatically updated. Needs checking.
    ##
    ## We use HQL because XWQL doesn't allow us to escape the special characters from the class name.
    #set($instancesStatement = ', BaseObject as obj where doc.fullName = obj.name and obj.className = :className'
      + ' and doc.fullName not in (:className, :templateName)')
    #set($className = $stringtool.removeEnd($doc.fullName, 'Class'))
    #set($instancesQuery = $services.query.hql($instancesStatement).bindValue('className', $doc.fullName).bindValue(
      'templateName', "${className}Template"))
    #foreach($instanceDocName in $instancesQuery.execute())
      #set($instanceDoc = $xwiki.getDocument($instanceDocName))
      #set($discard = $instanceDoc.document.renameProperties($doc.documentReference, $fieldsToRename))
      #set($discard = $instanceDoc.save($services.localization.render('core.comment.updateClassPropertyName'), true))
    #end
  #end
#end

#**
 * Handle Date fields that have the "Empty is today" option checked in the class edit form.
 * See https://jira.xwiki.org/browse/XWIKI-10296
 **#
#macro(handleEmptyIsTodayDateFields $templateDoc)
  #foreach($property in $doc.xWikiClass.properties)
    ## We check directly on the request if the user provided an empty date. We can not check from the template
    ## document's object that we've just parsed from the request using the updateObjectFromRequest method because it
    ## already applies the emtpyIsToday mechanism and that would not be good for us.
    #set($newValueRequestParameterName = "${doc.fullName}_0_${property.name}")
    #set($newDateStringValue = "$!{request.getParameter($newValueRequestParameterName)}")
    #if($property.classType == 'Date' && $property.getValue('emptyIsToday') == 1 && $newDateStringValue == '')
      #set($discard = $templateDoc.set($property.name, $MAGIC_DATE))
    #end
  #end
#end

#**
 * Updates and saves the class template based on the submitted data.
 *#
#macro(updateAndSaveTemplate)
  #if(!$errorMessage && $request.updateClassTemplate)
    #set($className = $stringtool.removeEnd($doc.fullName, 'Class'))
    #set($templateRef = $services.model.resolveDocument("${className}Template"))
    #set($templateDoc = $xwiki.getDocument($templateRef))
    #set($discard = $templateDoc.setParent($doc.documentReference.name))
    #if ($request.templateTitle)
      #set($discard = $templateDoc.setTitle($request.templateTitle))
    #end
    #if ($request.templateContent)
      #set($discard = $templateDoc.setContent($request.templateContent))
    #end
    ## Rename the properties of the template's object, if applicable.
    #set($discard = $templateDoc.document.renameProperties($doc.documentReference, $fieldsToRename))
    ## Fill the template's object with the default values from the class editor's form.
    #set($discard = $templateDoc.updateObjectFromRequest($doc.fullName))
    ##
    #handleEmptyIsTodayDateFields($templateDoc)
    #set($discard = $templateDoc.setHidden(true))
    #set($discard = $templateDoc.save(
      $services.localization.render('platform.appwithinminutes.classEditorTemplateSaveComment'),
      $minorEdit))
  #end
#end

#**
 * Updates and saves the class sheet based on the submitted data.
 *#
#macro(updateAndSaveSheet)
  #if(!$errorMessage && $request.updateClassSheet)
    #set($classSheets = $services.sheet.getClassSheets($doc))
    #if($classSheets.isEmpty())
      #set($className = $stringtool.removeEnd($doc.fullName, 'Class'))
      #set($sheetReference = $services.model.resolveDocument("${className}Sheet"))
      #set($discard = $services.sheet.bindClassSheet($doc, $sheetReference))
      #set($discard = $doc.save($services.localization.render('platform.appwithinminutes.classEditorBindSheetSaveComment'),
        $minorEdit))
    #elseif($classSheets.size() == 1)
      #set($sheetReference = $classSheets.get(0))
    #end
    #if($sheetReference)
      #set($sheetDoc = $xwiki.getDocument($sheetReference))
      #set($sheetGeneratorDoc = $xwiki.getDocument('AppWithinMinutes.ClassSheetGenerator'))
      #set($discard = $sheetDoc.setParent($doc.documentReference.name))
      #set($discard = $sheetDoc.setContent($doc.getRenderedContent($sheetGeneratorDoc.content,
        $sheetGeneratorDoc.syntax.toIdString(), 'plain/1.0')))
      ## We assume for now that the output produced by the sheet generator uses the same syntax as the code of the sheet
      ## generator. We have to set the syntax because the default wiki syntax (used when creating new wiki pages) could
      ## be different than the one used by the sheet generator.
      #set($discard = $sheetDoc.setSyntax($sheetGeneratorDoc.syntax))
      #set($discard = $sheetDoc.setHidden(true))
      #set($discard = $sheetDoc.save($services.localization.render('platform.appwithinminutes.classEditorSheetSaveComment'),
        $minorEdit))
    #end
  #end
#end

#**
 * Updates and saves the class translation bundle based on the submitted data.
 *#
#macro(updateAndSaveTranslations)
  #if(!$errorMessage && $request.updateClassTranslations)
    #set($className = $stringtool.removeEnd($doc.fullName, 'Class'))
    #set($translationsRef = $services.model.resolveDocument("${className}Translations"))
    #set($translationsDoc = $xwiki.getDocument($translationsRef))
    #set($translationsObj = $translationsDoc.getObject('XWiki.TranslationDocumentClass', true))
    #set ($scope = 'USER')
    #if ($services.security.authorization.hasAccess('admin', $doc.documentReference.wikiReference))
      #set ($scope = 'WIKI')
    #end
    #set($discard = $translationsObj.set('scope', $scope))
    #set($discard = $translationsDoc.setParent($doc.documentReference.name))
    #set($translationsGeneratorDoc = $xwiki.getDocument('AppWithinMinutes.ClassTranslationsGenerator'))
    #set($discard = $translationsDoc.setContent($doc.getRenderedContent($translationsGeneratorDoc.content,
      $translationsGeneratorDoc.syntax.toIdString(), 'plain/1.0')))
    #set($discard = $translationsDoc.setSyntaxId('plain/1.0'))
    #set($discard = $translationsDoc.setHidden(true))
    #set($discard = $translationsDoc.save(
      $services.localization.render('platform.appwithinminutes.classEditorTranslationsSaveComment'),
      $minorEdit))
  #end
#end

#**
 * Updates and saves the class definition, the class sheet and the class template.
 *#
#macro (doSave)
  #set ($minorEdit = "$!request.minorEdit" != '')
  #maybeCreateCodeSpace
  #updateAndSaveClass
  #updateAndSaveTemplate
  #updateAndSaveSheet
  #updateAndSaveTranslations
  #if ($action == 'save')
    #if ($errorMessage)

$errorMessage

    #elseif ("$!request.wizard" == 'true')
      ## Redirect to next wizard step.
      #set ($className = $stringtool.removeEnd($doc.fullName, 'Class'))
      #set ($templateProviderReference = $services.model.resolveDocument("${className}TemplateProvider"))
      #set ($queryString = {
        'wizard': true,
        'sheet': 'AppWithinMinutes.TemplateProviderEditSheet'
      })
      #if (!$xwiki.exists($templateProviderReference))
        #set ($discard = $queryString.putAll({
          'template': 'XWiki.TemplateProviderTemplate',
          'parent': $doc.fullName
        }))
      #end
      $response.sendRedirect($xwiki.getURL($templateProviderReference, 'edit', $escapetool.url($queryString)))
    #else
      ## Redirect to view mode.
      $response.sendRedirect($doc.getURL())
    #end
  #else
    #if ($errorMessage)
      $response.sendError(400, $errorMessage)
    #else
      $response.setStatus(204)
    #end
  #end
#end
{{/velocity}}

{{velocity}}
#if("$!request.wizard" == 'true')
  {{include reference="AppWithinMinutes.WizardStep" /}}
#end
{{/velocity}}

{{velocity}}
{{html clean="false"}}
## Determine the action button that triggered the request
#set ($action = 'edit')
#foreach ($paramName in $request.getParameterMap().keySet())
  #if ($paramName.startsWith('xaction_'))
    #set ($action = $paramName.substring(8))
    #break
  #end
#end
#if ($action == 'edit')
  #doEdit()
#elseif ($action == 'save' || $action == 'saveandcontinue')
  #if ($services.csrf.isTokenValid($request.form_token))
    #doSave()
  #else
    $response.sendRedirect($services.csrf.getResubmissionURL())
  #end
#end
{{/html}}
{{/velocity}}</content>
  <attachment>
    <filename>arrow-move.png</filename>
    <mimetype>image/png</mimetype>
    <author>xwiki:XWiki.Admin</author>
    <version>1.1</version>
    <comment/>
    <content>iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sFCwoGAh4PDUEAAAHoSURBVCjPY2CAAotpDxkYGBgYzKc/1fBe9ua05cxnbgxYACMyx272C10pUbYZxuoCVneffXt29vr7XGEBng07YgT/wdQwGXRdhWu4eftm7cHjl4V+/vr9d/v+C28ePbpXuPPAAXYGBgYGg84rCJO1Wy/q6ndcXsXAwMDAmLwluvbU93/cOTvXMTAwMOh3Xpmm03bJB6aWRaHimIaUCOeMZy8/CUkVHYr+9P2H+dev3/8x/WfkVig7Gvr54xd1Qy1R/7eFh7OERbg3M+o1nTjtYqNtwsj4/+/3P3+Y3n7++u/r159MrOwsf///+8/8n4Hh79cvP1gY/v57d+3acy2W58/fVt+882T+9dvP3nz+/OPu399/uY09jJyO7zx3nIWN5fXf33/F1K01ja8fu9rNw8f1jun1dO9dBw5fyf344fOn1zO8g/58+TTnDRM7879fP1+/nOYVzMj479HV/RdauQQ4eh5NcP3NwsDAwMDK+G8DMzPzdt7oZdP+fvum/p6Z8++vjx/EeGOWL/3140c1ExPT0yd97n/Es7aiRgpH0Fwfvo4LT5k2///Pv/zdNw7PqdWcYQtZsUUgA1f4QkjEWLX6szeefstk21HBkrKOhYEQYAqay8Tg0C3O5DeTlYGBgYE1cC6KPAAEFtSlCs4TrgAAAABJRU5ErkJggg==</content>
    <filesize>616</filesize>
  </attachment>
  <attachment>
    <filename>bulletcross.png</filename>
    <mimetype>image/png</mimetype>
    <author>xwiki:XWiki.Admin</author>
    <version>1.1</version>
    <comment/>
    <content>iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAB3RJTUUH1woQCDkQxGYFbwAAAAlwSFlzAAAewQAAHsEBw2lUUwAAAARnQU1BAACxjwv8YQUAAAHaSURBVHja3VJBa9RAFP5mJroxm3TdrR4sgojHFaGIvVvBo3gWlGr9CUrFXyD4KwQR/A1KD4VW8NBTrnWr6XazLVvbjW4mmcyML9sqG1v17oOXSd7M982X7z1gIqy1/0y5sOhNfrMKwZOlw5eXL3BSZAuLsIWCUQW8t2/GNV45IcTh+vTZX8FGKSR37p5AoPUlIrGWc5s/etz+WZYPHrZBNR4EdvBh7eaYKFf4eus2r/7C0nOLWg225NrdhZHp1fJGxnkoGlNwGg1k0RZkHDOT52guv4NTIchzQwo4CwIwz4PNZAjGwH0fTjAFGUXIerE2qgS/H2MqBNnOzg3uuh95kgineRbCdQFjKDXkl890c18Xo9HcudWVX5iKB+7rV+s6TefUYGCgNLC5CXQ6pTeQvdgcgdcnMRUFR8OQMc44dAGQ22MFBfkgOC/3fj9eUZDeu9+myQjFGY9ABJ6ZobwwJqiRgcIRYX/2evvPCqwNHQKf8j2k29uQ/T4ZppjbbPH6+WnopA61vx9uXb7CLnY2jiuwZBY9kHZ7Zas09dq3StW/d7t6uPGJRoGXLYUlT4jkuAJ1MJzXo3RZDQ+MLbQ/vboiy3p8bdZPoujb6SAQcm9v3m21QCv+k/gB4NkSwe3cC88AAAAASUVORK5CYII=</content>
    <filesize>587</filesize>
  </attachment>
  <attachment>
    <filename>bullettick.png</filename>
    <mimetype>image/png</mimetype>
    <author>xwiki:XWiki.Admin</author>
    <version>1.1</version>
    <comment/>
    <content>iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAB3RJTUUH1woQCDUr2dijRwAAAAlwSFlzAAAewQAAHsEBw2lUUwAAAARnQU1BAACxjwv8YQUAAAEySURBVHjaY2AYBYzEKgxd4DPx+5/vNgz/GE9uSduTBRNnIlazuJBYnqWOuREnE2cmshwLsZoVZeUZ/gHh65dvGHAakLUr3ubf3/+Hv3/9XrAwZPVEmGZZaWmGX/9+M2xcv43h9rubKN5G8cKf339WyAlJM/z+9adXv1dVk12EKU1KSorhz78/YM233txgtO4ywe2CF9+fimpxajJoK2syC/EIXhMXFWP48/8Pw7aNuxhuv7/JaNFhxLiJ5+B/nLFgPl2HQZBH4L+jthPDv39/wc7evmkPw/WX1xg/zv6BNYxQXMArz8305PIzxo0fN/1X4ldhuHPzHsO151cYOSXZmT4y/PhHVDowWaTF9Orq63+SLDL/b7+9wfj7/2+mzzP/YNWMF/BmsHGIN/MRndAGDgAAvQx64I+NPuAAAAAASUVORK5CYII=</content>
    <filesize>419</filesize>
  </attachment>
  <object>
    <name>AppWithinMinutes.ClassEditSheet</name>
    <number>1</number>
    <className>XWiki.JavaScriptExtension</className>
    <guid>cb6bd1b4-599c-4fd6-9862-7969cf38b097</guid>
    <class>
      <name>XWiki.JavaScriptExtension</name>
      <customClass/>
      <customMapping/>
      <defaultViewSheet/>
      <defaultEditSheet/>
      <defaultWeb/>
      <nameField/>
      <validationScript/>
      <cache>
        <cache>0</cache>
        <defaultValue>long</defaultValue>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>cache</name>
        <number>5</number>
        <prettyName>Caching policy</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>long|short|default|forbid</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </cache>

        <contenttype>PureText</contenttype>
        <disabled>0</disabled>
        <editor>PureText</editor>
        <name>code</name>
        <number>2</number>
        <prettyName>Code</prettyName>
        <rows>20</rows>
        <size>50</size>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType>

      <name>
        <disabled>0</disabled>
        <name>name</name>
        <number>1</number>
        <prettyName>Name</prettyName>
        <size>30</size>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.StringClass</classType>
      </name>
      <parse>
        <disabled>0</disabled>
        <displayFormType>select</displayFormType>
        <displayType>yesno</displayType>
        <name>parse</name>
        <number>4</number>
        <prettyName>Parse content</prettyName>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.BooleanClass</classType>
      </parse>
      <use>
        <cache>0</cache>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>use</name>
        <number>3</number>
        <prettyName>Use this extension</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>currentPage|onDemand|always</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </use>
    </class>
    <property>
      <cache>long</cache>
    </property>
    <property>
      var XWiki = (function (XWiki) {
// Start XWiki augmentation.
/**
 * Displays the title (tool tip) inside the text input when there's no value and the text input is not focused.
 */
XWiki.InputWithTitle = Class.create({
  initialize : function(input) {
    this.input = input;
    this.input.observe('focus', this._onFocus.bindAsEventListener(this));
    this.input.observe('blur', this._onBlur.bind(this));

    // We have to listen to the save event because 'Save & Continue' doesn't trigger a form submit event.
    document.observe('xwiki:class:save', this._onSave.bind(this));

    this._onBlur();
  },
  _onFocus : function(event) {
    if (this.input.hasClassName('unset')) {
      this.input.value = '';
      this.input.removeClassName('unset');
    }
  },
  _onBlur : function() {
    if (this.input.value == '') {
      this.input.addClassName('unset');
      this.input.value = this.input.title;
    }
  },
  _onSave : function() {
    this._onFocus();
    this._onBlur.bind(this).defer();
  }
});

/**
 * A text input that is automatically resized to fit its value.
 */
XWiki.AutoResizeInput = Class.create({
  initialize : function(input) {
    this.input = input;
    var resizeEvents = ['blur', 'change', 'cut', 'keyup', 'paste'];
    for(var i = 0; i < resizeEvents.length; i++) {
      this.input.observe(resizeEvents[i], this._resize.bindAsEventListener(this));
    }
    this.input.observe('keypress', this._reserveSpace.bindAsEventListener(this));

    this.meter = new Element('pre', {'class': 'autoResize-meter inherit'});
    this.meter.appendChild(document.createTextNode(''));
    this.input.addClassName('inherit').insert({after: this.meter});

    this._resize();
  },
  _resize : function(event) {
    if (event) {
      this._resize.bind(this).defer();
    } else {
      // Make sure the meter text is not set to the empty string because otherwise the meter offset width is wrongly
      // computed in Chrome. See http://code.google.com/p/chromium/issues/detail?id=105573 .
      this.meter.firstChild.data = this.input.value || ' ';
      this.input.setStyle({width: (this.meter.offsetWidth + 6) + 'px'});
    }
  },
  _reserveSpace : function(event) {
    /* Ignore: Backspace, Delete, End, Home, Left, Up, Right, Down. */
    var ignored = [8, 46, 35, 36, 37, 38, 39, 40];
    for(var i = 0; i < ignored.length; i++) {
      if (event.keyCode == ignored[i]) {
        return;
      }
    }
    this.input.setStyle({width: (this.input.offsetWidth + 6) + 'px'});
  }
});

/**
 * A toggle button backed by a select with two options.
 */
XWiki.ToggleSelect = Class.create({
  initialize : function(select) {
    this.select = select;
    this.select.hide();

    this.off = select.options[0];
    this.on = select.options[1];

    this.switcher = new Element('span', {'class': this.select.className, title: this.select.title});
    this.switcher.removeClassName('toggle').addClassName('toggle-switcher');
    this.switcher.appendChild(document.createTextNode(''));
    this.switcher.observe('click', this._toggle.bind(this));
    this.select.insert({after: this.switcher});

    this._update();
  },
  _toggle : function() {
    if (this.on.selected) {
      this.off.selected = true;
    } else {
      this.on.selected = true;
    }
    this._update();
  },
  _update : function() {
    this.switcher.firstChild.nodeValue = this.select.options[this.select.selectedIndex].text;
  }
});

// Allow widgets to catch the save event before the form is submitted when the 'Save & Continue' button is clicked. We
// need this hack because the 'Save & Continue' handler from actionbuttons.js is added right after the page is loaded,
// before any other code can register its own listener.
document.observe('xwiki:actions:save', function(event) {
  document.fire('xwiki:class:save', {originalEvent: event});
}.bindAsEventListener(window));
// End XWiki augmentation.
return XWiki;
}(XWiki || {}));
    </property>
    <property>
      <name>Widgets</name>
    </property>
    <property>
      <parse>0</parse>
    </property>
    <property>
      <use>onDemand</use>
    </property>
  </object>
  <object>
    <name>AppWithinMinutes.ClassEditSheet</name>
    <number>2</number>
    <className>XWiki.JavaScriptExtension</className>
    <guid>9ecc7b32-69ee-4b95-8806-60885aaeb85b</guid>
    <class>
      <name>XWiki.JavaScriptExtension</name>
      <customClass/>
      <customMapping/>
      <defaultViewSheet/>
      <defaultEditSheet/>
      <defaultWeb/>
      <nameField/>
      <validationScript/>
      <cache>
        <cache>0</cache>
        <defaultValue>long</defaultValue>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>cache</name>
        <number>5</number>
        <prettyName>Caching policy</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>long|short|default|forbid</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </cache>

        <contenttype>PureText</contenttype>
        <disabled>0</disabled>
        <editor>PureText</editor>
        <name>code</name>
        <number>2</number>
        <prettyName>Code</prettyName>
        <rows>20</rows>
        <size>50</size>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType>

      <name>
        <disabled>0</disabled>
        <name>name</name>
        <number>1</number>
        <prettyName>Name</prettyName>
        <size>30</size>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.StringClass</classType>
      </name>
      <parse>
        <disabled>0</disabled>
        <displayFormType>select</displayFormType>
        <displayType>yesno</displayType>
        <name>parse</name>
        <number>4</number>
        <prettyName>Parse content</prettyName>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.BooleanClass</classType>
      </parse>
      <use>
        <cache>0</cache>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>use</name>
        <number>3</number>
        <prettyName>Use this extension</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>currentPage|onDemand|always</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </use>
    </class>
    <property>
      <cache>long</cache>
    </property>
    <property>
      var XWiki = (function (XWiki) {
// Start XWiki augmentation.
/**
 * Handles the form field display (through AJAX), configure (toggle) and delete.
 */
XWiki.FormField = Class.create({
  initialize : function(container) {
    this.container = container;
  },
  enhance : function(data) {
    this.container.insert({top: this._createToolBox()});
    if (data) {
      this.container.addClassName('loading');
      new Ajax.Request(data, {
        method: 'get',
        onSuccess: this._display.bind(this)
      });
    } else {
      this._display(null);
    }
  },
  getContainer : function() {
    return this.container;
  },
  getViewer : function() {
    return this.container.down('.field-viewer')
  },
  getConfig : function() {
    return this.container.down('.field-config');
  },
  getToolBox : function() {
    return this.container.down('.toolBox');
  },
  getName : function() {
    if (!this.container._typeInput) {
      // Field type should be stored in the first hidden input under the field container.
      this.container._typeInput = this.container.down('input[type=hidden]');
    }
    return this.container._typeInput.name.substring('type-'.length);
  },
  getPropertyId : function(propertyName) {
    return 'field-' + this.getName() + '_' + propertyName;
  },
  _display : function(response) {
    // If the field container is still attached, update its content.
    if (this.container.parentNode) {
      if (response) {
        this.container.removeClassName('loading');
        this._injectHTML(response.responseText);
      }

      this.getToolBox().insert({top: this._createConfigIcon()});
      this._onConfigure();

      document.fire('xwiki:class:displayField', {field: this, 'new': !!response});

      this._twoColumnConfigDisplay();
    }
  },
  _injectHTML : function(html) {
    // We don't use Element#update() because it doesn't move external scripts and sheets into HEAD and also because we
    // don't want to support in-line scripts in displayers.
    var container = new Element('div');
    container.innerHTML = html;
    var head = document.body.previous('head');
    container.select('link').each(function(link) {
      head.insert(link);
    });
    container.select('script').each(function(script) {
      if (script.src) {
        head.insert(new Element('script', {type: script.type, src: script.readAttribute('src')}));
      }
      script.remove();
    });
    this.container.insert(container);
  },
  _createToolBox : function() {
    var deleteIcon = new Element('img', {
      src: '$xwiki.getAttachmentURL('AppWithinMinutes.ClassEditSheet', 'bulletcross.png')',
      alt: '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorDeleteFieldLabel'))',
      title: '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorDeleteFieldLabel'))'
    });
    deleteIcon.observe('click', this._onDelete.bindAsEventListener(this));

    var toolBox = new Element('div', {'class': 'toolBox'});
    toolBox.insert(deleteIcon);
    return toolBox;
  },
  _createConfigIcon : function() {
    return new Element('img').observe('click', this._onConfigure.bindAsEventListener(this));
  },
  _onConfigure : function(event) {
    var icon = event ? event.element() : this.getToolBox().down('img');
    var fieldConfig = this.getConfig();
    if (fieldConfig.visible()) {
      fieldConfig.hide();
      icon.alt = icon.title = '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorConfigureFieldLabel'))';
      icon.src = '$xwiki.getSkinFile('icons/silk/bullet_wrench.png')';
      this._maybePreview();
    } else {
      fieldConfig.show();
      icon.alt = '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorPreviewFieldLabel'))';
      icon.title = '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorPreviewFieldHint'))';
      icon.src = '$xwiki.getAttachmentURL('AppWithinMinutes.ClassEditSheet', 'bullettick.png')';
      this._maybeSavePreviewData();
    }
  },
  _onDelete : function(event) {
    new XWiki.widgets.ConfirmationBox({
      onYes : this._onConfirmedDelete.bind(this)
    }, {
      confirmationText : '$services.localization.render('core.editors.class.deleteProperty.confirm')'
    });
  },
  _onConfirmedDelete : function() {
    this.container.remove();
    document.fire('xwiki:class:deleteField', {field: this});
  },
  _maybeSavePreviewData : function() {
    // Initialize the preview data.
    this.previewData || this._savePreviewData();
    // Make sure the preview data is updated when the field is renamed.
    if (!this._renameListener) {
      this._renameListener = function(event) {
        if (event.memo.field.getContainer() == this.container) {
          this._savePreviewData();
        }
      }.bindAsEventListener(this);
      document.observe('xwiki:class:renameField', this._renameListener);
    }
  },
  _savePreviewData : function() {
    this.previewData = this._asHash();
    // Ignore properties that don't affect the preview.
    ['name', 'prettyName', 'hint', 'required', 'number'].each(function(propertyName) {
      this.previewData.unset(this.getPropertyId(propertyName));
    }.bind(this));
    // Add parameters required by the AJAX preview request.
    this.previewData.set('preview', true);
    this.previewData.set('xpage', 'plain');
  },
  _maybePreview : function() {
    if(!this.previewData) {
      return;
    }
    // Check if there are configuration changes.
    var data = new Hash();
    var areEqual = function(alice, bob) {
      if (typeof alice == typeof bob) {
        if (alice.join) {
          return alice.join() == bob.join();
        }
        return alice == bob;
      }
      return false;
    }
    this._asHash().each(function(pair) {
      var oldValue = this.previewData.get(pair.key);
      if (oldValue != undefined && !areEqual(pair.value, oldValue)) {
        data.set(pair.key, pair.value);
      }
    }.bind(this));
    var modifiedKeys = data.keys();
    if (modifiedKeys.length > 1 || (modifiedKeys.length == 1 && !modifiedKeys[0].endsWith('_0_' + this.getName()))) {
      this._preview(this.previewData.clone().update(data));
    }
  },
  _preview : function(data) {
    var preview = this.getViewer().down('dd');
    if (preview) {
      preview.addClassName('loading');
      // Firefox 3.6 doesn't resolve the empty string to the current page URL so we have to explicitly specify it.
      new Ajax.Request(window.location.href, {
        parameters: data.toQueryString(),
        onSuccess: function(response) {
          preview.removeClassName('loading');
          preview.update(response.responseText);
          this.previewData = data;
          document.fire('xwiki:class:previewField', {field: this});
          document.fire('xwiki:dom:updated', {elements: [preview]});
        }.bind(this)
      });
    }
  },
  _asHash : function() {
    // Simulate a submit so that we collect the right values.
    document.fire('xwiki:class:save', {originalEvent: {memo: {}}});
    var inputs = this.container.select('input', 'textarea', 'select');
    return new Hash(Form.serializeElements(inputs, {hash: true}));
  },
  _twoColumnConfigDisplay : function() {
    var leftColumn = this.getConfig();
    var fieldConfig = new Element('div', {'class': leftColumn.className});
    leftColumn.insert({before: fieldConfig});
    fieldConfig.insert(leftColumn);
    fieldConfig.hide();
    leftColumn.className = 'leftColumn';
    leftColumn.show();
    var rightColumn = new Element('dl', {'class': 'rightColumn'});
    fieldConfig.insert(rightColumn);

    var visibleDTs = [];
    leftColumn.select('dt').each(function(dt) {
      if(dt.visible()) {
        visibleDTs.push(dt);
      }
    });

    var half = Math.ceil(visibleDTs.length / 2);
    for(var i = half; i < visibleDTs.length; i++) {
      var dt = visibleDTs[i];
      var dd = dt.next();
      rightColumn.insert(dt);
      if (dd && dd.tagName.toLowerCase() == 'dd') {
        rightColumn.insert(dd);
      }
    }

    fieldConfig.insert(new Element('div', {'style': 'clear: left'}));
  }
});

/**
 * Manages the form field names. Generates unique names for newly added fields and renames field configuration property ids/names when a field is renamed.
 */
XWiki.FormFieldNameManager = Class.create({
  initialize : function() {
    // Generate a unique field name when a new field is dragged from the palette to the form canvas.
    document.observe('xwiki:class:displayField', this._maybeGenerateFieldName.bind(this));
    // Rename the field configuration property ids/names when a field is renamed. This is needed for AJAX Save & Continue.
    document.observe('xwiki:document:saved', this._maybeRenameFields.bind(this));
  },
  _maybeGenerateFieldName : function(event) {
    if (!event.memo['new']) {
      return;
    }
    var field = event.memo.field;
    var nameInput = $(field.getPropertyId('name'));
    var counter = 1;
    // We check the type meta property because there can be unsupported field types for which we know only their type.
    while($('type-' + nameInput.value + counter++));
    nameInput.value = nameInput.value + (counter - 1);
    this._maybeRenameField(field);
  },
  _maybeRenameFields : function() {
    $('fields').childElements().each(function(item) {
      this._maybeRenameField(new XWiki.FormField(item));
    }.bind(this));
  },
  _maybeRenameField : function(field) {
    var oldName = field.getName();
    var newName = $(field.getPropertyId('name')).value;
    if (oldName != newName) {
      this._maybeRenameElement(oldName, newName, field.getContainer());
      field.getContainer().descendants().each(this._maybeRenameElement.bind(this, oldName, newName));
      document.fire('xwiki:class:renameField', {field: field});
    }
  },
  _maybeRenameElement : function(oldName, newName, element) {
    ['id', 'name', 'for'].each(function(attribute) {
      var value = element.readAttribute(attribute) || '';
      if (value.startsWith('xwiki-form-' + oldName)) {
        // This is for fields with multiple values (e.g. radio buttons).
        value = 'xwiki-form-' + newName + value.substring(11 + oldName.length);
      } else if (value.endsWith('_0_' + oldName)) {
        // This is for field default value.
        value = value.substring(0, value.length - oldName.length) + newName;
      } else {
        // This is for field meta properties.
        var nameIndex = value.indexOf('-') + 1;
        if (nameIndex > 0 && (value.substring(nameIndex) == oldName || value.substring(nameIndex).startsWith(oldName + '_'))) {
          value = value.substring(0, nameIndex) + newName + value.substring(nameIndex + oldName.length);
        }
      }
      if (value != '') {
        element.writeAttribute(attribute, value);
      }
    });
  }
});

/**
 * Enhances the way the field pretty name is edited. Allows users to edit the pretty name in-place inside the field preview.
 */
XWiki.FormFieldPrettyNameManager = Class.create({
  initialize : function() {
    document.observe('xwiki:class:displayField', this._onDisplayField.bindAsEventListener(this));
  },
  _onDisplayField : function(event) {
    var field = event.memo.field;
    var prettyNameInput = $(field.getPropertyId('prettyName'));
    // Hide the DD containing the pretty name input.
    var parentDD = prettyNameInput.up('dd');
    parentDD.hide();
    // Hide the DT containing the pretty name input label.
    var previousDT = parentDD.previous('dt');
    previousDT.hide();
    // Use the label text as tool tip for the pretty name input, if the title attribute is not specified.
    if (!prettyNameInput.title) {
      var label = previousDT.down('label');
      prettyNameInput.title = label.textContent || label.innerText;
    }
    // Replace the preview label with the pretty name input.
    var previewLabel = field.getViewer().down('label');
    previewLabel.writeAttribute('for', '');
    previewLabel.removeChild(previewLabel.lastChild);
    previewLabel.insert(prettyNameInput);
    // Enhance the pretty name input.
    new XWiki.InputWithTitle(prettyNameInput);
    new XWiki.AutoResizeInput(prettyNameInput);
  }
});

/**
 * Adds a tool tip to the default value input.
 */
XWiki.FormFieldDefaultValueManager = Class.create({
  initialize : function() {
    var enhanceFieldDefaultValue = this._enhanceFieldDefaultValue.bindAsEventListener(this);
    document.observe('xwiki:class:displayField', enhanceFieldDefaultValue);
    document.observe('xwiki:class:previewField', enhanceFieldDefaultValue);

    // We need to listen to save event to rename the default value inputs to match the new field name.
    document.observe('xwiki:class:save', this._onSave.bind(this));
  },
  _enhanceFieldDefaultValue : function(event) {
    var field = event.memo.field;
    var fieldName = field.getName();
    field.getViewer().select('input[type=text]', 'textarea').each(function(input) {
      if (!input.title && input.name.endsWith('_0_' + fieldName)) {
        input.title = '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorFieldDefaultValueToolTip'))';
      }
    });
  },
  _onSave : function() {
    $('fields').childElements().each(function(item) {
      this._maybeRenameDefaultValue(new XWiki.FormField(item));
    }.bind(this));
  },
  _maybeRenameDefaultValue : function(field) {
    var oldName = field.getName();
    var newName = $(field.getPropertyId('name')).value;
    if(oldName != newName) {
      field.getViewer().select('input', 'textarea', 'select').each(function(element) {
        if (element.name.endsWith('_0_' + oldName)) {
          element.name = element.name.substring(0, element.name.length - oldName.length) + newName;
        }
      });
    }
  }
});

/**
 * Makes the hint editable in place.
 */
XWiki.FormFieldHintManager = Class.create({
  initialize : function() {
    document.observe('xwiki:class:displayField', this._onDisplayField.bindAsEventListener(this));
  },
  _onDisplayField : function(event) {
    var field = event.memo.field;
    var hintInput = $(field.getPropertyId('hint'));
    // TODO: Remove this test after the 'hint' meta property is added to the platform.
    if (hintInput) {
      hintInput.addClassName('xHint');
      if (!hintInput.title) {
        hintInput.title = 'Hint';
      }
      // Move the hint input below the pretty name input, in the field viewer.
      var dd = hintInput.up('dd');
      var dt = dd.previous('dt');
      field.getViewer().down('label').insert({after: hintInput});
      dt.remove();
      dd.remove();
      // Enhance the hint input.
      new XWiki.InputWithTitle(hintInput);
      new XWiki.AutoResizeInput(hintInput);
    }
  }
});

/**
 * Replaces the input of the "required" field property with a toggle button.
 */
XWiki.FormFieldRequiredManager = Class.create({
  initialize : function() {
    document.observe('xwiki:class:displayField', this._onDisplayField.bindAsEventListener(this));
  },
  _onDisplayField : function(event) {
    var field = event.memo.field;
    var requiredInput = $(field.getPropertyId('required'));
    // TODO: Remove this test after the 'required' meta property is added to the platform.
    if (requiredInput) {
      var prettyNameLabel = field.getViewer().down('label');
      var wrapper = new Element('div', {'class': 'labelLine'});
      prettyNameLabel.insert({before: wrapper});
      wrapper.insert(prettyNameLabel);
      wrapper.insert('<select class="xRequired" title="Click to toggle between (Optional) and (Required)"><option>(Optional)</option><option>(Required)</option></select>');
      new XWiki.ToggleSelect(prettyNameLabel.next().down('select'));
    }
  }
});

/**
 * Manages the sortable list of form fields. Accepts fields dragged from the field palette.
 */
XWiki.FormCanvas = Class.create({
  initialize : function(container) {
    this.container = $(container);
    Droppables.add(this.container, {accept: 'field', hoverclass: 'hovered', onDrop: this._onDrop.bind(this)});

    document.observe('xwiki:class:deleteField', this._onDeleteField.bind(this));
    document.observe('xwiki:class:displayField', this._onDisplayField.bindAsEventListener(this));

    this.fields = this.container.down('ul');
    if (!this.fields) {
      this.fields = new Element('ul');
      this.container.insert(this.fields);
    }
    this.fields.id = 'fields';
    if (this.fields.empty()) {
      this.container.addClassName('empty');
    } else {
      this._enhanceFields();
    }
  },
  _enhanceFields : function() {
    this.fields.childElements().each(function(item) {
      new XWiki.FormField(item).enhance();
    });
  },
  _onDrop : function(field) {
    var fieldContainer = new Element('li', {'data-new': 'true'});
    this.fields.insert(fieldContainer);
    this.container.removeClassName('empty');
    new XWiki.FormField(fieldContainer).enhance(field.down('.data').value);
  },
  _onDeleteField : function() {
    if (this.fields.empty()) {
      this.container.addClassName('empty');
    } else {
      this._updateOrder();
    }
  },
  _onDisplayField : function(event) {
    var field = event.memo.field;

    // We have to set the ID of the field container as otherwise Sortable.onUpdate() isn't triggered.
    field.getContainer().id = 'field-' + field.getName();

    // Hide the property number, as ordering can be done by drag and drop
    var numberInput = $(field.getPropertyId('number'));
    numberInput.up().hide();
    numberInput.up().previous().hide();

    // Create and insert the move icon.
    var moveIcon = new Element('img', {
      src: '$xwiki.getAttachmentURL('AppWithinMinutes.ClassEditSheet', 'arrow-move.png')',
      alt: '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorMoveFieldLabel'))',
      title: '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorMoveFieldHint'))',
      'class': 'icon-move'
    });
    field.getToolBox().insert({bottom: moveIcon});

    Sortable.create('fields', {
      handle: 'icon-move',
      format: /^[^_\-](?:[A-Za-z0-9\-\_]*)[\-](.*)$/,
      onUpdate : this._updateOrder.bind(this)
    });

    if (event.memo['new']) {
      this._updateOrder();
    }
  },
  _updateOrder : function() {
    var items = this.fields.childElements();
    for(var i = 0; i < items.length; i++) {
      var field = new XWiki.FormField(items[i]);
      if (field.getConfig()) {
        $(field.getPropertyId('number')).value = i + 1;
      } else {
        // Stop updating the order. This method will be called again when the field will be loaded.
        break;
      }
    }
  }
});

/**
 * Manages the palette of form fields.
 */
XWiki.FormFieldPalette = Class.create({
  initialize : function(container) {
    $(container).select('.field').each(function(field) {
      new Draggable(field, {
        revert: true,
        reverteffect: function(field) {
          // Reset the in-line style.
          field.setStyle({
            height: '',
            left: '',
            position: '',
            top: '',
            zIndex: '',
            width: ''
          });
        },
        ghosting: true
      });
    });
  }
});
// End XWiki augmentation.
return XWiki;
}(XWiki || {}));

(function() {
  function initForm(form) {
    // Let the sheet handle the form submit. The form is submitted by default to the preview action which dispatches the
    // request to the save action if the save button is detected on the request parameters. By submitting to the edit
    // action the edit sheet is evaluated and thus it can handle the save by itself.
    form.action = XWiki.currentDocument.getURL('edit');

    // Apply the vertical form layout standard.
    form.addClassName('xform');

    // Remove the preview button.
    form.down("input[name='action_preview']").remove();

    // Rename the save buttons.
    form.down("input[name='action_save']").name = 'xaction_save';
    form.down("input[name='action_saveandcontinue']").name = 'xaction_saveandcontinue';

    // Make sure the 'Save & Continue' button is submitted when clicked.
    document.observe('xwiki:class:save', function(event) {
      event = event.memo.originalEvent;
      if (event.memo['continue']) {
        var submitButton = event.memo.originalEvent.element();
        submitButton.insert({after: new Element('input', {type: 'hidden', 'name': submitButton.name})});
        (function () {
          submitButton.next().remove();
        }).defer();
      }
    }.bindAsEventListener(window));

    // Be prepared to enhance some of the field properties.
    new XWiki.FormFieldNameManager();
    new XWiki.FormFieldPrettyNameManager();
    new XWiki.FormFieldHintManager();
    new XWiki.FormFieldDefaultValueManager();
    new XWiki.FormFieldRequiredManager();

    // Make the palette and the canvas live.
    new XWiki.FormFieldPalette('palette');
    new XWiki.FormCanvas('canvas');
  }

  function init() {
    var form = $('inline');
    if (form) {
      require(['scriptaculous/dragdrop'], function() {
        initForm(form);
      });
    }
    return form;
  }

  (XWiki.domIsLoaded && init()) || document.observe('xwiki:dom:loaded', init);
}).call();

require(['jquery', 'xwiki-events-bridge'], function($) {
  $(document).on('xwiki:class:displayField xwiki:class:previewField', function(event, data) {
    var container = $(data.field.getContainer());
    if (container.attr('data-new') === 'true') {
      // We can't suggest property values for properties that don't exist yet (have not been saved) so we provide
      // suggestions for the template property that was used to create them. Note that the suggested values depend on
      // the saved property meta data so changing the property (field) meta data may not affect the suggestions until
      // those changes are saved.
      var templateHiddenInput = container.find('#' + 'template-' + data.field.getName());
      var propertyValueSuggester = container.find('.field-viewer .suggest-propertyValues').first();
      propertyValueSuggester.attr({
        'data-className': templateHiddenInput.val(),
        'data-propertyName': templateHiddenInput.attr('data-propertyName')
      });
    }
  }).on('xwiki:document:saved', function(event) {
    // We need to update the property value suggesters because:
    // * newly saved properties should have their own suggestions instead of relying on the template property
    // * for renamed properties we need to fetch the suggestions from a different location
    $('ul#fields > li').each(function() {
      var container = $(this);
      container.removeAttr('data-new');
      var propertyValueSuggester = container.find('.field-viewer .suggest-propertyValues').first();
      if (propertyValueSuggester.length > 0) {
        // We need to preserve the selected values because they are lost when the suggester is destroyed.
        var selectedValues = propertyValueSuggester.children();
        propertyValueSuggester[0].selectize.destroy();
        // Restore the selected values.
        propertyValueSuggester.empty().append(selectedValues);
        var className = XWiki.Model.serialize(XWiki.currentDocument.documentReference.relativeTo(
          new XWiki.WikiReference(XWiki.currentWiki)));
        propertyValueSuggester.attr({
          'data-className': className,
          'data-propertyName': propertyValueSuggester.attr('name').substr((className + '_0_').length)
        });
        propertyValueSuggester.suggestPropertyValues();
      }
    });
  });
});
    </property>
    <property>
      <name>Framework</name>
    </property>
    <property>
      <parse>1</parse>
    </property>
    <property>
      <use>onDemand</use>
    </property>
  </object>
  <object>
    <name>AppWithinMinutes.ClassEditSheet</name>
    <number>0</number>
    <className>XWiki.RequiredRightClass</className>
    <guid>2a2d7281-2e73-4ed6-a6b4-863b8ae360e3</guid>
    <class>
      <name>XWiki.RequiredRightClass</name>
      <customClass/>
      <customMapping/>
      <defaultViewSheet/>
      <defaultEditSheet/>
      <defaultWeb/>
      <nameField/>
      <validationScript/>
      <level>
        <cache>0</cache>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <multiSelect>0</multiSelect>
        <name>level</name>
        <number>1</number>
        <picker>0</picker>
        <prettyName>level</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators> ,|</separators>
        <size>1</size>
        <sort>none</sort>
        <unmodifiable>0</unmodifiable>
        <validationMessage/>
        <validationRegExp/>
        <values>edit|programming</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </level>
    </class>
    <property>
      <level>programming</level>
    </property>
  </object>
  <object>
    <name>AppWithinMinutes.ClassEditSheet</name>
    <number>0</number>
    <className>XWiki.SheetDescriptorClass</className>
    <guid>c25fb06c-956e-4cc3-a891-63bef91b2c21</guid>
    <class>
      <name>XWiki.SheetDescriptorClass</name>
      <customClass/>
      <customMapping/>
      <defaultViewSheet/>
      <defaultEditSheet/>
      <defaultWeb/>
      <nameField/>
      <validationScript/>
      <action>
        <customDisplay/>
        <disabled>0</disabled>
        <name>action</name>
        <number>1</number>
        <picker>0</picker>
        <prettyName>Action</prettyName>
        <size>30</size>
        <unmodifiable>0</unmodifiable>
        <validationMessage/>
        <validationRegExp/>
        <classType>com.xpn.xwiki.objects.classes.StringClass</classType>
      </action>
    </class>
    <property>
      <action>edit</action>
    </property>
  </object>
  <object>
    <name>AppWithinMinutes.ClassEditSheet</name>
    <number>0</number>
    <className>XWiki.StyleSheetExtension</className>
    <guid>ca2012cf-0113-4cc4-a140-98582ca729a1</guid>
    <class>
      <name>XWiki.StyleSheetExtension</name>
      <customClass/>
      <customMapping/>
      <defaultViewSheet/>
      <defaultEditSheet/>
      <defaultWeb/>
      <nameField/>
      <validationScript/>
      <cache>
        <cache>0</cache>
        <defaultValue>long</defaultValue>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>cache</name>
        <number>5</number>
        <prettyName>Caching policy</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>long|short|default|forbid</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </cache>

        <contenttype>PureText</contenttype>
        <disabled>0</disabled>
        <editor>PureText</editor>
        <name>code</name>
        <number>2</number>
        <prettyName>Code</prettyName>
        <rows>20</rows>
        <size>50</size>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType>

      <contentType>
        <cache>0</cache>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>contentType</name>
        <number>6</number>
        <prettyName>Content Type</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>CSS|LESS</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </contentType>
      <name>
        <disabled>0</disabled>
        <name>name</name>
        <number>1</number>
        <prettyName>Name</prettyName>
        <size>30</size>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.StringClass</classType>
      </name>
      <parse>
        <disabled>0</disabled>
        <displayFormType>select</displayFormType>
        <displayType>yesno</displayType>
        <name>parse</name>
        <number>4</number>
        <prettyName>Parse content</prettyName>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.BooleanClass</classType>
      </parse>
      <use>
        <cache>0</cache>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>use</name>
        <number>3</number>
        <prettyName>Use this extension</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>currentPage|onDemand|always</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </use>
    </class>
    <property>
      <cache>long</cache>
    </property>
    <property>
      #template('colorThemeInit.vm')
#canvas, #options {
  border: 1px solid $theme.pageContentBackgroundColor;
  margin: 0.5em 16em 0.5em 0;
  padding: 0.5em;
}

#canvas.empty, #canvas.hovered {
  border: 1px dashed $theme.borderColor;
}

#canvas .hint {
  background: url("$xwiki.getSkinFile('icons/silk/application_form_add.png')") no-repeat scroll left center transparent;
  color: $theme.textSecondaryColor;
  display: none;
  font-style: italic;
  margin: 0.5em;
  padding-left: 24px;
}

#canvas.empty .hint {
  display: block;
}

#fields input:focus, #fields textarea:focus {
  background-color: $theme.highlightColor;
}

#fields {
  margin: 0;
  padding: 0;
}

#fields > li {
  border: 1px dotted $theme.pageContentBackgroundColor;
  list-style: none;
  /* Make sure the loading animation is properly displayed. */
  min-height: 18px;
  padding: 0 0.5em 0.5em;
}

#fields > li .toolBox {
  display: none;
  position: absolute;
  right: 0.5em;
  top: 0.5em;
  z-index: 1000;
}

#fields .icon-move {
  cursor: move;
  margin: 2px;
}

#fields > li:hover {
  border: 1px dotted $theme.borderColor;
}

#fields > li:hover .toolBox {
  display: block;
}

#fields .field-config {
  background-color: $theme.backgroundSecondaryColor;
  margin-top: 1em;
  padding: 0 0.5em 0.5em;
}

#fields .fullScreenEditLinkContainer {
  display: none;
}

#fields .field-config .leftColumn, #fields .field-config .rightColumn {
  float: left;
  width: 49%;
}

#fields .field-config .leftColumn {
  margin-right: 2%;
}

#fields .field-config .readOnly {
  background-color: $theme.backgroundSecondaryColor;
}

#options {
  background-color: $theme.backgroundSecondaryColor;
  padding-left: 1em;
  width: 33%;
}

#options .warningmessage {
  border: 0 none;
  font-size: 0.8em;
}

#palette {
  background-color: $theme.backgroundSecondaryColor;
  float: right;
  max-width: 13.5em;
  padding: 0.5em 1em 1em;
}

#palette ul {
  margin: 0;
  padding: 0;
}

#palette li {
  list-style: none;
}

#palette .category {
  border-bottom: 1px dotted $theme.borderColor;
  color: $theme.linkColor;
  display: block;
  margin-bottom: 0.5em;
  margin-top: 0.7em;
}

#palette .field {
  cursor: move;
  display: block;
  font-size: smaller;
  padding: .3em;
}

#palette .field:hover {
  background-color: $theme.highlightColor !important;
}

.xform .wizard-footer {
  margin: 1em 16em 0 0;
}
    </property>
    <property>
      <contentType/>
    </property>
    <property>
      <name>Framework</name>
    </property>
    <property>
      <parse>1</parse>
    </property>
    <property>
      <use>onDemand</use>
    </property>
  </object>
  <object>
    <name>AppWithinMinutes.ClassEditSheet</name>
    <number>1</number>
    <className>XWiki.StyleSheetExtension</className>
    <guid>25c841f5-068e-4fdc-9599-6b8f63353958</guid>
    <class>
      <name>XWiki.StyleSheetExtension</name>
      <customClass/>
      <customMapping/>
      <defaultViewSheet/>
      <defaultEditSheet/>
      <defaultWeb/>
      <nameField/>
      <validationScript/>
      <cache>
        <cache>0</cache>
        <defaultValue>long</defaultValue>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>cache</name>
        <number>5</number>
        <prettyName>Caching policy</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>long|short|default|forbid</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </cache>

        <contenttype>PureText</contenttype>
        <disabled>0</disabled>
        <editor>PureText</editor>
        <name>code</name>
        <number>2</number>
        <prettyName>Code</prettyName>
        <rows>20</rows>
        <size>50</size>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.TextAreaClass</classType>

      <contentType>
        <cache>0</cache>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>contentType</name>
        <number>6</number>
        <prettyName>Content Type</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>CSS|LESS</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </contentType>
      <name>
        <disabled>0</disabled>
        <name>name</name>
        <number>1</number>
        <prettyName>Name</prettyName>
        <size>30</size>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.StringClass</classType>
      </name>
      <parse>
        <disabled>0</disabled>
        <displayFormType>select</displayFormType>
        <displayType>yesno</displayType>
        <name>parse</name>
        <number>4</number>
        <prettyName>Parse content</prettyName>
        <unmodifiable>0</unmodifiable>
        <classType>com.xpn.xwiki.objects.classes.BooleanClass</classType>
      </parse>
      <use>
        <cache>0</cache>
        <disabled>0</disabled>
        <displayType>select</displayType>
        <freeText>forbidden</freeText>
        <largeStorage>0</largeStorage>
        <multiSelect>0</multiSelect>
        <name>use</name>
        <number>3</number>
        <prettyName>Use this extension</prettyName>
        <relationalStorage>0</relationalStorage>
        <separator> </separator>
        <separators>|, </separators>
        <size>1</size>
        <unmodifiable>0</unmodifiable>
        <values>currentPage|onDemand|always</values>
        <classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
      </use>
    </class>
    <property>
      <cache>long</cache>
    </property>
    <property>
      #options dt, #fields dt {
  margin-top: 0;
  padding-top: 0.5em;
}

#fields .field-config label {
  color: $theme.textSecondaryColor;
}

#fields .unset {
  color: $theme.textSecondaryColor !important;
}

#fields .autoResize-meter {
  left: -9999px;
  position: absolute;
  top: 0;
  visibility; hidden;
}

#fields .inherit {
  background-image: none;
  border: 0 none;
  box-shadow: none;
  color: inherit;
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  padding: 0;
  text-align: inherit;
  text-transform: inherit;
  word-spacing: inherit;
}

#fields input.inherit:hover, #fields input.xHint:hover {
  background-color: $theme.highlightColor;
}

#fields input.xHint {
  color: $theme.textSecondaryColor;
  font-size: smaller;
  font-weight: normal;
}

#fields .labelLine label {
  display: inline-block;
}

#fields .toggle-switcher {
  cursor: pointer;
}
    </property>
    <property>
      <contentType/>
    </property>
    <property>
      <name>Widgets</name>
    </property>
    <property>
      <parse>1</parse>
    </property>
    <property>
      <use>onDemand</use>
    </property>
  </object>
</xwikidoc>

Output:
【漏洞类型】跨站脚本（XSS）

【严重性】高

【漏洞摘要】这是一个跨站脚本（XSS）漏洞。在`ClassEditSheet.xml`文件中，未对用户输入进行适当的HTML转义，导致攻击者可以通过注入恶意脚本在页面上执行任意代码。具体表现为未对`$formFieldIconURL`和`$fieldURL`进行HTML转义处理。攻击者可以利用这些未转义的用户输入构造恶意的URL，从而在页面上注入和执行恶意脚本。

【关键代码模式】
- 未转义的用户输入：`$formFieldIconURL`、`$fieldURL`
- 关键函数：`$escapetool.xml()`
- 关键关键词：`velocity`、`groovy`、`xwiki.getURL()`、`xwiki.getDocument()`

【修复方法说明】修复方法通过使用`$escapetool.xml()`对`$formFieldIconURL`和`$fieldURL`进行HTML转义处理，从而防止恶意脚本的注入和执行。此修复方法遵循了最佳实践之一，即对所有用户输入进行适当的HTML转义，以防止XSS攻击。