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

Next challenges

Recent achievements

  • Global
  • Personal

Recognition

  • Give kudos
  • Received
  • Given

Leaderboard

  • Global

Trophy case

Kudos (beta program)

Kudos logo

You've been invited into the Kudos (beta program) private group. Chat with others in the program, or give feedback to Atlassian.

View group

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

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
Community showcase
Published in Confluence

Announcing Team Calendars in Confluence Data Center

Hi Community! We're thrilled to share that Team Calendars for Confluence is now a built-in feature for Confluence Data Center releases 7.11 and beyond.  A long time favorite,  Team Cale...

198 views 0 6
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