December 13, 2016
Development

Jira - Confluence connector in PHP - Part II

Image

In the first part of this post we covered how we have extracted information from JIRA, how we implemented the logics and processing in the middleware, and then integrated with Confluence, to generate a human digestible report.

Image

Create pages in Confluence

In this part I will talk about creating HTML pages in Confluence using PHP and Confluence REST API. 
The documentation of REST API can be found here.
 

Before I start, I must say thank you to the writers of these articles:

The task

In this task we had to create a tree of pages with the quality score (QS) of each product based on the QS of projects belonging to the product for every quarter, and every year, for four years back. All the QS’s had to be supported with the detailed result of calculations.

The solution

After finishing the calculations described in the first part I could find all the results in a subfolder, in separated files in JSON format.
 

I had plenty of files to process, so I choose the same way of multiprocessing as I did in the first part: The logic was to put into the present.php file ($date is the date of calculations in ‘Y-m-d’ format):
 

  1. $cmd = "php present.php $date >/dev/null &";
  2. exec($cmd);


Within this present.php, I read all files containing the results of calculations and created Confluence pages in the desired structure. To achieve the goal, I created a class called Page with some basic functions to create, delete and update Confluence pages.
 

  1. class Page {
  2.   /**
  3.   * @var array
  4.   * - 'title'
  5.   * - 'spaceKey'
  6.   * - 'expand'
  7.   */
  8.   static protected $query;
  9.   /**
  10.   * The name of the space.
  11.   * @var string
  12.   */
  13.   static protected $spaceKey;
  14.   /**
  15.   * The title of the page.
  16.   *
  17.   * @var string
  18.   */
  19.   static protected $title;
  20. 
     
  21.   /**
  22.   * Page constructor
  23.   * 
  24.   * @param string $space_key
  25.   *   The space key.
  26.   * @param string $title
  27.   *   The page title.
  28.   */
  29.   public function __construct($space_key, $title) {
  30.     self::$spaceKey = $space_key;
  31.     self::$title = $title;
  32.   }
  33. 
     
  34.   /**
  35.   * {@inheritdoc}
  36.   */
  37.   public static function init($space_key, $title) {
  38.     return new Page($space_key, $title);
  39.   }
  40. 
     
  41.   (...)
  42. }


If you want to create a page in Confluence, you will need the root or parent page ID.  To get any page ID, you need to know the page title and the space name where the page is found. At this point you can come to the conclusion that within a space all document titles must be unique.
 

  1.   /**
  2.   * Get the page id with $title in the $space.
  3.   *
  4.   * @param $space_key
  5.   *   The space key.
  6.   * @param $title
  7.   *   The title.
  8.   *
  9.   * @return bool|mixed
  10.   */
  11.   public static function getId() {
  12.     $result = FALSE;
  13.     $page = self::get();
  14.     if (isset($page['id'])) {
  15.       $result = $page['id'];
  16.     }
  17.     return $result;
  18.   }
  19. 
     
  20.   /**
  21.   * Get the page with $title in the space with $space_key.
  22.   */
  23.   public static function get($space_key, $title) {
  24.     $page = self::extract();
  25. 
     
  26.     return $page;
  27.   }
  28. 
     
  29.   /**
  30.   * We get the page content here.
  31.   */
  32.   protected static function extract() {
  33.     $query = array(
  34.       'title' => self::$title,
  35.       'spaceKey' => self::$spaceKey,
  36.       'expand' => 'space,body.view,version,container',
  37.     );
  38. 
     
  39.     $host = YOUR_HOST;
  40.     $api_uri = YOUR_API_URI;
  41.     $url = $host . $api_uri . '?' . http_build_query($query);
  42.     $response = self::exec($url);
  43.     $result = array();
  44.     if (isset($response['results']) && is_array($response['results'])) {
  45.       $result = reset($response['results']);
  46.     }
  47.     return $result;
  48.   }
  49. 
     
  50.   static function createUrl($path, array $query = array()) {
  51.     $host = YOUR_HOST;
  52.     $api_uri = YOUR_API_URI;
  53. 
     
  54.     $result = $host . $api_uri . '/';
  55.     if (!empty($path)) {
  56.       $result .= $path;
  57.     }
  58.     if (!empty($query)) {
  59.       $result .= '?' . http_build_query($query);
  60.     }
  61.     return $result;
  62.   }


