Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Copying Page Trees for Regular Users

ScriptRunner for Confluence has a built-in script for copying a tree of pages that's available for Confluence Administrators and Space Administrators, but what if you wanted to create something like that for regular Confluence users?

The answer actually requires two ScriptRunner features to work together.

First, you would need a custom REST Endpoint to copy a page. Second, you'd need a custom Web Item to add a button that would call to your REST Endpoint.

There are many use cases around this, but let's consider one specific one: you want to let people copy a tree of pages to their personal space.

The code below demonstrates how a REST Endpoint could copy a tree of pages. It's deliberately oversimplified (it has minimal validation, it doesn't copy labels, attachments, comments, etc.), but it's a useful starter.

import com.atlassian.confluence.core.DefaultSaveContext
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.setup.settings.SettingsManager
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.sal.api.transaction.TransactionTemplate
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import groovy.transform.Field

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

@Field
PageManager pageManager = ComponentLocator.getComponent(PageManager)

@Field
SpaceManager spaceManager = ComponentLocator.getComponent(SpaceManager)

@Field
SettingsManager settingsManager = ComponentLocator.getComponent(SettingsManager)

copyPageTree(
    httpMethod: "GET",
    groups: ["confluence-users"]
) { MultivaluedMap queryParams, String body ->
    def pageId = queryParams.getFirst("page")?.toString()?.toInteger()
    def sourcePage = pageManager.getPage(pageId)

    def user = AuthenticatedUserThreadLocal.get()
    def personalSpace = spaceManager.getPersonalSpace(user)

    def newPage = copyPage(sourcePage, personalSpace.homePage)
    copyPages(sourcePage.children, newPage)

    def baseUrl = settingsManager.getGlobalSettings().getBaseUrl()
    def redirect = new URI("${baseUrl}${newPage.urlPath}")
    log.debug "Redirect: ${redirect}"

    return Response.temporaryRedirect(redirect).build() //to new page
}

void copyPages(List pages, Page targetPage) {
    log.debug "Copying pages: ${pages*.title} to ${targetPage.title}"
    pages.each { sourcePage ->
        def clonePage = copyPage(sourcePage, targetPage)
        copyPages(sourcePage.children, clonePage)
    }
}

Page copyPage(Page sourcePage, Page targetPage) {
    def transactionTemplate = ScriptRunnerImpl.scriptRunner.getBean(TransactionTemplate)
    transactionTemplate.execute {
        def extantPage = pageManager.getPage(targetPage.space.key, sourcePage.title)
        if (!extantPage) {
            def newPage = new Page()
            newPage.with {
                setTitle(sourcePage.title)
                setSpace(targetPage.space)
                setParentPage(targetPage)
                setBodyAsString(sourcePage.bodyAsString)
                setCreator(sourcePage.creator)
                setContentStatus(sourcePage.getContentStatus())
                setCreationDate(sourcePage.getCreationDate())
            }
            targetPage.addChild(newPage)
            pageManager.saveContentEntity(newPage, DefaultSaveContext.BULK_OPERATION)
            return newPage
        } else {
            return extantPage
        }
    }
}

 

The Web Item's configuration would look something like the attached image.

custom_web_item.png

Two important bits of that configuration are worth a special mention: the URL and the section.

The section selected (system.content.action/modify) will add our custom web item to the three-dot menu on the upper right corner of a Confluence page.

The URL will point to the custom REST Endpoint we setup earlier and provide the current page ID as a parameter. /rest/scriptrunner/latest/custom/copyPageTree?page=${page.id}

 

2 comments

Hi,

I have a question to this code.

What does the line

def transactionTemplate = ScriptRunnerImpl.scriptRunner.getBean(TransactionTemplate)

 do? Is this necessary when modifying content in Confluence via ScriptRunner? I have not seen this in the documentation or examples so far.

Regards,

Kurt

Like Jonny Carter likes this

@Kurt Rosivatz- good question. Without getting too much into the weeds, the way we're using it isolates each page's creation to its own database transaction. That can be important to making sure the script doesn't fail in some scenarios.

You can read a bit more about the TransactionTemplate at https://docs.atlassian.com/sal-api/3.1.2/sal-api/apidocs/com/atlassian/sal/api/transaction/TransactionTemplate.html. You may also benefit from reading about the class that it's based off of from the Spring Framework API docs. https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/support/TransactionTemplate.html

I wouldn't recommend using it in every single script without thinking. In this use case (bulk page creation), it's known to resolve some potential problems.

Like Kurt Rosivatz likes this

Comment

Log in or Sign up to comment
TAGS
Community showcase
Published in Confluence

Confluence Mythbusters: Does Atlassian even use Confluence?

Hi, Confluence collaborators! As part of #Confluence-Collaboratory month, we’ve created a very special Mythsbusters segment, where we're dive into an interesting myth and uncover the truth behind i...

1,690 views 7 31
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you