%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/waritko/jetty-distribution-9.4.21.v20190926/webapps/ROOT/templates/
Upload File :
Create Path :
Current File : //home/waritko/jetty-distribution-9.4.21.v20190926/webapps/ROOT/templates/extension.vm

## Make sure the browser won't keep the same version of the resource in cache from one version of XWiki to another
#set($environmentVersion = $services.extension.core.repository.environmentExtension.id.version)
#set ($discard = $xwiki.ssfx.use('uicomponents/extension/extension.css', {'forceSkinAction': true, 'version': $environmentVersion}))
#set ($discard = $xwiki.jsfx.use('uicomponents/extension/extension.js', {'forceSkinAction': true, 'version': $environmentVersion}))
#set ($discard = $xwiki.ssfx.use('uicomponents/viewers/diff.css', {'forceSkinAction': true, 'version': $environmentVersion}))
#set ($discard = $xwiki.jsfx.use('uicomponents/viewers/diff.js', {'forceSkinAction': true, 'version': $environmentVersion}))
#set ($discard = $xwiki.ssfx.use('uicomponents/widgets/buttonGroup.css', {'forceSkinAction': true, 'version': $environmentVersion}))
#set ($discard = $xwiki.jsfx.use('uicomponents/widgets/buttonGroup.js', {'forceSkinAction': true, 'version': $environmentVersion}))

#set($discard = $services.template.execute('job_macros.vm'))
#set($discard = $services.template.execute('rating_macros.vm'))

## Various configuration of extension.vm behavior
## * skipCheckRight: skip right validation when executing a action (install plan, install, etc.)
## * skipCurrentUser: don't take into account context use (XAR extension documents will keep their author for example)
## * installJAROnRoot: force installing JAR extensions on root namespaces
#if (!$extensionConfig)
  #set ($extensionConfig = {})
#end

#set ($extensionManager = $services.extension)
## From the main wiki we can manage other namespaces.
#if ($xcontext.isMainWiki() && "$!request.extensionNamespace" != '')
  #set ($extensionNamespace = $request.extensionNamespace)
#else
  #set ($extensionNamespace = "wiki:$xcontext.database")
#end
#set ($isAjaxRequest = $request.getHeader('X-Requested-With') == 'XMLHttpRequest')

#macro (displayExtensionSearchBar)
  <div class="extension-search-bar">
    ## Simple search form.
    <form action="$xwiki.relativeRequestURL" id="extension-search-simple" class="form-inline">
      <div>
        #if ($request.section)
          <input type="hidden" name="section" value="$escapetool.xml($request.section)" />
        #end
        <label class="hidden" for="extensionSearchInput">$services.localization.render('extensions.search.tip')</label>
        <input type="text" id="extensionSearchInput" name="search"
          #if ("$!request.search" != '') value="$escapetool.xml($request.search)"#{end}
          placeholder="$services.localization.render('extensions.search.tip')"/>
        <label class="hidden" for="extensionSearchRepositoryList">$services.localization.render('extensions.search.repository.label')</label>
        #set ($selectedRepositoryId = 'recommended')
        #if ($request.repo)
          #set ($selectedRepositoryId = $request.repo)
        #end
        <select id="extensionSearchRepositoryList" name="repo">
          <optgroup label="$services.localization.render('extension.search.repositoryGroup.remote.label')">
            ## Remote extensions repository
            <option value=""#if ($selectedRepositoryId == '') selected="selected"#end>
              $services.localization.render('extensions.search.repository.remote.label')
            </option>
            ## Recommended extensions repository (default)
            <option value="recommended"#if ($selectedRepositoryId == 'recommended') selected="selected"#end>
              $services.localization.render("extensions.search.repository.recommended.label")
            </option>
          </optgroup>
          <optgroup label="$services.localization.render('extension.search.repositoryGroup.local.label')">
            ## Local repositories
            #foreach ($repositoryId in ['installed', 'local', 'core'])
              <option value="$repositoryId"#if ($selectedRepositoryId == $repositoryId) selected="selected"#end>
                $services.localization.render("extensions.search.repository.${repositoryId}.label")</option>
            #end
          </optgroup>
        </select>
        ## We don't use #em_submitButton because we want to use <button> instead of <input type="submit">, in order to 
        ## be able to use HTML content inside the text of the button (the search icon and the hidden span for screen readers).
        <span class="buttonwrapper">
          <button class="primary" type="submit">$services.icon.renderHTML('search') <span class="hidden">$escapetool.xml($services.localization.render('extensions.advancedSearch.actions.submit'))</span></button>
        </span>
      </div>
    </form>
    ## Advanced search form.
    <form action="$xwiki.relativeRequestURL" class="xform">
      <fieldset id="extension-search-advanced">
        <legend><a href="#extension-search-advanced-body">$services.localization.render('extensions.advancedSearch.title')</a></legend>
        <div id="extension-search-advanced-body"></div>
        <div class="plainmessage extension-search-advanced-popup hidden">
          #if ($request.section)
            <input type="hidden" name="section" value="$escapetool.xml($request.section)" />
          #end
          <dl>
            <dt><label for="advancedExtensionSearch-id">$services.localization.render('extensions.advancedSearch.id.label')</label></dt>
            <dd><input type="text" name="extensionId" id="advancedExtensionSearch-id" value="" /></dd>
            <dt><label for="advancedExtensionSearch-version">$services.localization.render('extensions.advancedSearch.version.label')</label></dt>
            <dd><input type="text" name="extensionVersion" id="advancedExtensionSearch-version" value="" /></dd>
          </dl>
          <p>
            #em_submitButton('extensions.advancedSearch.actions.submit')
            #em_linkButton('#extension-search-simple' 'extensions.advancedSearch.actions.cancel' 'actionCancel')
          </p>
        </div>
      </fieldset>
    </form>
    <div class="clearfloats"></div>
  </div>
#end

#macro (displayExtensionName $extension)
  #set ($name = "$!{extension.name}")
  #if ($name == '')
    #set ($name = "$!{extension.id.id}")
    #if ($name.indexOf(':') >= 0)
      #set ($name = $name.substring($mathtool.add($name.indexOf(':'), 1)))
    #end
  #end
  ${name}##
#end

#macro (displayExtensionActionButtons $extension $readOnly)
  <div class="extension-actions">
    #displayExtensionActionButtons_detailsToggle($extension)
    #if (!$readOnly)
      ## Group the buttons that trigger an extension job.
      <span class="dynamic-button-group">
        #displayExtensionActionButtons_jobTriggers($extension)
      </span>
    #end
  </div>
#end

#macro (displayExtensionActionButtons_detailsToggle $extension)
  #computeXBack()
  #if ($showExtensionDetails)
    #if ($isAjaxRequest)
      ## AJAX request to show extension details.
      #extensionActionButton('showDetails' true 'visibilityAction')
      #extensionActionButton('hideDetails' true 'visibilityAction')
    #else
      ## The given extension is displayed alone.
      #em_linkButton($xback 'extensions.actions.back' 'extension-link')
    #end
  #else
    ## The given extension is displayed in a list of extensions.
    #extensionActionButton('showDetails' true)
  #end
  ## Make sure the URL to get back is preserved when we submit an extension action.
  <input type="hidden" name="xback" value="$escapetool.xml($xback)" />
#end

#macro (displayExtensionActionButtons_jobTriggers $extension)
  #if(!$extensionStatus)
    #determineExtensionStatus($extension $extensionStatus $extensionStatusMessage)
  #end
  ## Determine if there is an extension job waiting to be resumed or a previously created job plan that can be executed.
  #set ($showContinueButton = $jobState == 'WAITING')
  #if (!$showContinueButton)
    #isExtensionPlan($jobStatus $showContinueButton)
  #end
  #if ($showContinueButton)
    ## One of the following statements is true:
    ## * the current extension job is waiting for user input and the user needs a button to resume the job,
    ## * an extension job plan was previously computed and the user needs a button to execute the plan.
    #extensionActionButton('continue')
    <input name="form_token" value="$!services.csrf.getToken()" type="hidden" />
    #if ($jobState == 'WAITING')
      ## Only the Continue button should be available when a job is waiting.
      #break
    #end
  #end
  ## Note that the Continue button doesn't exclude the following buttons, unless a job is waiting. The user should be
  ## able for instance to recompute the install plan.
  #if ($extensionStatus.startsWith('installed'))
    ## The given extension object could be an instance of LocalExtension so make sure we use an InstalledExtension instance.
    #getInstalledExtension($extension $extensionNamespace $installedExtension)
    ## If the installed extension might be invalid and need repairing
    #extensionRepairButtons($installedExtension)
    ## This extension can be uninstalled
    #extensionUninstallButtons($installedExtension)
    ## XAR specific buttons
    #extensionActionXARButtons($installedExtension)
    ## If the extension is not installed on farm propose it
    #if (!$installedExtension.isInstalled($NULL))
      #extensionActionGlobalButton('install' false)
    #end
  #elseif ($extensionStatus == 'remote')
    ## Installable extension.
    #if ($xcontext.action == 'distribution' && $showRepairXARButton)
      ## NOTE: This code is normally reached only when JavaScript is disabled since otherwise the button is added from JavaScript.
      ## TODO: Find a better way to 'force' the repair XAR extension button.
      #set ($showRepairXARButton = $NULL)
      #extensionActionButton('repairXAR')
      ## The repair job is executed without confirmation (i.e. without a plan).
      <input name="form_token" value="$!services.csrf.getToken()" type="hidden" />
    #else
      #extensionInstallButtons($extension)
    #end
  #elseif ($extensionStatus.startsWith('remote-installed'))
    ## An extension that can be either upgraded or downgraded.
    ## Compare this version with the version that is currently installed to determine which button to display.
    #getInstalledExtension($extension $extensionNamespace $installedExtension)
    #if ($extension.compareTo($installedExtension) > 0)
      #extensionUpgradeButtons($installedExtension 'upgrade')
    #else
      #extensionUpgradeButtons($installedExtension 'downgrade' true)
    #end
    ## If the extension is not installed on farm propose it
    #if (!$installedExtension.isInstalled($NULL))
      #extensionActionGlobalButton('install' false)
    #end
  #end
