Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
4,366,304
Community Members
 
Community Events
168
Community Groups

Customize pdf export using Script Runner for Confluence Server

Edited

Hi, 

I have made some customization of pdf export of a Confluence page. Sharing my approach here for others to try/benefit.

My Setup:

  • Confluence Server - 7.3.2
  • Script Runner for Confluence - 6.2.0-p5
  • Table Filter and Charts for Confluence - 5.3.25
  • Confluence has HTML macro enabled

My Approach:

  • Create Custom Menu to do the job
  • Create a temporary page which can be trashed once export job is done
  • Get the page content as string, customize it (in my case remove jira issues hyperlinks)
  • Edit the temporary page and update it with customized content, redirect the page;
  • With delay from javascript redirect it to pdf export link again

My Configurations

  • In Confluence Script Runner - Fragment -Create Script Fragments - Custom WebItem
    • What Section this should go in - system.content.action/secondary
    • Key: external-pdf
    • Menu Text - Custom
    • Weight - 1
    • Condition (such that this custom menu is displayed only in some pages)
      • if(context.space.key == "SOME_SPACE_KEY" && context.page.title.contains("SOME UNIQ TITLE STRING ") && !(context.page.title.contains("NEGATE TITLE"))) {
        return true
        } else {
        return false
        }
    • Do What - Navigate to Link
    • Link - https://HOST/wiki/rest/scriptrunner/latest/custom/exportPage_toPDF?spaceKey=<SOME SPACE KEY>&parentPageTitle=${page.title}
  • In Confluence Script Runner - Jobs - setup to trash the temporary page
    • import com.atlassian.sal.api.component.ComponentLocator
      import com.atlassian.confluence.spaces.Space
      import com.atlassian.confluence.spaces.SpaceManager
      import com.atlassian.confluence.core.DefaultDeleteContext
      import com.atlassian.confluence.pages.Page
      import com.atlassian.confluence.pages.PageManager

      def spaceManager = ComponentLocator.getComponent(SpaceManager)
      def pageManager = ComponentLocator.getComponent(PageManager)

      def parentPage=pageManager.getPage("SOME_SPACE_KEY", "TO DELETE PAGES")
      def childPages = pageManager.getDescendants(parentPage)

      if(childPages) {
           childPages.each { thisChild ->
                     pageManager.trashPage(thisChild, DefaultDeleteContext.DEFAULT)
            }
      }

  • In Confluence Script Runner - REST Endpoint
    • //This script is loaded in Confluence - Script Runner - Rest End Point
      //It takes the spaceKey and page title as url parameters
      //gets page content
      //replacesAll href of jira issue links
      //relacesAll Back to Top CDATA
      //creates a child page from replaced string
      //redirects that child page for pdf download after the page is loaded in browser

      /*import org.apache.log4j.Level
      import org.apache.log4j.Logger
      def logx = Logger.getLogger("com.acme.workflows")
      logx.setLevel(Level.DEBUG)*/

      import org.apache.http.HttpRequest
      import org.apache.http.protocol.HttpContext
      import groovy.json.JsonBuilder
      import groovy.json.JsonSlurper
      import org.apache.http.HttpRequestInterceptor
      import groovyx.net.http.HTTPBuilder
      import net.sf.json.JSONArray
      import groovyx.net.http.RESTClient
      import static groovyx.net.http.Method.GET
      import static groovyx.net.http.ContentType.JSON
      import groovy.transform.Field

      import org.codehaus.groovy.runtime.MethodClosure
      import org.apache.http.HttpEntity;
      import org.apache.http.HttpEntityEnclosingRequest

      import java.io.InputStreamReader

      import com.atlassian.confluence.setup.settings.SettingsManager
      import com.atlassian.confluence.core.BodyContent
      import com.atlassian.confluence.core.BodyType
      import com.atlassian.confluence.core.DefaultSaveContext
      import com.atlassian.confluence.core.DefaultDeleteContext
      import com.atlassian.confluence.pages.DuplicateDataRuntimeException
      import com.atlassian.confluence.pages.Page
      import com.atlassian.confluence.pages.PageManager
      import com.atlassian.confluence.pages.templates.PageTemplateManager
      import com.atlassian.confluence.security.Permission
      import com.atlassian.confluence.security.PermissionManager
      import com.atlassian.confluence.spaces.Space
      import com.atlassian.confluence.spaces.SpaceManager
      import com.atlassian.confluence.user.AuthenticatedUserThreadLocal
      import com.atlassian.sal.api.component.ComponentLocator
      import com.onresolve.scriptrunner.canned.confluence.utils.PermissionDeniedException
      import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
      import groovy.json.JsonOutput
      import groovy.transform.BaseScript
      import groovy.transform.Field
      import org.apache.log4j.Logger

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

      import java.text.SimpleDateFormat

      import groovy.time.TimeCategory
      import groovy.time.TimeDuration


      Date start = new Date()

      @Field SpaceManager spaceManager = ComponentLocator.getComponent(SpaceManager)
      @Field PageManager pageManager = ComponentLocator.getComponent(PageManager)
      @Field PermissionManager permissionManager = ComponentLocator.getComponent(PermissionManager)
      @Field PageTemplateManager pageTemplateManager = ComponentLocator.getComponent(PageTemplateManager)
      //@Field Logger log = Logger.getLogger("com.onresolve.scriptrunner.runner.ScriptRunnerImpl")

      @BaseScript CustomEndpointDelegate delegate

      //End point name is the same as the method's name
      //exportPage_toPDF_withoutJira_hyperLinks(httpMethod: "GET", groups: ["confluence-administrators", "confluence-users"]) { MultivaluedMap queryParams, String body ->
      exportPage_toPDF_withoutJira_hyperLinks(httpMethod: "GET") { MultivaluedMap queryParams, String body ->

      Date date = new Date()
      String datePart = date.format("dd/MMM/yyyy")
      String timePart = date.format("HH:mm:ss.SSS")
      String dateTimeStamp = date.format("dd/MMM/yyyy HH:mm:ss.SSS")

      pageManager = ComponentLocator.getComponent(PageManager)

      def spaceKey = queryParams.getFirst("spaceKey").toString()
      def parentPageTitle = queryParams.getFirst("parentPageTitle").toString()

      try {
      parentPage=pageManager.getPage(spaceKey, parentPageTitle)
      def contentEntityObject = parentPage.getEntity()
      def parentPageBody=parentPage.getBodyAsString()

      def space = spaceManager.getSpace(spaceKey) as Space
      def childPageTitle = parentPageTitle + " - External"
      def childPage = pageManager.getPage(spaceKey, childPageTitle)

      if(childPage) {
      pageManager.trashPage(childPage, DefaultDeleteContext.DEFAULT)
      }

      def HTML_A_HREF_TAG_PATTERN_jira = "\\s*(?i)href\\s*=\\s*\"([^\"]*jira/browse/[^\"]*\")"
      def regex_BacktoTop = "<p><ac:link ac:anchor=\"_wiki_toc\"><ri:content-entity ri:content-id=\"\\d+\" /><ac:plain-text-link-body><!\\[CDATA\\[Back to Top\\]\\]></ac:plain-text-link-body></ac:link></p>"


      def childPageContent = parentPageBody.replaceAll(HTML_A_HREF_TAG_PATTERN_jira, "dummy")
      childPageContent = childPageContent.replaceAll(regex_BacktoTop, "")


      def regex_jiraMacro = "<p style=\"font-size: 15.0px;\"><strong>G3 Issue used to create this Release Page</strong></p><p><ac:structured-macro ac:name=\"jira\" ac:schema-version=\"\\d+\" ac:macro-id=\"([a-zA-Z0-9*--]+)\"><ac:parameter ac:name=\"server\">Jira</ac:parameter><ac:parameter ac:name=\"serverId\">([a-zA-Z0-9*--]+)</ac:parameter><ac:parameter ac:name=\"key\">((?<!([A-Za-z]{1,10})-?)[A-Z]+-\\d+)</ac:parameter></ac:structured-macro></p>"
      childPageContent = childPageContent.replaceAll(regex_jiraMacro, "")

      def regex_history = "<h1>Document history</h1><p><ac:structured-macro ac:name=\"version-history\" ac:schema-version=\"\\d+\" ac:macro-id=\"([a-zA-Z0-9*--]+)\" /></p>"
      childPageContent = childPageContent.replaceAll(regex_history, "")

      def toDelete_parentPage=pageManager.getPage(spaceKey, "TO DELETE PAGES")

      def createdChildPageId = createBasicPage(space, toDelete_parentPage, childPageTitle, "Temporary Page")

      def tableFilterString = "<ac:structured-macro ac:name=\"table-filter\" ac:schema-version=\"1\""

      def loadResources = "" //tf-export-ready is Table filter element

      if(childPageContent.contains(tableFilterString)) {
      loadResources = "<ac:structured-macro ac:name=\"html\">\
      <ac:plain-text-body><![CDATA[\
      <script type=\"text/javascript\">\
      AJS.toInit(function() {\
      AJS.\$('#editPageLink').hide();\
      AJS.bind('tf-export-ready', function() {\
      setTimeout(function() {AJS.\$(location).prop('href', 'https://HOST/wiki/spaces/flyingpdf/pdfpageexport.action?pageId=" + createdChildPageId + "');}, 2000);\
      });\
      });\
      </script>]]>\
      </ac:plain-text-body>\
      </ac:structured-macro>"
      } else {
      //removed AJS.bind('tf-export-ready', function() {\
      loadResources = "<ac:structured-macro ac:name=\"html\">\
      <ac:plain-text-body><![CDATA[\
      <script type=\"text/javascript\">\
      AJS.toInit(function() {\
      AJS.\$('#editPageLink').hide();\
      setTimeout(function() {AJS.\$(location).prop('href', 'https://HOST/wiki/spaces/flyingpdf/pdfpageexport.action?pageId=" + createdChildPageId + "');}, 2000);\
      });\
      </script>]]>\
      </ac:plain-text-body>\
      </ac:structured-macro>"
      }

      childPageContent = childPageContent.replaceAll("dummy", "") + loadResources

      editPage("${createdChildPageId}", childPageTitle, childPageContent, "2")

      pageUrl = "https://HOST/wiki/pages/viewpage.action?pageId=${createdChildPageId}"
      return Response.temporaryRedirect(URI.create(pageUrl)).build()
      } catch(Exception ex) {
      //pageIdMap["Exception"] = "Exception to get page or append page for spaceKey = ${spaceKey} and title = ${title}: ${ex.toString()}"
      }
      }

      /**
      * Create a basic page. This is not linked in any hierarchy.
      *
      * @param space The space that this page belongs to.
      * @param title The title of the page we are creating.
      * @param content The content of the page we are creating.
      *
      * @return The create page object.
      */
      def createBasicPage(Space space, Page parentPage, String title, String content) {
      def pageManager = ComponentLocator.getComponent(PageManager)
      def page = new Page()
      def bodyContent = new BodyContent(page, content, BodyType.XHTML)
      page.with {
      setVersion(1)
      setSpace(space)
      setTitle(title)
      setBodyContent(bodyContent)
      setCreator(AuthenticatedUserThreadLocal.get())
      }
      linkPages(parentPage, page)
      pageManager.saveContentEntity(page, DefaultSaveContext.SUPPRESS_NOTIFICATIONS)

      return page.id
      }

      /**
      * Link a parent and a child page together. This method creates a bi-directional relationship between the two pages.
      *
      * @param parent The parent page that we wish to link.
      * @param child The child page that we wish to link.
      */
      void linkPages(Page parent, Page child) {
      // Set the parent page on the child
      child.setParentPage(parent)
      // Set the child page on the parent
      parent.addChild(child)
      // Set the ancestors on the child page
      def ancestors = []
      def parentPageAncestors = parent.ancestors as List
      if (parentPageAncestors) {
      ancestors.addAll(parentPageAncestors)
      }
      ancestors.add(parent)
      child.setAncestors(ancestors)
      }


      void editPage(String pageId, String pageTitle, String pageContent, String pageVersion) {
      //https://community.atlassian.com/t5/Confluence-questions/How-to-edit-the-page-content-using-rest-api/qaq-p/904345
      //def logx = Logger.getLogger("com.acme.workflows")
      //logx.setLevel(Level.DEBUG)

      def pageManager = ComponentLocator.getComponent(PageManager)

      def params = [
      id: pageId,
      type: "page",
      title: pageTitle,
      body: [storage:[value:pageContent, representation:"storage"]],
      version: [number:pageVersion]
      ]

      def jsonString = new JsonBuilder(params).toString()

      def rest_api_str = "https://HOST/wiki/rest/api/content/${pageId}"
      def http = new HTTPBuilder(rest_api_str)
      try { def json = http.request(groovyx.net.http.Method.PUT, groovyx.net.http.ContentType.JSON) { req ->
      req.addHeader('Authorization', 'Basic ' + 'USER:PASSWORD'.bytes.encodeBase64().toString())
      body = jsonString
      response.success = { resp, jsonData ->
      assert resp.status == 200
      return jsonData
      }
      response.failure = { resp ->
      return resp.status
      }
      }
      } catch(Exception ex) {

      }
      }

       

       

 

0 comments

Comment

Log in or Sign up to comment
TAGS

Atlassian Community Events