Now that we can have the page ID of our root page, it’s time to add a page to Confluence.
 

  1.   /**
  2.   * Add a page with $title and content of $body under page of $ancestor_id within space of 
  3.   * $space_key.
  4.   */
  5.   public static function add($space_key, $ancestor_id, $title, $body = '') {
  6.     $data = array(
  7.       'type' => 'page',
  8.       'title' => $title,
  9.       'space' => array('key' => $space_key,),
  10.       'ancestors' => array(
  11.         array(
  12.           'type' => 'page',
  13.           'id' => $ancestor_id,
  14.         ),
  15.       ),
  16.       'body' => array(
  17.         'storage' => array(
  18.           'value' => $body,
  19.           'representation' => 'storage',
  20.         )
  21.       ),
  22.     );
  23.     $url = self::createUrl('content');
  24.     $result = self::post($data, $url);
  25. 
     
  26.     return $result;
  27.   }


Parameter $body in the function above could contain, for example, a table. 
To use your own CSS file in a Confluence space you need permission from the sysadmin . It’s unlikely you will get it, so it’s better to use inline CSS, as shown in the first row of the example below. 

To show a nicely formatted table in Confluence the best way is to use the theme given by Confluence, so a table could be formatted like this:
 

  1. $body = '<table class="confluenceTable" style="text-align: center;">';
  2. $body .= '<thead>';
  3. $body .= '<th class="confluenceTh">1st column title</th>
  4. <th class="confluenceTh">2nd column title</th>
  5. <th class="confluenceTh">3rd column title</th>';
  6. $body .= '</thead>';
  7. $body .= '<tbody>';
  8. $body .= '<tr>';
  9. $body .= '<td class="confluenceTd">' . some_data_here . '</td>';
  10. $body .= '<td class="confluenceTd">' . some_data_here . '</td>';
  11. $body .= '<td class="confluenceTd">' . some_data_here  . '</td>';
  12. $body .= '</tr>'
  13. $body .= '</tbody>';
  14. $body .= '</table>';


To add a link to your table, you can adapt the following code snippet to your needs:
 

  1. $href = urlencode("$date $product_id");
  2. $body .= '<td class="confluenceTd"><a class="confluence-link" data-linked-resource-type="page" href="' . $href . '"><strong>' . $project_id . '</strong></a></td>';


In order to complete our example, we need to add the post function to our class.
 

  1.  private static function post($data, $url) {
  2.     $json = json_encode($data);
  3.     $username = USER_NAME;
  4.     $password = USER_PASSWORD;
  5. 
     
  6.     $curl = curl_init();
  7.     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
  8.     curl_setopt($curl, CURLOPT_USERPWD, "$username:$password");
  9.     curl_setopt($curl, CURLOPT_URL, $url);
  10.     curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
  11.     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
  12.     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
  13.     curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
  14.     curl_setopt($curl, CURLOPT_POST, TRUE);
  15.     curl_setopt($curl, CURLOPT_POSTFIELDS, $json);
  16. 
     
  17.     $response = curl_exec($curl);
  18. 
     
  19.     $result = self::getResult($response, $curl, $url);
  20.     return $result;
  21.   }


In the getResult function, we just handle the response and the possible errors. In case of success, we get the response in json format. For more details about the getResult function, see part I.