#end

#macro(extensionInstallButtons $extension)
  #set($isGlobalActionSecondary = false)
  #if ($services.extension.isAllowed($extension, $extensionNamespace))
    #extensionActionButton('install')
    #set($isGlobalActionSecondary = true)
  #end
  #if ($services.extension.isAllowed($extension, $NULL))
    #extensionActionGlobalButton('install' $isGlobalActionSecondary)
  #end
#end

#macro(extensionUpgradeButtons $installedExtension $displayHint $secondary)
  #if ($installedExtension.isInstalled($NULL))
    ## It's installed on root, lets upgrade on root
    #extensionActionGlobalButtonWithDisplayHint('install' $displayHint $secondary)
    ## Also propose to install it on farm
    #extensionActionButton('install' true)
  #elseif ($installedExtension.isInstalled($extensionNamespace))
    ## It's installed on provided namespace, lets upgrade on provided namespace
    #extensionActionButtonWithDisplayHint('install' $displayHint $secondary)
  #end
#end

#macro(extensionRepairButtons $installedExtension)
  #if ($installedExtension.isInstalled($NULL))
    #if (!$installedExtension.isValid($NULL))
      ## It's invalid on root namespace
      #extensionActionGlobalButtonWithDisplayHint('install' 'repair')
    #end
  #elseif ($installedExtension.isInstalled($extensionNamespace)
           && !$installedExtension.isValid($extensionNamespace))
    ## It's invalid on provided namespace
    #extensionActionButtonWithDisplayHint('install' 'repair')
  #end
#end

#macro(extensionUninstallButtons $installedExtension)
  #if (!$installedExtension.isInstalled($NULL) && $installedExtension.isInstalled($extensionNamespace))
    ## It's installed on provided namespace
    #extensionActionButton('uninstall' true)
  #end
  ## It might be installed somewhere else, propose to remove it from the whole farm
  #extensionActionGlobalButton('uninstall' true)
#end

#macro(extensionActionXARButtons $installedExtension)
  #if ($installedExtension.type == 'xar' && $installedExtension.isInstalled($extensionNamespace))
    ## Add the button that can be used to compute the differences between the documents from the XAR and the documents
    ## from the database.
    #extensionActionButton('diffXAR' true)
  #end
#end

#macro (displayExtensionRating $extension)
  #set ($rating = $extension.getRating())
  #if ($rating != $NULL)
    #set($id = $xwiki.getUniquePageName("string").replaceAll("[0-9]*", "").toLowerCase())
    <div class="extension-rating">
      #ratingstars($id "" "" $rating.averageVote $rating.totalVotes true)
    </div>
  #end
#end

#macro (displayExtensionAuthors $extension)
  #set ($authors = [])
  #foreach ($author in $extension.authors)
    #if ($author.name == 'devs')
      #set ($discard = $authors.add("<a class=""extension-author"" href=""http://www.xwiki.org/"">$services.localization.render('extensions.info.authors.xwikiorg')</a>"))
    #elseif ("$!author.url" != '' && "$!author.name" != '')
      #set ($discard = $authors.add("<a class=""extension-author"" href=""$author.url"">$escapetool.xml($author.name)</a>"))
    #else
      #set ($discard = $authors.add("<span class=""extension-author"">$escapetool.xml($author.name)</span>"))
    #end
  #end
  #if (!$authors.isEmpty())
    <p class="extension-authors">$services.localization.render('extensions.info.authors') $stringtool.join($authors, ', ')</p>
  #end
#end

#macro (displayProgressBar $extension)
  #if (!$jobStatus || !$jobStatus.request.extensions.contains($extension.id))
    #getExtensionJobStatus($extension.id.id $extension.id.version.value $jobStatus)
    #set ($jobState = $jobStatus.state)
  #end
  #if ($jobStatus && $jobState != 'FINISHED')
    #displayJobProgressBar($jobStatus)
  #end
#end

#macro (displayExtensionDetails_menuLink $detail $selected)
  <a href="#extension-body-${detail}-$extensionIdHashCode"#if ($selected) class="current"#end>
    $services.localization.render("extensions.info.category.${detail}")
  </a>
#end

#macro (displayExtensionDetails_menu $extension)
#if (!$jobStatus || !$jobStatus.request.extensions.contains($extension.id))
  #getExtensionJobStatus($extension.id.id $extension.id.version.value $jobStatus)
  #set ($jobState = $jobStatus.state)
#end
<ul class="innerMenu">
<li>#displayExtensionDetails_menuLink('description')</li>##
##
#if ($extension.dependencies.size() > 0 || $backwardDependencies.size() > 0)
<li>#displayExtensionDetails_menuLink('dependencies')</li>##
#end
##
#if ($jobStatus)
#if ($jobState == 'FINISHED' && $jobStatus.documentDiffs)
<li>#displayExtensionDetails_menuLink('changes')</li>##
#end
#set ($selected = $jobState != 'FINISHED' || $request.extensionSection == 'progress')
<li>#displayExtensionDetails_menuLink('progress' $selected)</li>##
#end
</ul>
#end

#macro (displayExtensionDetails_description $extension)
  <div id="extension-body-description-$extensionIdHashCode"></div>
  <dl class="extension-body-description extension-body-section">
    <dt>$services.localization.render('extensions.info.id')</dt>
    <dd>$extension.id.id</dd>

    #if ($extension.extensionFeatures.size() > 0)
      <dt>$services.localization.render('extensions.info.features', [$extension.extensionFeatures.size()])</dt>
      <dd>
      #if ($extension.extensionFeatures.size() == 1)
        $escapetool.xml($extension.extensionFeatures.iterator().next().id)
      #else
        <ul>
          #foreach ($feature in $extension.extensionFeatures)
            <li>$escapetool.xml($feature.id)</li>
          #end
        </ul>
      #end
      </dd>
    #end

    <dt>$services.localization.render('extensions.info.type')</dt>
    <dd>$extension.type</dd>

    <dt>$services.localization.render('extensions.info.license', [$extension.licenses.size()])</dt>
    #if ($extension.licenses.size() > 0)
      <dd>
        #if ($extension.licenses.size() == 1)
          $extension.licenses.iterator().next().name
        #else
          <ul>
            #foreach ($license in $extension.licenses)
              <li>$license.name</li>
            #end
          </ul>
        #end
      </dd>
    #end

    #if ("$!{extension.webSite}" != '')
      <dt>$services.localization.render('extensions.info.website')</dt>
      <dd><a href="$extension.webSite">$escapetool.xml($extension.webSite.replaceAll('^[^/]++//([^/\?]++)[/\?]?.*+$', '$1'))</a></dd>
    #end

    #if ($extension.repository.descriptor.URI && $extension.repository.descriptor.URI.scheme != 'file')
      <dt>$services.localization.render('extensions.info.repository')</dt>
      <dd><a href="$extension.repository.descriptor.URI">$escapetool.xml($extension.repository.descriptor.id)</a></dd>
    #end

    #if ($extension.scm)
      #if ($extension.scm.url)
        <dt>$services.localization.render('extensions.info.scm')</dt>
        <dd><a href="$extension.scm.url">$escapetool.xml($extension.scm.url.replaceAll('^[^/]++//([^/\?]++)[/\?]?.*+$', '$1'))</a></dd>
      #elseif ($extension.scm.connection)
        <dt>$services.localization.render('extensions.info.scm')</dt>
        <dd><a href="$extension.scm.connection.path">$escapetool.xml($extension.scm.connection.system)</a></dd>
      #end
    #end

    #if ($extension.issueManagement.getURL())
      <dt>$services.localization.render('extensions.info.issueManagement')</dt>
      <dd><a href="$extension.issueManagement.getURL()">#if($extension.issueManagement.system)$escapetool.xml($extension.issueManagement.system)#else$escapetool.xml($extension.issueManagement.getURL())#end</a></dd>
    #end

    #if ($extension.isInstalled())
      #displayExtensionDetails_description_wikis($extension)
    #end

    #if ($extensionStatus != 'loading')
      #displayExtensionDetails_description_versions($extension)
    #end

    ##
    ## TODO: need a decision on what exactly is the description and how it should be safely displayed (wiki syntax,
    ## server side generated HTML, etc.)
    ## <dt>Description</dt>
    ## <dd>$!extension.description</dd>
  </dl>
#end

#macro (displayExtensionDetails_description_wikis $extension)
  #if (!$extension.namespaces || $extension.namespaces.isEmpty())
    ## The given extension was installed globally.
    #getInstallInfo($extension $NULL $install)
    #if ($install.date)
      <dt>$services.localization.render('extensions.info.installedGloballyBy',
        [$xwiki.getUserName($install.userReference), $xwiki.formatDate($install.date)])</dt>
    #else
      <dt>$services.localization.render('extensions.info.namespaces.global')</dt>
    #end
  #elseif ($xcontext.isMainWiki())
    ## Display the list of namespaces where the given extension is installed only if we are on the main wiki.
    <dt>$services.localization.render('extensions.info.namespaces.list')</dt>
    <dd><ul>
      #foreach ($namespace in $extension.namespaces)
        #getInstallInfo($extension $namespace $install)
        #if ($install.date)
          <li>$services.localization.render('extensions.info.installedOnNamespaceBy', [
            "#displayExtensionNamespace($namespace)",
            $xwiki.getUserName($install.userReference),
            $xwiki.formatDate($install.date)
          ])</li>
        #else
          <li>#displayExtensionNamespace($namespace)</li>
        #end
      #end
    </ul></dd>
  #else
    #getInstallInfo($extension $extensionNamespace $install)
    #if ($install.date)
      <dt>$services.localization.render('extensions.info.installedBy',
        [$xwiki.getUserName($install.userReference), $xwiki.formatDate($install.date)])</dt>
    #end
  #end
#end

#macro (getInstallInfo $installedExtension $namespace $return)
  #set ($install = {'date': $installedExtension.getInstallDate($namespace)})
  #if ($install.date)
    #set ($install.userReference = $installedExtension.getUserReference($namespace))
  #else
    #set ($installJobStatus = $extensionManager.getExtensionJobStatus($installedExtension.id.id, $namespace))
    #if ($installJobStatus && $installJobStatus.jobType == 'install')
      #set ($install.date = $installJobStatus.startDate)
      #set ($install.userReference = $installJobStatus.request.getProperty('user.reference'))
    #end
  #end
  #set ($return = $NULL)
  #setVariable("$return" $install)
#end

#macro (displayExtensionNamespace $namespace)
#if ("$!namespace" == '' && $namespace != '')
$services.localization.render('extensions.info.globalNamespace')##
#elseif ($namespace.startsWith('wiki:'))
#wikiHomePageLink($namespace)##
#else
$namespace##
#end
#end

#macro (displayExtensionDetails_description_versions $extension)
  <dt>$services.localization.render('extensions.info.stableVersions.label')</dt>
  <dd>
    #if (!$request.listVersions)
      <a href="#getExtensionURL($extension.id.id $extension.id.version.value {'listVersions': true})"
        class="extension-versions-link">
        $services.localization.render('extensions.info.stableVersions.linkLabel')
      </a>
    #else
      #set ($stableVersions = [])
      #foreach ($version in $extensionManager.resolveVersions($extension.id.id, 0, -1))
        #if ($version.type == 'STABLE')
          #set ($discard = $stableVersions.add($version.value))
        #end
      #end
      #if ($stableVersions.size() > 0)
        <ul>
        ## Latest version first.
        #foreach ($i in [$mathtool.sub($stableVersions.size(), 1)..0])
          #set ($version = $stableVersions.get($i))
          <li><a href="#getExtensionURL($extension.id.id $version)" class="extension-link">$version</a></li>
        #end
        </ul>
      #else
        $services.localization.render('extensions.info.stableVersions.noResults')
      #end
    #end
  </dd>
#end

#**
 * NOTE: We explicitly overwrite the $extensionNamespace global variable because we want the dependency status to be
 * determined for the given namespace. See #determineExtensionStatus() macro.
 *#
#macro (displayDependency $dependencyOrExtension $extensionNamespace $resolveRemotely)
  #if ($dependencyOrExtension.versionConstraint)
    ## Dependency object.
    #set ($dependencyId = $dependencyOrExtension.id)
    #set ($dependencyVersion = $dependencyOrExtension.versionConstraint)
    #if ($resolveRemotely)
      #set ($dependencyExtension = $extensionManager.resolve($dependencyOrExtension, $extensionNamespace))
    #else
      ## Search for the dependency only in the core and local repositories.
      ## TODO: Check also the remote extensions that have been partially cached locally (e.g. only their pom has been
      ## downloaded). We need a resolve method that doesn't perform any remote calls.
      #set ($dependencyExtension = $extensionManager.getRepository('core').resolve($dependencyOrExtension))
      #if (!$dependencyExtension)
        #set ($dependencyExtension = $extensionManager.getRepository('local').resolve($dependencyOrExtension))
      #end
    #end
  #else
    ## Extension object.
    #set ($dependencyId = $dependencyOrExtension.id.id)
    #set ($dependencyVersion = $dependencyOrExtension.id.version.value)
    #set ($dependencyExtension = $dependencyOrExtension)
  #end
  #set ($dependencyStatus = 'unknown')
  #set ($dependencyStatusMessage = $NULL)
  #set ($dependencyName = $dependencyId)
  #if ($dependencyExtension)
    ## The extension status is determined for the $extensionNamespace . The name of this parameter is very important
    ## because it has to overwrite the global variable with the same name.
    #determineExtensionStatus($dependencyExtension $dependencyStatus $dependencyStatusMessage $dependencyOrExtension.versionConstraint)
    #set ($dependencyURL = "#getExtensionURL($dependencyId $dependencyVersion)")
    #set ($dependencyName = "<a href=""$dependencyURL"" class=""extension-link"">#displayExtensionName($dependencyExtension)</a>")
  #end
  <div class="dependency-item extension-item-$dependencyStatus">
    <span class="extension-name">${dependencyName}</span><span class="extension-version">$!dependencyVersion</span>
    #if ($extensionNamespace.startsWith('wiki:'))
      <span class="extension-namespace">$services.localization.render('extensions.info.dependency.wiki', ["#wikiHomePageLink($extensionNamespace)"])</span>
    #end
    #if ("$!dependencyStatusMessage" != '')
      <span class="extension-status">$dependencyStatusMessage</span>
    #end
  </div>
#end

#macro (displayExtensionDetails_dependencies_upstream $extension)
  #if ($extension.dependencies.size() > 0)
    <dt>$services.localization.render('extensions.info.dependencies.directDependencies', [$extension.dependencies.size()])</dt>
    <dd>
      <ul class="dependencies">
        #foreach ($dependency in $extension.dependencies)
          <li>#displayDependency($dependency $extensionNamespace)</li>
        #end
      </ul>
    </dd>
  #end
#end

#macro (displayExtensionDetails_dependencies_downstream $backwardDependencies)
  #if ($backwardDependencies.size() > 0)
    <dt>$services.localization.render('extensions.info.dependencies.backwardDependencies', [$backwardDependencies.size()])</dt>
    <dd>
      <ul class="dependencies">
        #foreach ($namespace in $backwardDependencies.entrySet())
          #foreach ($dependency in $namespace.value)
            <li>#displayDependency($dependency $namespace.key)</li>
          #end
        #end
      </ul>
    </dd>
  #end
#end

#macro (displayExtensionDetails_dependencies $extension $backwardDependencies)
  #if ($extension.dependencies.size() > 0 || $backwardDependencies.size() > 0)
    <div id="extension-body-dependencies-$extensionIdHashCode"></div>
    #computeXBack()
    <dl class="extension-body-dependencies extension-body-section">
      #displayExtensionDetails_dependencies_upstream($extension)
      #displayExtensionDetails_dependencies_downstream($backwardDependencies)
    </dl>
  #end
#end

#macro (displayExtensionDetails_changes $extension)
  #if (!$jobStatus || !$jobStatus.request.extensions.contains($extension.id))
    #getExtensionJobStatus($extension.id.id $extension.id.version.value $jobStatus)
    #set ($jobState = $jobStatus.state)
  #end
  #set ($documentDiffs = $jobStatus.documentDiffs)
  #if ($jobState == 'FINISHED' && $documentDiffs)
    ## Need csrf token for reverts
    <input name="form_token" value="$!services.csrf.getToken()" type="hidden" />
    <div id="extension-body-changes-$extensionIdHashCode"></div>
    <div class="extension-body-changes extension-body-section">
      #template('diff_macros.vm')
      #displayDocumentUnifiedDiffsWithSummary($documentDiffs 3)
    </div>
  #end
#end

#macro (displayExtensionDetails_progress $extension)
  #if (!$jobStatus || !$jobStatus.request.extensions.contains($extension.id))
    #getExtensionJobStatus($extension.id.id $extension.id.version.value $jobStatus)
    #set ($jobState = $jobStatus.state)
  #end
  #if ($jobStatus)
    <div id="extension-body-progress-$extensionIdHashCode"></div>
    <div class="extension-body-progress extension-body-section">
      #displayExtensionJobStatus($jobStatus)
      #if ($jobState == 'WAITING')
        <div class="extension-question xform">
          #displayExtensionDetails_progressQuestion($extension $jobStatus)
        </div>
      #end
    </div>
  #end
#end

#macro (displayExtensionJobStatus $jobStatus)
  #isExtensionPlan($jobStatus $isExtensionPlan)
  #if ($isExtensionPlan)
    #displayExtensionPlan($jobStatus)
  #else
    ## Display any incompatibility errors
    #set ($jobStatusCollapsed = false)
    #if ($jobStatus.state == 'FINISHED')
      #if ($jobStatus.error && $exceptiontool.getRootCauseMessage($jobStatus.error).contains('not compatible'))
        #set ($jobStatusCollapsed = true)
        #set ($errorMessage = $services.localization.render('platform.extension.info.error.versionNotCompatible'))
        #set ($errorMessage = "${errorMessage}<br/>")
        #set ($errorMessage = "${errorMessage}${services.localization.render('platform.extension.info.error.versionNotCompatibleHint')}")
        <p>#error($errorMessage)</p>
      #end      
    #end
    #displayJobStatusLog($jobStatus, $jobStatusCollapsed)
  #end
#end

#macro (isExtensionPlan $jobStatus $return)
  #set ($isExtensionPlan = $jobStatus.state == 'FINISHED' && $jobStatus.actions && !$jobStatus.error)
  #set ($return = $NULL)
  #setVariable ("$return" $isExtensionPlan)
#end

#macro (displayExtensionPlan $plan)
  ## Group the extensions by the actions that will be performed on them.
  #set($extensionsByAction = {'INSTALL': [], 'UPGRADE': [], 'DOWNGRADE': [], 'UNINSTALL': [], 'REPAIR': []})
  #set ($noAction = true)
  #foreach($planAction in $plan.actions)
    #set ($targetExtensions = $extensionsByAction.get($planAction.action.name()))
    #if ($targetExtensions)
      #set ($discard = $targetExtensions.add($planAction))
      #set ($noAction = false)
    #end
  #end
  ##
  #if ($noAction)
    #set ($emptyPlanMessageKeys = {
      'installplan': 'extensions.install.error.alreadyInstalled',
      'uninstallplan': 'extensions.uninstall.error.notInstalled'
    })
    <div class="infomessage">$services.localization.render($emptyPlanMessageKeys.get($plan.jobType))</div>
  #else
    <dl>
    #foreach($entry in $extensionsByAction.entrySet())
      #if (!$entry.value.isEmpty())
        <dt>$services.localization.render("extensions.install.list.${entry.key.toLowerCase()}")</dt>
        <dd>
          <ul class="dependencies">
            #foreach ($planAction in $entry.value)
              <li>#displayDependency($planAction.extension $planAction.namespace)</li>
            #end
          </ul>
        </dd>
      #end
    #end
    </dl>
  #end
#end