If you would like to attach an image, you could do something like this:
 

  1.   public static function addImage($page_id, $filename, $comment = '') {
  2.     $data = array(
  3.       'file' => '@' . $filename,
  4.     );
  5.     $json = json_encode($data);
  6. 
     
  7.     self::initQuery();
  8.     $url = self::createUrl('content/' . $page_id . '/child/attachment');
  9.     $username = USER_NAME;
  10.     $password = USER_PASSWORD;
  11. 
     
  12.     $curl = curl_init();
  13.     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
  14.     curl_setopt($curl, CURLOPT_USERPWD, "$username:$password");
  15.     curl_setopt($curl, CURLOPT_URL, $url);
  16.     curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
  17.     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
  18.     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
  19.     curl_setopt($curl, CURLOPT_SAFE_UPLOAD, FALSE);
  20.     curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: image/png'));
  21.     curl_setopt($curl, CURLOPT_POST, TRUE);
  22.     curl_setopt($curl, CURLOPT_POSTFIELDS, $json);
  23. 
     
  24.     $response = curl_exec($curl);
  25. 
     
  26.     $result = self::getResult($response, $curl, $url);
  27. 
     
  28.     return $result;
  29.   }


To update an existing page, first you will get it’s current version, then increase it by one.
 

  1. public static function getVersion($space_key, $title) {
  2.     $result = FALSE;
  3.     $page = self::get($space_key, $title);
  4.     if (isset($page['version']['number'])) {
  5.       $result = $page['version']['number'];
  6.     }
  7.     return $result;
  8.   }
  9. 
     
  10.   public static function update($space_key, $ancestor_id, $page_id, $title, $body) {
  11.     $version = self::getVersion($space_key, $title);
  12.     $version++;
  13.     $data = array(
  14.       'id' => $page_id,
  15.       'type' => 'page',
  16.       'title' => $title,
  17.       'space' => array('key' => self::$spaceKey,),
  18.       'ancestors' => array(
  19.         array(
  20.           'type' => 'page',
  21.           'id' => $ancestor_id,
  22.         ),
  23.       ),
  24.       'body' => array(
  25.         'storage' => array(
  26.           'value' => $body,
  27.           'representation' => 'storage',
  28.         )
  29.       ),
  30.       'version' => array(
  31.         'number' => $version,
  32.       ),
  33.     );
  34.     $url = self::createUrl('content/' . $page_id);
  35. 
     
  36.     $result = self::put($data, $url);
  37.     return $result;
  38.   }


In the update function, instead of post we use put.
 

  1.  private static function put($data, $url) {
  2.     $json = json_encode($data);
  3.     $username = USER_NAME;
  4.     $password = USER_PASSWORD;
  5. 
     
  6.     $curl = curl_init();
  7.     curl_setopt($curl, CURLOPT_URL, $url);
  8.     curl_setopt($curl, CURLOPT_USERPWD, "$username:$password");
  9.     curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Content-Length: ' . strlen($json)));
  10.     curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
  11.     curl_setopt($curl, CURLOPT_POSTFIELDS, $json);
  12.     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
  13.     $response = curl_exec($curl);
  14. 
     
  15.     $result = self::getResult($response, $curl, $url);
  16.     return $result;
  17.   }


If you want to delete a single page, you need to provide its ID.
 

  1.  public static function delete($id) {
  2.     $url = self::createUrl("content/$id");
  3. 
     
  4.     $username = USER_NAME;
  5.     $password = USER_PASSWORD;;
  6. 
     
  7.     $curl = curl_init();
  8.     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
  9.     curl_setopt($curl, CURLOPT_USERPWD, "$username:$password");
  10.     curl_setopt($curl, CURLOPT_URL, $url);
  11.     curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
  12.     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
  13.     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
  14.     curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
  15. 
     
  16.     $response = curl_exec($curl);
  17. 
     
  18.     $result = self::getResult($response, $curl, $url);
  19. 
     
  20.     return $result;
  21.   }


And how do you use all of this? Here are some examples:
 

  1. // Getting the root page id.
  2. Page::init($space_key, $root_page_title);
  3. $root_page_id = Page::getId($space_key, $root_page_title);
  4. Page::add($space_key, $root_page_id, $title, $body);
  5. Page::update($space_key, $parent_page_id, $page_id, $title, $body);


That’s it folks. Happy coding!
The JIRA and Confluence REST API client can be downloaded from here.

Related posts

Image
Image
December 8, 2016
Development

How we have extracted information from JIRA, how we implemented the logics and processing in the middleware, and then integrated with Confluence, to generate a human digestible report.