#macro (displayExtensionDetails_progressQuestion $extension $jobStatus)
  #set ($question = $jobStatus.question)
  #set ($questionType = $question.getClass().getName())
  #if ($questionType.endsWith('.ConflictQuestion'))
    #displayExtensionDetails_mergeConflictQuestion($question)
  #elseif ($questionType.endsWith('.CleanPagesQuestion'))
    #displayExtensionDetails_cleanPagesQuestion($question)
  #elseif ($questionType.endsWith('.DefaultConflictActionQuestion'))
    #displayExtensionDetails_defaultConflictActionQuestion($question)
  #end
#end

#macro (displayExtensionDetails_mergeConflictQuestion $question)
  <dl>
    <dt>
      <label>$services.localization.render('extensions.upgrade.mergeConflict.label')</label>
      <span class="xHint">$services.localization.render('extensions.upgrade.mergeConflict.hint',
        ["<a href=""$xwiki.getURL($question.currentDocument.documentReference)"">$question.currentDocument</a>"])</span>
    </dt>
    <dd>
      <select name="versionToKeep">
      #set ($versions = {
        'NEXT': $question.nextDocument,
        'MERGED': $question.mergedDocument,
        'CURRENT': $question.currentDocument
      })
      #foreach($entry in $versions.entrySet())
        ## Make sure that each version has a document associated. We don't have for instance a merged document when
        ## there is no previous installed version of a XAR extension but the imported documents already exist in the wiki.
        #if ($entry.value)
          <option value="$entry.key"#if ($question.globalAction == $entry.key) selected="selected"#end>
            $services.localization.render("extensions.upgrade.mergeConflict.versionToKeep.${entry.key.toLowerCase()}")
          </option>
        #end
      #end
      </select>
    </dd>
    <dt>
      <label>
        <input type="checkbox" name="autoResolve" value="true" />
        $services.localization.render('extensions.upgrade.mergeConflict.autoResolve')
      </label>
      <span class="xHint">$services.localization.render('extensions.upgrade.mergeConflict.autoResolve.hint')</span>
    </dt>
  </dl>
  #displayExtensionDetails_mergeConflictChanges($question)
#end

#macro (displayExtensionDetails_mergeConflictChanges $question)
  <h3 class="extension-diff-title">$services.localization.render('extensions.upgrade.mergeConflict.changes.title',
    ["<a href=""$xwiki.getURL($question.currentDocument.documentReference)"">$question.currentDocument</a>"])</h3>
  <div class="extension-diff-options">
    #set ($versions = {
      'PREVIOUS': $question.previousDocument,
      'CURRENT': $question.currentDocument,
      'NEXT': $question.nextDocument,
      'MERGED': $question.mergedDocument
    })
    <span class="label">$services.localization.render('extensions.upgrade.mergeConflict.changes.original')</span><select name="original">
    #if ("$!request.original" != '')
      #set ($originalVersion = $request.original)
    #else
      #set ($originalVersion = 'CURRENT')
    #end
    #set ($originalDocument = $versions.get($originalVersion))
    #foreach($entry in $versions.entrySet())
      #if ($entry.value)
        <option value="$entry.key"#if ($entry.key == $originalVersion) selected="selected"#end>
          $services.localization.render("extensions.upgrade.mergeConflict.changes.versionToCompare.${entry.key.toLowerCase()}")
        </option>
      #end
    #end
    </select><span class="label">$services.localization.render('extensions.upgrade.mergeConflict.changes.revised')</span><select name="revised">
    #if ("$!request.revised" != '')
      #set ($revisedVersion = $request.revised)
    #elseif ($question.mergedDocument)
      #set ($revisedVersion = 'MERGED')
    #else
      #set ($revisedVersion = 'NEXT')
    #end
    #set ($revisedDocument = $versions.get($revisedVersion))
    #foreach($entry in $versions.entrySet())
      #if ($entry.value)
        <option value="$entry.key"#if ($entry.key == $revisedVersion) selected="selected"#end>
          $services.localization.render("extensions.upgrade.mergeConflict.changes.versionToCompare.${entry.key.toLowerCase()}")
        </option>
      #end
    #end
    </select>#extensionActionButton('diff' true)
  </div>
  #if ($originalDocument && $revisedDocument)
    <div id="changescontent">
      #set ($conflictsList = $question.documentConflicts)
      #set ($rev1 = $originalVersion.toLowerCase())
      #set ($rev2 = $revisedVersion.toLowerCase())
      #set ($wrappedDocs = $xwiki.wrapDocs([$originalDocument, $revisedDocument]))
      #set ($origdoc = $wrappedDocs.get(0))
      #set ($newdoc = $wrappedDocs.get(1))
      #set ($headingLevel = 4)
      #template('changesdoc.vm')
    </div>
  #end
#end

#macro (displayExtensionDetails_cleanPagesQuestion $question)
  <dl>
    <dt>
      <label>$escapetool.xml($services.localization.render('extensions.uninstall.cleanPages.label'))</label>
      <span class="xHint">$escapetool.xml($services.localization.render('extensions.uninstall.cleanPages.hint'))</span>
    </dt>
    <dd>
      #set ($entityReferenceTree = $services.model.toTree($question.pages.keySet()))
      #displayDocumentTree($entityReferenceTree $question.pages)
    </dd>
  </dl>
#end

#macro (displayDocumentTree $entityReferenceTree $mapOfSelectedDocuments)
  #set ($currentLocale = $services.localization.currentLocale)
  #displayDocumentTreeNode($entityReferenceTree $mapOfSelectedDocuments)
#end

#macro (displayDocumentTreeNode $node $mapOfSelectedDocuments)
  #if ($node.reference)
    #set ($nodeLabel = $node.reference.name)
    #if ("$node.reference.type" == 'WIKI')
      #set ($wikiPrettyName = $services.wiki.getById($node.reference.name).prettyName)
      #if ("$!wikiPrettyName.trim()" != '')
        #set ($nodeLabel = $wikiPrettyName)
      #end
    #end
    <div class="$!node.reference.type.toString().toLowerCase() node">$escapetool.xml($nodeLabel)
      #if ("$node.reference.type" == 'DOCUMENT')
        ## Currently there's no string representation of a document reference with parameters (such as locale) that can
        ## be resolved so we need to submit a set of (document reference without locale, locale) pairs.
        #set ($name = $escapetool.xml($services.model.serialize($node.reference, 'default')))
        ## We don't display the locale child nodes if there is only one locale. We display just the document node.
        #if ($node.locales && $node.locales.size() == 1)
          #set ($documentReference = $node.locales.iterator().next())
          #if ("$!documentReference.locale" != '')
            ## Display the locale if it's not the default one.
            <span class="locale">$escapetool.xml($documentReference.locale.getDisplayName($currentLocale))</span>
          #end
          <span class="actions">
            <input type="checkbox" name="$name" value="$escapetool.xml("$!documentReference.locale")"
              #if ($mapOfSelectedDocuments.get($documentReference)) checked="checked"#end />
          </span>
        #end
      #end
    </div>
  #end
  #if ($node.children && $node.children.size() > 0)
    <ul#if (!$node.reference) class="collapsible selectable document-tree"#end>
      #foreach ($child in $node.children)
        <li#if ("$!child.reference.type" == 'DOCUMENT') class="collapsed"#end>
          #displayDocumentTreeNode($child $mapOfSelectedDocuments)
        </li>
      #end
    </ul>
  #elseif ($node.locales && $node.locales.size() > 1)
    <ul>
      #foreach ($documentReference in $node.locales)
        #if ("$!documentReference.locale" == '')
          #set ($locale = $services.localization.render(
            'platform.extension.distributionWizard.reportStepDocumentsDefaultLanguage'))
        #else
          #set ($locale = $documentReference.locale.getDisplayName($currentLocale))
        #end
        <li class="locale node">$escapetool.xml($locale)
          <span class="actions">
            <input type="checkbox" name="$name" value="$escapetool.xml("$!documentReference.locale")"
              #if ($mapOfSelectedDocuments.get($documentReference)) checked="checked"#end />
          </span>
        </li>
      #end
    </ul>
  #end
#end

#macro (displayExtensionDetails_defaultConflictActionQuestion $question)
  <dl>
    <dt>
      <label>$services.localization.render('extension.xar.question.defaultConflictAction.label')</label>
      <span class="xHint">$services.localization.render('extension.xar.question.defaultConflictAction.hint')</span>
    </dt>
    <dd>
      <dl>
        #foreach ($conflictType in $services.extension.xar.conflictTypes)
          #set ($defaultConflictAction = $question.getConflictAction($conflictType))
          <dt>
            <label>$services.localization.render("extension.xar.conflict.${conflictType}.label")</label>
            <span class="xHint">$services.localization.render("extension.xar.conflict.${conflictType}.hint")</span>            
          </dt>
          <dd>
            <select name="defaultConflictAction_$conflictType">
            #foreach ($conflictAction in $conflictType.actions)
              <option value="$conflictAction" label="$services.localization.render("extension.xar.conflict.action.${conflictAction}.hint")"#if ($conflictAction == $defaultConflictAction) selected="selected"#end>
                $services.localization.render("extension.xar.conflict.action.${conflictAction}.label")
              </option>
            #end
            </select>            
          </dd>
        #end
      </dl>
    </dd>
  </dl>
#end

#macro (answerExtensionJobQuestion_defaultConflictActionQuestion $question)
  #foreach ($conflictType in $services.extension.xar.conflictTypes)
    #set ($conflictAction = $request.getParameter("defaultConflictAction_$conflictType"))
    #if ($conflictAction)
      #set ($void = $question.setConflictAction($conflictType, $conflictAction))
    #end
  #end
#end

#macro (getBackwardDependencies $extension $return)
  #getInstalledExtension($extension $extensionNamespace $installedExtension)
  #if ($installedExtension.isInstalled($NULL))
    ## The extension is installed in the root namespace so it can have backward dependencies on multiple namespaces.
    #set ($backwardDependencies = $services.extension.installed.getBackwardDependencies($installedExtension.id))
    #if ($backwardDependencies && ($xcontext.isMainWiki() || $backwardDependencies.containsKey($extensionNamespace)))
      #if (!$xcontext.isMainWiki())
        ## If we're not on the main wiki then display only the backward dependencies from the current namespace.
        #set ($backwardDependencies = {$extensionNamespace: $backwardDependencies.get($extensionNamespace)})
      #end
    #else
      #set ($backwardDependencies = {})
    #end
  #elseif ($installedExtension)
    ## The extension is installed on the current namespace.
    #set ($backwardDependencies = $services.extension.installed.getBackwardDependencies($installedExtension.id.id,
      $extensionNamespace))
    #if ($backwardDependencies && $backwardDependencies.size() > 0)
      #set ($backwardDependencies = {$extensionNamespace: $backwardDependencies})
    #else
      #set ($backwardDependencies = {})
    #end
  #else
    #set ($backwardDependencies = {})
  #end
  #set ($return = $NULL)
  #setVariable("$return" $backwardDependencies)
#end

#macro (displayExtensionDetails $extension)
  <div class="extension-body">
    #set ($extensionIdHashCode = "$extensionNamespace/$extension.id.id/$extension.id.version.value")
    #set ($extensionIdHashCode = $extensionIdHashCode.hashCode())
    #getBackwardDependencies($extension $backwardDependencies)
    #displayExtensionDetails_menu($extension)

    #displayExtensionDetails_description($extension)
    #displayExtensionDetails_dependencies($extension $backwardDependencies)
    #displayExtensionDetails_changes($extension)
    #displayExtensionDetails_progress($extension)
  </div>
#end

#macro (displayExtension $extension $readOnly)
  ## The job status can change while the extension is displayed so we cache it. Let's reset the cache.
  #set ($jobStatus = $NULL)
  #isShowingExtensionDetails($extension $showExtensionDetails)
  #determineExtensionStatus($extension $extensionStatus $extensionStatusMessage)
  <form action="$xwiki.relativeRequestURL" method="post" class="extension-item extension-item-${extensionStatus}">
    <div class="hidden">
      <input name="readOnly" value="$!readOnly" type="hidden" />
      <input name="extensionId" value="$!escapetool.xml($extension.id.id)" type="hidden" />
      <input name="extensionVersion" value="$!escapetool.xml($extension.id.version.value)" type="hidden" />
      <input name="extensionNamespace" value="$!escapetool.xml($extensionNamespace)" type="hidden" />
      #if ($request.section)
        <input name="section" value="$escapetool.xml($request.section)" type="hidden" />
      #end
      #if ($request.viewer)
        ## Send the AJAX requests to the specified viewer (Velocity template). This is needed for instance when the
        ## Extension Manager UI is not installed.
        <input name="xpage" value="$escapetool.xml($request.viewer)" type="hidden" />
      #end
    </div>
    <div class="extension-header">
      <h2 class="extension-title">
        <span class="extension-name">#displayExtensionName($extension)</span>
        <span class="extension-version">$escapetool.xml($extension.id.version)</span>
      </h2>
      #if ($extensionStatusMessage)
        <p class="extension-status">$escapetool.xml($extensionStatusMessage)</p>
      #end
      #displayExtensionActionButtons($extension, $readOnly)
      #displayExtensionRating($extension)
      #if ($extension.authors.size() > 0)
        #displayExtensionAuthors($extension)
      #end
      #if ("$!extension.summary" != '')
        <div class="extension-summary">$escapetool.xml($extension.summary)</div>
      #end
      #if ($extension.isRecommended())
        <div class="extension-recommended">$services.localization.render('extensions.info.recommended')</div>
      #end
      #displayProgressBar($extension)
      <div class="clearfloats"></div>
    </div>
    #if ($showExtensionDetails)
      #displayExtensionDetails($extension)
    #end
  </form>
#end

#macro (handleExtensionRequest)
  #if ($request.form_token)
    #if ($services.csrf.isTokenValid($request.form_token))
      #handleExtensionAction(true)
    #elseif ($isAjaxRequest)
      ## The CSRF token expired. We only redisplay the extension because the resubmission confirmation doesn't fit nicely in-line.
      $response.sendRedirect("#getExtensionURL()")
    #else
      $response.sendRedirect($services.csrf.getResubmissionURL())
    #end
  #else
    #handleExtensionAction(false)
  #end
#end

#macro (handleExtensionAction $withValidToken)
  #if ($request.extensionAction == 'continue' && $withValidToken)
    #continueExtensionJob($request.extensionId $request.extensionVersion)
  #elseif ($request.extensionAction == 'repairXAR' && $withValidToken)
    #repairXarExtension($request.extensionId $request.extensionVersion)
  #elseif ($request.extensionAction == 'install' || $request.extensionAction == 'upgrade'
    || $request.extensionAction == 'downgrade')
    #computeInstallPlan($request.extensionId $request.extensionVersion $extensionNamespace)
  #elseif ($request.extensionAction == 'installGlobally')
    #computeInstallPlan($request.extensionId $request.extensionVersion $NULL)
  #elseif ($request.extensionAction == 'upgradeGlobally' || $request.extensionAction == 'downgradeGlobally')
    #computeUpgradePlan($request.extensionId $request.extensionVersion)
  #elseif ($request.extensionAction == 'uninstall')
    #computeUninstallPlan($request.extensionId $request.extensionVersion $extensionNamespace)
  #elseif ($request.extensionAction == 'uninstallGlobally')
    #computeUninstallPlan($request.extensionId $request.extensionVersion $NULL)
  #elseif ($request.extensionAction == 'diffXAR')
    #diffXarExtension($request.extensionId $request.extensionVersion)
  #elseif ($request.extensionAction == 'revertDocument' && $withValidToken)
    #revertDocument($request.documentReference $request.documentLocale $request.documentExtensionId $request.documentExtensionVersion, $request.extensionId)
  #elseif ($request.extensionVersionConstraint)
    #set ($dependency = $extensionManager.createExtensionDependency($request.extensionId, $request.extensionVersionConstraint))
    #displayDependency($dependency $extensionNamespace true)
  #else
    ## Display the extension.
    ## Create a dependency in order to support version constraints (e.g. a version range).
    #set ($readOnly = $request.readOnly == 'true')
    #set ($dependency = $extensionManager.createExtensionDependency($request.extensionId, $request.extensionVersion))
    #if ($dependency.versionConstraint.version && $dependency.versionConstraint.ranges.isEmpty())
      ## A precise version was specified so we resolve the requested extension.
      #set ($extension = $extensionManager.resolve($request.extensionId, $request.extensionVersion))
    #else
      ## A version range was specified so we resolve the requested extension dependency. Note that the result might not
      ## be exactly the requested extension but some extension that provides / features the requested extension.
      #set ($extension = $extensionManager.resolve($dependency, $extensionNamespace))
    #end
    #if ($extension)
      #displayExtension($extension, $readOnly)
    #else
      <div class="infomessage">$services.localization.render('extensions.advancedSearch.noResults',
        ["<strong>$!escapetool.xml($request.extensionId)</strong>",
        "<strong>$!escapetool.xml($request.extensionVersion)</strong>"])</div>

      ## DEBUG START: Check if there is any job status associated with the specified extension.
      #getExtensionJobStatus($request.extensionId, $request.extensionVersion $jobStatus)
      #if ($jobStatus)
        ## Normally we shouldn't get here.
        <div class="errormessage">We found an extension job associated with the missing extension:</div>
        #displayExtensionJobStatus($jobStatus)
      #end
      ## DEBUG STOP
    #end
  #end
#end

#macro (computeInstallPlan $extensionId $extensionVersion $extensionNamespace)
  #set ($installPlanRequest = $extensionManager.createInstallPlanRequest($extensionId, $extensionVersion, $extensionNamespace))
  #if ($extensionConfig.skipCheckRight)
    #set ($discard = $installPlanRequest.removeProperty('checkrights'))
  #end
  #if ($extensionConfig.skipCurrentUser)
    #set ($discard = $installPlanRequest.removeProperty('user.reference'))
  #end
  #if ($extensionConfig.installJAROnRoot)
    #set ($discard = $installPlanRequest.rewriter.installExtensionTypeOnRootNamespace('jar'))
    #set ($discard = $installPlanRequest.rewriter.installExtensionTypeOnRootNamespace('webjar'))
  #end
  #set ($discard = $extensionManager.createInstallPlan($installPlanRequest))
  #handleExtensionJobStartFailure('extensions.install.error.prepareFailure')
#end

#macro (computeUpgradePlan $extensionId $extensionVersion)
  ## Upgrade all the namespaces were the specified extension is installed.
  #set ($namespaces = [])
  #foreach ($extension in $extensionManager.installedExtensions)
    #if ($extension.id.id == $extensionId)
      #if ($extension.isInstalled($NULL))
        #set ($discard = $namespaces.clear())
        #break
      #else
        #foreach ($namespace in $extension.namespaces)
          #set ($discard = $namespaces.add($namespace))
        #end
      #end
    #end
  #end
  ## The namespace that will appear in the job id.
  #set ($jobIdNamespace = $NULL)
  #if ($namespaces.size() == 1)
    #set ($jobIdNamespace = $namespaces.get(0))
  #end
  #set ($installPlanRequest = $extensionManager.createInstallPlanRequest($extensionId, $extensionVersion, $jobIdNamespace))
  #foreach ($namespace in $namespaces)
    #set ($discard = $installPlanRequest.addNamespace($namespace))
  #end
  #set ($discard = $extensionManager.createInstallPlan($installPlanRequest))
  #handleExtensionJobStartFailure('extensions.install.error.prepareFailure')
#end

#macro (computeUninstallPlan $extensionId $extensionVersion $extensionNamespace)
  #set ($discard = $extensionManager.createUninstallPlan($extensionId, $extensionNamespace))
  #handleExtensionJobStartFailure('extensions.uninstall.error.prepareFailure')
#end

#macro (continueExtensionJob $extensionId $extensionVersion)
  #getExtensionJobStatus($extensionId $extensionVersion $jobStatus)
  #set ($jobState = $jobStatus.state)
  #set ($jobType = $jobStatus.jobType)
  #if ($jobState == 'FINISHED')
    #isExtensionPlan($jobStatus $isExtensionPlan)
    #if ($isExtensionPlan)
      ## Execute the latest extension job plan.
      ## TODO: Would be nice to reuse somehow the job request that was used to create the plan.
      #if ($jobType == 'installplan')
        #installExtension($extensionId $extensionVersion $jobStatus.request)
        #break
      #elseif ($jobType == 'uninstallplan')
        #uninstallExtension($extensionId $extensionVersion $jobStatus.request)
        #break
      #end
    #end
  #elseif ($jobState == 'WAITING')
    #answerExtensionJobQuestion($jobStatus)
  #end
  ## Redirect to extension display.
  $response.sendRedirect("#getExtensionURL()")
#end

#macro(revertDocument $documentReference $documentLocale $documentExtensionId $documentExtensionVersion $jobExtensionId)
  ## Create instance of ExtensionId
  #set ($extensionId = $services.extension.createExtensionId($documentExtensionId, $documentExtensionVersion))
  ## Create instance of DocumentRefernce with the Locale
  #set ($documentReferenceWithLocale = $services.model.createDocumentReference($services.model.resolveDocument($documentReference), $services.localization.toLocale($documentLocale)))
  ## Revert the document to the extension state
  #if (!$services.extension.xar.reset($documentReferenceWithLocale, $extensionId, $services.extension.xar.getDiffJobId($jobExtensionId, $extensionNamespace)))
    $response.sendError(500, $services.localization.render('extensions.xar.resetDocument.error'))
  #end
#end

#macro (answerExtensionJobQuestion $jobStatus)
  ## Continue an interactive extension job using the data submitted by the user.
  #set ($question = $jobStatus.question)
  #set ($questionType = $question.getClass().getName())
  #if ($questionType.endsWith('.ConflictQuestion'))
    ## A merge conflict occurred during installation.
    #set ($discard = $question.setGlobalAction($request.versionToKeep))
    #set ($discard = $question.setAlways($request.autoResolve.equals('true')))
    #set ($allDecisions = $request.getParameterValues("conflict_decision_select"));
    #set ($allCustoms = $request.getParameterValues("conflict_decision_value_custom"))
    #foreach($conflictReference in $request.getParameterValues("conflict_id"))
      #set ($decision = $allDecisions[$foreach.index])
      #set ($custom = $allCustoms[$foreach.index])
      #set ($discard = $question.setConflictDecision($conflictReference, $decision, $custom))
    #end
  #elseif ($questionType.endsWith('.CleanPagesQuestion'))
    #foreach ($entry in $question.pages.entrySet())
      ## Currently there's no string representation of a document reference with parameters (such as locale) that can
      ## be resolved so what has been submitted is a set of (document reference without locale, locale) pairs.
      #set ($stringReferenceWithoutLocale = $services.model.serialize($entry.key, 'default'))
      #set ($localesToDelete = $request.getParameterValues($stringReferenceWithoutLocale))
      #set ($delete = $localesToDelete && $localesToDelete.contains($entry.key.locale.toString()))
      #set ($discard = $entry.setValue($delete))
    #end
  #elseif ($questionType.endsWith('.DefaultConflictActionQuestion'))
    #answerExtensionJobQuestion_defaultConflictActionQuestion($question)
  #end
  #set ($discard = $jobStatus.answered())
#end

#macro (installExtension $extensionId $extensionVersion $installRequest)
  #set ($discard = $extensionManager.install($installRequest))
  #handleExtensionJobStartFailure('extensions.install.error.installFailure')
#end

#macro (uninstallExtension $extensionId $extensionVersion $uninstallRequest)
  #set ($discard = $extensionManager.uninstall($uninstallRequest))
  #handleExtensionJobStartFailure('extensions.uninstall.error.uninstallFailure')
#end

#macro (repairXarExtension $extensionId $extensionVersion)
  #if ($extensionNamespace.startsWith('wiki:'))
    #set ($wikiName = $extensionNamespace.substring(5))
  #else
    #set ($wikiName = $xcontext.database)
  #end
  #set ($discard = $services.extension.xar.repairInstalledExtension($extensionId, $extensionVersion, $wikiName))
  #handleExtensionJobStartFailure('extensions.install.error.repairXarFailure' $services.extension.xar)
#end

#macro (diffXarExtension $extensionId $extensionVersion)
  #if ($extensionNamespace.startsWith('wiki:'))
    #set ($wikiName = $extensionNamespace.substring(5))
  #else
    #set ($wikiName = $xcontext.database)
  #end
  #set ($discard = $services.extension.xar.diff($extensionId, $wikiName))
  #handleExtensionJobStartFailure('extensions.install.error.diffXarFailure' $services.extension.xar)
#end

#macro (handleExtensionJobStartFailure $errorMessageKey $scriptService)
  #if ($scriptService)
    #set ($lastError = $scriptService.lastError)
  #else
    #set ($lastError = $extensionManager.lastError)
  #end
  #if ($lastError)
    #set ($errorMessage = $services.localization.render($errorMessageKey, [$extensionId, $extensionVersion]))
    #if ($isAjaxRequest)
      ## Send error back.
      $response.sendError(400, $errorMessage)
    #else
      <div class="errormessage">$errorMessage #printThrowable($lastError)</div>
    #end
  #else
    ## Redirect to extension display.
    $response.sendRedirect("#getExtensionURL($extensionId $extensionVersion {'extensionSection': 'progress'})")
  #end
#end

#macro (determineExtensionStatus $extension $_extensionStatus $_extensionStatusMessage $versionConstraint)
  #set ($currentVersion = $NULL)
  #if (!$jobStatus || !$jobStatus.request.extensions.contains($extension.id))
    #getExtensionJobStatus($extension.id.id $extension.id.version.value $jobStatus)
    #set ($jobState = $jobStatus.state)
  #end
  #if ($jobStatus && $jobState != 'FINISHED')
    #set ($status = 'loading')
  #else
    #if ($extension.isInstalled($extensionNamespace))
      ## Determine if the extension is still valid.
      #if ("$!extension.isValid($extensionNamespace)" == 'false')
        #set ($status = 'installed-invalid')
      #elseif ($extension.isDependency($extensionNamespace))
        #set ($status = 'installed-dependency')
      #else
        #set ($status = 'installed')
      #end
    #elseif ($extension.repository.descriptor.id == 'core')
      #set ($status = 'core')
    #else
      ## An extension, either local or remote, that might be available to install.
      ## Check if a different version of this extension is installed or is a core dependency.
      #set ($currentVersion = $services.extension.core.getCoreExtension($extension.id.id))
      #if (!$currentVersion)
        #getInstalledExtension($extension $extensionNamespace $currentVersion)
      #end
      #if ($currentVersion)
        #set ($status = "#determineVersionCompatibility($extension $currentVersion $versionConstraint)")
      #else
        #set ($status = 'remote')
      #end
    #end
  #end
  #set ($message = $NULL)
  #if ($status != 'remote' && $status != 'loading')
    #set ($message = $services.localization.render("extensions.info.status.${status}", [$currentVersion.id.version.value]))
  #end
  #set ($_extensionStatus = $NULL)
  #setVariable ("$_extensionStatus" $status)
  #set ($_extensionStatusMessage = $NULL)
  #setVariable ("$_extensionStatusMessage" $message)
#end

#macro (determineVersionCompatibility $alice $bob $versionConstraint)
#set ($prefix = '')
#if (!$alice.id.equals($bob.id))
#set ($prefix = 'remote-')
#end
#set ($suffix = '')
#if ($versionConstraint && !$versionConstraint.isCompatible($bob.id.version))
#set ($suffix = '-incompatible')
#elseif ("$!bob.isValid($extensionNamespace)" == 'false')
#set ($suffix = '-invalid')
#elseif ($bob.isDependency($extensionNamespace))
#set ($suffix = '-dependency')
#end
${prefix}${bob.repository.descriptor.id}${suffix}##
#end

#macro (getExtensionJobStatus $extensionId $extensionVersion $return)
  ## Retrieve the job status for the current wiki and for the entire farm (if the current wiki is the main wiki).
  #getExtensionJobStatusForNamespace($extensionId $extensionVersion $extensionNamespace $jobStatusForWiki)
  #if ($xcontext.isMainWiki())
    #getExtensionJobStatusForNamespace($extensionId $extensionVersion $NULL $jobStatusForFarm)
  #end
  #set ($return = $NULL)
  #if (!$jobStatusForWiki)
    #setVariable ("$return" $jobStatusForFarm)
  #elseif (!$jobStatusForFarm)
    #setVariable ("$return" $jobStatusForWiki)
  ## Return the most recent job status.
  ## No start date means the job was scheduled but hasn't started yet.
  #elseif (!$jobStatusForFarm.startDate || ($jobStatusForWiki.startDate
    && $jobStatusForFarm.startDate.after($jobStatusForWiki.startDate)))
    #setVariable ("$return" $jobStatusForFarm)
  #else
    #setVariable ("$return" $jobStatusForWiki)
  #end
#end

#macro (getExtensionJobStatusForNamespace $extensionId $extensionVersion $extensionNamespace $return)
  ## Retrieve the latest job status stored for the specified extension.
  #set ($_jobStatus = $extensionManager.getExtensionJobStatus($extensionId, $extensionNamespace))
  #if ($_jobStatus)
    ## Check if the job status matches the extension version.
    #set ($targetVersion = $_jobStatus.request.extensions.get(0).version)
    #if (!$targetVersion)
      ## Some jobs don't require the extension version. Let's determine the currently available version.
      #set ($_extension = $services.extension.resolve($extensionId, $extensionVersion))
      #getInstalledExtension($_extension $extensionNamespace $installedExtension)
      #set ($targetVersion = $installedExtension.id.version)
    #end
    #if ($targetVersion && $extensionVersion != $targetVersion.value)
      #set ($_jobStatus = $NULL)
    #end
  #end
  ## Retrieve the latest plan stored for the specified extension.
  #set ($_plan = $extensionManager.getExtensionPlanJobStatus($extensionId, $extensionNamespace))
  #if ($_plan)
    ## Check if the plan matches the extension version.
    #set ($targetVersion = $_plan.request.extensions.get(0).version)
    #if (!$targetVersion)
      ## Some jobs don't require the extension version. Let's determine the currently available version.
      #set ($_extension = $services.extension.resolve($extensionId, $extensionVersion))
      #getInstalledExtension($_extension $extensionNamespace $installedExtension)
      #set ($targetVersion = $installedExtension.id.version)
    #end
    #if ($targetVersion && $extensionVersion != $targetVersion.value)
      #set ($_plan = $NULL)
    #end
  #end
  #set ($return = $NULL)
  #if (!$_jobStatus)
    #setVariable ("$return" $_plan)
  #elseif (!$_plan)
    #setVariable ("$return" $_jobStatus)
  ## Return the most recent one between the job status and the plan.
  ## No start date means the job/plan was scheduled but hasn't started yet.
  #elseif (!$_jobStatus.startDate || ($_plan.startDate && $_jobStatus.startDate.after($_plan.startDate)))
    #setVariable ("$return" $_jobStatus)
  #else
    #setVariable ("$return" $_plan)
  #end
#end

#macro (getInstalledExtension $_extension $namespace $installedExtension)
  #set ($features = [])
  #if ($_extension)
    #set ($discard = $features.addAll($_extension.extensionFeatures))
    #set ($discard = $features.add($_extension.id))
  #end
  #set ($discard = $collectionstool.reverse($features))
  #set ($return = $NULL)
  #foreach ($feature in $features)
    #set ($return = $services.extension.installed.getInstalledExtension($feature.id, $namespace))
    #if ($return)
      #break
    #end
  #end
  #set ($installedExtension = $NULL)
  #setVariable("$installedExtension" $return)
#end

#macro (getExtensionURL $extensionId $extensionVersion $extraParams)
#set ($parameters = {})
##
#if ($extraParams)
  #set ($discard = $parameters.putAll($extraParams))
#end
##
#if ($extensionId)
#set ($discard = $parameters.put('extensionId', $extensionId))
#elseif ($request.extensionId)
#set ($discard = $parameters.put('extensionId', $request.extensionId))
#end
##
#if ($extensionVersion)
#set ($discard = $parameters.put('extensionVersion', $extensionVersion))
#elseif ($request.extensionVersion)
#set ($discard = $parameters.put('extensionVersion', $request.extensionVersion))
#end
##
#if ($request.extensionNamespace)
#set ($discard = $parameters.put('extensionNamespace', $request.extensionNamespace))
#end
##
#if ($xback)
#set ($discard = $parameters.put('xback', $xback))
#elseif ($request.xback)
#set ($discard = $parameters.put('xback', $request.xback))
#end
##
## Copy known parameters.
#foreach ($paramName in ['section', 'xpage', 'viewer'])
#set ($paramValue = $request.getParameter($paramName))
#if ("$!paramValue" != '')
#set ($discard = $parameters.put($paramName, $paramValue))
#end
#end
##
$doc.getURL($xcontext.action, $escapetool.url($parameters))##
#end

#macro (computeXBack)
  #set ($xback = "$!{request.xback}")
  #if ($xback == '')
    #set ($params = '')
    #foreach ($parameterName in $request.parameterNames)
      #if (!$parameterName.startsWith('extension'))
        #foreach ($value in $request.getParameterValues($parameterName))
          #set ($params = "${params}&${parameterName}=${value}")
        #end
      #end
    #end
    #if ($params.length() > 0)
      #set ($params = $params.substring(1))
    #end
    #set ($xback = $doc.getURL($xcontext.action, $params))
  #end
#end

#macro (em_submitButton $value $_name $secondary $extraClassName)
  <span class="buttonwrapper">
    <input type="submit" value="$escapetool.xml($services.localization.render($value))"#if($_name) name="$escapetool.xml($_name)"#end
      class="button#if($secondary) secondary#end#if($extraClassName) $!escapetool.xml($extraClassName)#end"/>
  </span>
#end

#macro (em_linkButton $href $label $extraClassName)
  <span class="buttonwrapper">
    <a href="$escapetool.xml($href)" class="button secondary#if($extraClassName) $!escapetool.xml($extraClassName)#end">
      $services.localization.render($label)
    </a>
  </span>
#end

#macro(extensionActionButtons $extension $action $secondary)
  #extensionActionButtonsWithDisplayHint($extension $action $action $secondary $extraClassName)
#end

#macro(extensionActionButtonsWithDisplayHint $extension $action $displayHint $secondary)
  #set ($isGlobalActionSecondary = $secondary)
  #set ($globalActionExtraClassName = $NULL)
  #if (!$installedExtension || ($installedExtension.isInstalled($extensionNamespace)
    && !$installedExtension.isInstalled($NULL)))
    ## Button that targets only the current wiki.
    #extensionActionButtonWithDisplayHint($action $displayHint $secondary)
    #set ($isGlobalActionSecondary = true)
    ## Indicate that the local action is available.
    #set ($globalActionExtraClassName = 'alternative-action')
  #end
  ## Button that targets the entire farm.
  #extensionActionGlobalButtonWithDisplayHint($action $displayHint, $isGlobalActionSecondary $globalActionExtraClassName)
#end

#macro (extensionActionGlobalButton $action $secondary $extraClassName)
  #extensionActionGlobalButtonWithDisplayHint($action $action $secondary $extraClassName)
#end

#macro (extensionActionGlobalButtonWithDisplayHint $action $displayHint $secondary $extraClassName)
  #if ($xcontext.isMainWiki())
    #extensionActionButtonWithDisplayHint("${action}Globally" "${displayHint}Globally", $secondary $extraClassName)
  #end
#end

#macro (extensionActionButton $action $secondary $extraClassName)
  #extensionActionButtonWithDisplayHint($action $action $secondary $extraClassName)
#end

#macro (extensionActionButtonWithDisplayHint $action $displayHint $secondary $extraClassName)
  #set ($classNames = [])
  #if ($secondary)
    #set ($discard = $classNames.add('secondary'))
  #end
  #if ($extraClassName)
    #set ($discard = $classNames.add($extraClassName))
  #end
  #set ($hintKey = "extensions.actions.${displayHint}.hint")
  <span class="buttonwrapper">
    <button type="submit" name="extensionAction" value="$escapetool.xml($action)"
      #if ($classNames.size() > 0) class="$escapetool.xml($stringtool.join($classNames, ' '))"#end
      #if ($services.localization.get($hintKey)) title="$escapetool.xml($services.localization.render($hintKey))"#end>
      $escapetool.xml($services.localization.render("extensions.actions.${displayHint}"))</button>
  </span>
#end

#macro (wikiHomePageLink $namespace)
#set ($wikiName = $stringtool.removeStart($namespace, 'wiki:'))
#set ($wikiReference = $services.model.createDocumentReference($wikiName, '', '').wikiReference)
#set ($wikiHomeDocumentReference = $services.model.resolveDocument('', 'default', $wikiReference))
#set ($wikiPrettyName = $services.wiki.getById($wikiName).prettyName)
#if ("$!wikiPrettyName.trim()" == '')
#set ($wikiPrettyName = $wikiName)
#end
<a href="$escapetool.xml($xwiki.getURL($wikiHomeDocumentReference))">$escapetool.xml($wikiPrettyName)</a>##
#end

#macro (isShowingExtensionDetails $extension $_showDetails)
  ## Show the extension details if this extension has been explicitely requested.
  #set ($return = "$!request.hideExtensionDetails" != 'true' && $request.extensionId == $extension.id.id
    && $request.extensionVersion == $extension.id.version.value)
  #if (!$return)
    #if (!$jobStatus || !$jobStatus.request.extensions.contains($extension.id))
      #getExtensionJobStatus($extension.id.id $extension.id.version.value $jobStatus)
      #set ($jobState = $jobStatus.state)
    #end
    ## Always show the extension details if the extension has a job waiting.
    #set ($return = $jobState == 'WAITING')
  #end
  #set ($_showDetails = $NULL)
  #setVariable("$_showDetails" $return)
#end

#macro (displayExtensionUpdaterPlan $plan)
  #if ($plan.error)
    #displayExtensionUpdaterPlanStatus($plan)
  #else
    ## Group extensions by status (invalid/outdated) and by namespace.
    #set ($invalid = {})
    #set ($outdated = {})
    ## Iterate the first level nodes from the upgrade plan tree.
    #foreach ($firstLevelNode in $plan.tree)
      #set ($planAction = $firstLevelNode.action)
      #set ($status = $NULL)
      #getInstalledExtension($planAction.extension $planAction.namespace $installedVersion)
      #if (!$installedVersion.isValid($planAction.namespace))
        #set ($status = $invalid)
      ## Check if the latest version has been installed after the upgrade plan was created.
      #elseif ($installedVersion.id.version.value != $planAction.extension.id.version.value)
        #set ($status = $outdated)
      #end
      #if ($status)
        #set ($statusForNamespace = $status.get($planAction.namespace))
        #if (!$statusForNamespace)
          #set ($statusForNamespace = [])
          #set ($discard = $status.put($planAction.namespace, $statusForNamespace))
        #end
        #set ($discard = $statusForNamespace.add($planAction))
      #end
    #end
    ##
    #if ($outdated.isEmpty() && $invalid.isEmpty())
      <div class="successmessage">
        $services.localization.render('platform.extension.updater.noUpdatesAvailable')
      </div>
    #else
      #displayExtensionUpdaterPlanActionByNamespace($invalid 'invalid')
      #displayExtensionUpdaterPlanActionByNamespace($outdated 'outdated')
    #end
  #end
#end

#macro(displayExtensionUpdaterPlanStatus $status)
  ## The status is null after the job is created, until the job is scheduled.
  #set ($isLoading = !$status || ($status.log.isEmpty() && $status.state != 'FINISHED'))
  <div class="extension-body-progress#if ($isLoading) loading#end">
    #if ($status)
      #printStatusLog($status)
    #end
  </div>
#end

#macro(displayExtensionUpdaterPlanActionByNamespace $actionByNamespace $key)
  #if (!$actionByNamespace.isEmpty())
    <div class="xLabel">
      $services.localization.render("platform.extension.updater.${key}ExtensionsLabel")
    </div>
  #end
  ## Sort the extensions by namespace and by their name.
  #set ($items = [])
  #foreach ($entry in $actionByNamespace.entrySet())
    #foreach ($planAction in $entry.value)
      #set ($namespace = $entry.key)
      #set ($namespacePrettyName = $namespace)
      #if ($stringtool.indexOf($namespace, ':') >= 0)
        #set ($namespacePrettyName = $stringtool.substringAfter($namespace, ':'))
        #if ($namespace.startsWith('wiki:'))
          #set ($wikiPrettyName = $services.wiki.getById($namespacePrettyName).prettyName)
          #if ("$!wikiPrettyName.trim()" != '')
            #set ($namespacePrettyName = $wikiPrettyName)
          #end
        #end
      #end
      #set ($extension = $planAction.extension)
      #set ($extensionName = "#displayExtensionName($extension)")
      #set ($discard = $items.add({
        'namespace': $namespace,
        'namespacePrettyName': "$!namespacePrettyName",
        'extension': $extension,
        'extensionName': $extensionName.trim()
      }))
    #end
  #end
  #set ($items = $sorttool.sort($items, ['namespacePrettyName', 'extensionName']))
  ## Paginate the extensions.
  #set ($hasPagination = false)
  ## We don't load the pagination CSS here (noSx:true) because the upgrade plan can be loaded through AJAX so
  ## the Skin Extension hooks might not be available. We load the pagination CSS elsewhere.
  #set ($paginationParams = {
    'defaultItemsPerPage': 5,
    'itemParamName': "${key}FirstIndex",
    'itemsPerPageParamName': "${key}PerPage",
    'noSx': true
  })
  #paginationPrepareParams($paginationParams)
  #set ($hasPagination = $items.size() > $paginationParams.itemsPerPage)
  #set ($indexOutOfRange = $items.size() > 0 && $paginationParams.firstItem >= $items.size())
  #if ($hasPagination || $indexOutOfRange)
    #set ($paginationParams.totalItems = $items.size())
    #set ($requestParams = {})
    #set ($discard = $requestParams.putAll($request.getParameterMap()))
    #set ($discard = $requestParams.remove($paginationParams.itemParamName))
    #set ($discard = $requestParams.remove($paginationParams.itemsPerPageParamName))
    #if ($indexOutOfRange)
      #if ($!request.get("${key}PagingReset"))
        ## it seems we already got redirected. how this?
        #set ($paginationParams.firstItem = 0)
      #else
        #set ($discard = $requestParams.put("${key}PagingReset", "1"))
        #set ($redirectUrl = $doc.getURL($xcontext.action, $escapetool.url($requestParams)))
        #set ($discard = $response.sendRedirect($redirectUrl))
        #stop
      #end
    #else
      #set ($discard = $requestParams.remove("${key}PagingReset"))
    #end
  #end
  #if($hasPagination)
    #set ($paginationParams.url = $doc.getURL($xcontext.action, $escapetool.url($requestParams)))
    #pagination($paginationParams)
  #end
  #set ($lastItem = $mathtool.min($items.size(), $mathtool.add($paginationParams.firstItem,
    $paginationParams.itemsPerPage)))
  #if ($request.get("${key}PagingReset"))
     <div class="box infomessage">$escapetool.xml($services.localization.render('platform.extension.updater.pagingrestart'))</div>
  #end
  #set ($items = $items.subList($paginationParams.firstItem, $lastItem))
  #foreach ($item in $items)
    #if ($foreach.index == 0 || $item.namespace != $extensionNamespace)
      #if ($foreach.index > 0)
        ## Close the previous group of extensions.
        </div>
      #end
      ## Start a new group of extensions.
      <div class="xHint">
        $services.localization.render("platform.extension.updater.${key}ExtensionsHint",
          ["#displayExtensionNamespace($item.namespace)"])
      </div>
      <div class="${key}Extensions">
      #set ($extensionNamespace = $item.namespace)
    #end
    #displayExtension($item.extension)
  #end
  #if ($items.size() > 0)
    ## Close the last group of extensions.
    </div>
  #end
  #if ($hasPagination)
    #set ($paginationParams.position = 'bottom')
    #pagination($paginationParams)
  #end
#end

#macro (getExtensionUpdaterPlan $_plan)
  #set ($localPlan = $extensionManager.getExtensionPlanJobStatus($NULL, $extensionNamespace))
  #if ($xcontext.isMainWiki())
    #set ($globalPlan = $extensionManager.getExtensionPlanJobStatus($NULL, $NULL))
  #else
    #set ($globalPlan = $NULL)
  #end
  #set ($_plan = $NULL)
  #if (!$globalPlan)
    #setVariable ("$_plan" $localPlan)
  #elseif (!$localPlan)
    #setVariable ("$_plan" $globalPlan)
  ## Return the most recent one between the local plan and the global plan.
  ## No start date means the plan was scheduled but hasn't started yet.
  #elseif (!$localPlan.startDate || ($globalPlan.startDate && $localPlan.startDate.after($globalPlan.startDate)))
    #setVariable ("$_plan" $localPlan)
  #else
    #setVariable ("$_plan" $globalPlan)
  #end
#end

#macro(computeExtensionUpdaterPlan $_plan $globally)
  #if ($globally && $xcontext.isMainWiki())
    ## Create the upgrade plan for the entire farm.
    #set ($upgradePlanJob = $extensionManager.createUpgradePlan())
  #else
    ## Create the upgrade plan only for the current wiki.
    #set ($upgradePlanRequest = $extensionManager.createUpgradePlanRequest($extensionNamespace))
    #if ($xcontext.isMainWiki())
      ## From the main wiki we can also upgrade the extensions that are installed globally (on the root namespace).
      #set ($discard = $upgradePlanRequest.addNamespace($NULL))
    #end
    #set ($upgradePlanJob = $extensionManager.createUpgradePlan($upgradePlanRequest))
  #end
  #set ($_plan = $NULL)
  #setVariable("$_plan" $upgradePlanJob.status)
#end

#macro (displayExtensionUpdaterTrigger $plan)
  <form action="$xwiki.relativeRequestURL" method="post">
    <div class="hidden">
      #if ($xcontext.action == 'view' || $xcontext.action == 'admin')
        #set ($docAction = 'get')
      #else
        #set ($docAction = $xcontext.action)
      #end
      ## Compute the URL that will be used to submit the request asynchronously.
      #if ("$!request.section" != '')
        ## Administration section.
        #set ($asyncURL = $xwiki.getURL($request.section, $docAction, $escapetool.url({
          'section': $request.section
        })))
      #else
        ## Stand-alone or Distribution Wizard.
        #set ($asyncURL = $doc.getURL($docAction))
      #end
      <input type="hidden" name="asyncURL" value="$!escapetool.xml($asyncURL)" disabled="disabled" />
      ## Redirect back to view mode after a POST request.
      <input type="hidden" name="xredirect" value="$!escapetool.xml($xwiki.relativeRequestURL)" />
    </div>
    #set ($disabled = $plan && $plan.state != 'FINISHED')
    <p class="buttons dynamic-button-group">
      #if ($xcontext.isMainWiki() && $plan && !$plan.request.hasNamespaces())
        ## The latest upgrade plan was computed for the entire farm so we display the global button first.
        #displayExtensionUpdaterButton('checkForUpdatesGlobally' $disabled)
      #end
      #displayExtensionUpdaterButton('checkForUpdates' $disabled)
      #if ($xcontext.isMainWiki() && (!$plan || $plan.request.hasNamespaces()))
        ## On the main wiki we can also compute the upgrade plan for the entire farm.
        #displayExtensionUpdaterButton('checkForUpdatesGlobally' $disabled)
      #end
    </p>
  </form>
#end

#macro (displayExtensionUpdaterButton $value $disabled)
  <span class="buttonwrapper">
    <button name="action" value="$value"#if ($disabled) disabled="disabled"#end>
      $escapetool.xml($services.localization.render("platform.extension.updater.$value"))
    </button>
  </span>
#end

#macro (extensionUpdater)
  #set ($plan = $NULL)
  #set ($error = $NULL)
  #if ($request.action.startsWith('checkForUpdates'))
    #computeExtensionUpdaterPlan($plan $request.action.equals('checkForUpdatesGlobally'))
    #set ($error = $extensionManager.lastError)
    #if ("$!request.xredirect" != '' && !$error)
      #set ($discard = $response.sendRedirect($request.xredirect))
      #break
    #end
  #end
  #if (!$plan)
    #getExtensionUpdaterPlan($plan)
  #end
  #if (!$isAjaxRequest)
    ## The list of outdated/invalid extensions is paginated so we load the CSS here because the request is not AJAX.
    ## Make sure the browser won't keep the same version of the resource in cache from one version of XWiki to another
    #set ($discard = $xwiki.ssfx.use('uicomponents/pagination/pagination.css', {'forceSkinAction': true, 'version': $environmentVersion}))
    <p class="noitems">
      $escapetool.xml($services.localization.render('extension.updater.hint'))
    </p>
    #displayExtensionUpdaterTrigger($plan)
  #end
  <div class="extensionUpdater">
    #if ($error)
      <div class="errormessage">
        $services.localization.render('platform.extension.updater.createUpgradePlanFailure')
        #printThrowable($error)
      </div>
    #elseif ($plan.state == 'FINISHED')
      <div class="xHint">
        $escapetool.xml($services.localization.render('platform.extension.updater.lastCheckDate',
          [$xwiki.formatDate($plan.endDate)]))
      </div>
      #displayExtensionUpdaterPlan($plan)
    #elseif ($plan)
      <div class="xHint">$services.localization.render('platform.extension.updater.loading')</div>
      #displayJobProgressBar($plan)
      #displayExtensionUpdaterPlanStatus($plan)
    #end
  </div>
#end

Zerion Mini Shell 1.0