Skip to content
Snippets Groups Projects
Commit 08c91e08 authored by Steven Brown's avatar Steven Brown :coffee:
Browse files

feat: Implements support to retrieve pages/books and export as files.

Abstracts requests for books/pages as `things`.
parent c7d6d129
No related branches found
No related tags found
No related merge requests found
...@@ -14,9 +14,9 @@ class BookStack_Client { ...@@ -14,9 +14,9 @@ class BookStack_Client {
private $headers = array(); private $headers = array();
private $debug = false; private $debug = false;
private $cookie = false; private $cookie = false;
private $data_string = false; protected $data_string = false;
private $file_as_json = false; private $file_as_json = false;
protected $c_types = array( private $c_types = array(
"gif" => "image/gif", "gif" => "image/gif",
"jpg" => "image/jpeg", "jpg" => "image/jpeg",
"png" => "image/png", "png" => "image/png",
...@@ -24,7 +24,19 @@ class BookStack_Client { ...@@ -24,7 +24,19 @@ class BookStack_Client {
"txt" => "text/plain", "txt" => "text/plain",
); );
function __construct( $domain_url, $id, $secret, $debug = false ) { private $extension_format = [
'pdf' => 'pdf',
'html' => 'html',
'plaintext' => 'txt',
'markdown' => 'md',
];
private $output_dir = null;
// Cache book ID's: "id" => "name"
private $cached_book_ids = array();
function __construct( $domain_url, $id, $secret, $debug = false ) {
if ( empty( $domain_url ) ) { if ( empty( $domain_url ) ) {
$this->last_status_message = 'The hostname/url cannot be empty.'; $this->last_status_message = 'The hostname/url cannot be empty.';
...@@ -60,13 +72,28 @@ class BookStack_Client { ...@@ -60,13 +72,28 @@ class BookStack_Client {
} }
$this->data_string = "{$id}:{$secret}"; $this->data_string = "{$id}:{$secret}";
$this->output_dir = realpath('./docs/');
} }
/**
* Assign a custom output path.
* @param $output_dir
* @return bool
*/
function update_output_path($output_dir) {
// Assign custom directory if overridden and exists
if (is_dir( $output_dir )) {
$this->output_dir = $output_dir;
return true;
}
return false;
}
/** /**
* Basic authentication * Basic authentication
* @return void * @return void
*/ */
function basic_auth() { protected function basic_auth() {
//$encoded_token = base64_encode( $this->data_string ); //$encoded_token = base64_encode( $this->data_string );
$encoded_token = $this->data_string; $encoded_token = $this->data_string;
$this->headers = array_merge( array( $this->headers = array_merge( array(
...@@ -81,15 +108,15 @@ class BookStack_Client { ...@@ -81,15 +108,15 @@ class BookStack_Client {
* *
* @return void * @return void
*/ */
function user_auth( $ch ) { protected function user_auth( $ch ) {
//$encoded_token = base64_encode( $this->data_string ); //$encoded_token = base64_encode( $this->data_string );
$encoded_token = $this->data_string; $encoded_token = $this->data_string;
curl_setopt( $ch, CURLOPT_USERPWD, $encoded_token ); curl_setopt( $ch, CURLOPT_USERPWD, $encoded_token );
curl_setopt( $ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC ); curl_setopt( $ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
//curl_setopt($ch, CURLOPT_COOKIESESSION, true); //curl_setopt($ch, CURLOPT_COOKIESESSION, true);
curl_setopt( $ch, CURLOPT_COOKIEJAR, $this->cookie ); curl_setopt( $ch, CURLOPT_COOKIEJAR, $this->cookie );
curl_setopt( $ch, CURLOPT_COOKIEFILE, $this->cookie ); curl_setopt( $ch, CURLOPT_COOKIEFILE, $this->cookie );
} }
/** /**
* Dumps values if debugging is enabled. * Dumps values if debugging is enabled.
...@@ -112,25 +139,24 @@ class BookStack_Client { ...@@ -112,25 +139,24 @@ class BookStack_Client {
* Create a POST request; supports a variety of scenarios. * Create a POST request; supports a variety of scenarios.
* *
* @param $url * @param $url
* @param $payload * @param array $payload
* @param $type * @param $type
* @param $filename * @param $filename
* *
* @return bool|string * @return bool|string
*/ */
function post( $url, $payload, $type = "POST", $filename = false ) { function post($url, $payload = array(), $type = "POST", $filename = false ) {
// Reset header // Reset header
$this->headers = array(); $this->headers = array();
// Set the last url attribute to match this request // Set the last url attribute to match this request
$this->last_url = $url; $this->last_url = $url;
// Get cURL resource // Get cURL resource
$curl = curl_init(); $curl = curl_init();
// Merge the payload with channel_payload
// $payload = json_encode($payload);
// Custom POST/GET requests // Custom POST/GET requests
if ( $type === "POST" ) { if ( $type === "POST" ) {
curl_setopt( $curl, CURLOPT_CUSTOMREQUEST, 'POST' ); curl_setopt( $curl, CURLOPT_CUSTOMREQUEST, 'POST' );
curl_setopt( $curl, CURLOPT_POSTFIELDS, $payload );
} }
if ( $type === "GET" ) { if ( $type === "GET" ) {
...@@ -142,7 +168,7 @@ class BookStack_Client { ...@@ -142,7 +168,7 @@ class BookStack_Client {
} }
// If file is empty set default header to JSON // If file is empty set default header to JSON
if ( $filename === false ) { if ( $type === "POST" && $filename === false ) {
$payload = json_encode( $payload ); $payload = json_encode( $payload );
$this->headers = array_merge( $this->headers, array( $this->headers = array_merge( $this->headers, array(
'Content-Type: application/json', 'Content-Type: application/json',
...@@ -194,47 +220,36 @@ class BookStack_Client { ...@@ -194,47 +220,36 @@ class BookStack_Client {
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true ); curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $curl, CURLOPT_USERAGENT, 'europa_bookstack' ); curl_setopt( $curl, CURLOPT_USERAGENT, 'europa_bookstack' );
curl_setopt( $curl, CURLOPT_POSTFIELDS, $payload );
curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, false ); curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
// Send the request & save response to $resp // Send the request & save response to $resp
$resp = curl_exec( $curl ); $resp = curl_exec( $curl );
$http_code = curl_getinfo( $curl, CURLINFO_HTTP_CODE ); $http_code = curl_getinfo( $curl, CURLINFO_HTTP_CODE );
$errors = curl_error( $curl ); $errors = curl_error( $curl );
curl_close( $curl ); curl_close( $curl );
// Check for response errors
$friendly_errors = $resp;
if ( ! isset( $resp ) ) {
if ( isset( $errors ) ) {
$friendly_errors = $errors;
} else {
$friendly_errors = $resp;
}
}
//$this->log($payload);
$this->log( $friendly_errors );
$this->last_status_message = $errors; $this->last_status_message = $errors;
$this->last_status_code = $http_code; $this->last_status_code = $http_code;
return ( $http_code >= 200 && $http_code < 300 ) ? $resp : $friendly_errors; return ( $http_code >= 200 && $http_code < 300 ) ? $resp : $errors;
} }
/** /**
* Utilises `post` to make a GET request using similar options. * Utilises `post` to make a GET request using similar options.
*
* @param $url * @param $url
* @param $payload * @param array $payload
* *
* @return bool|string * @return bool|string
*/ */
function get( $url, $payload ) { function get($url, $payload = array() ) {
return $this->post( $url, $payload, "GET" ); return $this->post( $url, $payload, "GET" );
} }
/** /**
* Utilises `post` to make a PUT request using similar options. * Utilises `post` to make a PUT request using similar options.
*
* @param $url * @param $url
* @param $payload * @param $payload
* *
...@@ -246,7 +261,6 @@ class BookStack_Client { ...@@ -246,7 +261,6 @@ class BookStack_Client {
/** /**
* Retrieves a list of shelves. * Retrieves a list of shelves.
*
* @param $payload * @param $payload
* *
* @return false * @return false
...@@ -268,31 +282,194 @@ class BookStack_Client { ...@@ -268,31 +282,194 @@ class BookStack_Client {
return false; return false;
} }
/** /**
* Retrieves a list of books. * GET Helper function to list from selected types: books, pages
* *
* @param $payload array * @param $type book or page
* * @param $payload
* @return false * @return false|mixed
*/ */
function get_books( $payload ) { protected function get_thing_helper($type, $payload = array() ) {
if ( is_array( $payload ) ) { $url = "{$this->url}{$type}";
$books = $this->get( "{$this->url}books", $payload );
if ( !empty( $payload ) ) {
if ( isset( $books ) ) { $url = "{$this->url}{$type}?" . http_build_query($payload);
$books = json_decode( $books );
if ( isset( $books->data ) ) { if (isset($payload["id"])) {
$books = $books->data; $url = "{$this->url}{$type}/{$payload["id"]}";
$this->log( $books ); }
}
return $books;
} $books = $this->get($url);
} //$this->log( $books );
}
if ( isset( $books ) ) {
return false; $books = json_decode( $books );
if (!empty($books)) {
return $books;
}
}
return false;
}
/**
* Creates a files from data; helper functions to be used by _export_thing_paginated_ with items such as books, pages.
*
* @param $type book or page
* @param $data data from a
* @param $ext
* @return void
*/
protected function create_file($type, $data, $ext = 'markdown') {
if ($data) {
// iterate over books and save each entry
foreach ($data as $entry) {
if (!isset($entry->slug)) {
continue;
}
$id = $entry->id;
$extension = $this->extension_format[$ext];
$content = $this->get("{$this->url}{$type}/{$id}/export/{$ext}");
//$this->log(array($type, $entry));
$slug = "{$entry->slug}";
$outpath = $this->output_dir;
// Change pages to use the book slug as a sub-directory
if ($type === "pages") {
if (isset($entry->book_id)) {
$book_name = $this->get_book_name($entry->book_id);
if (empty($book_name)) {
$book_name = "unknown";
}
$outpath = "{$this->output_dir}/{$book_name}";
// Dir doesn't exist make it
if (!is_dir($outpath)) {
mkdir($outpath);
}
}
}
$out = "{$outpath}/{$slug}.{$extension}";
$this->log($out);
file_put_contents($out, $content);
}
}
}
/**
* Used with pagination functionality; changes the type (book or page) and sets the count and offset
* @param $type
* @param $count
* @param $offset
* @return false|mixed
*/
protected function set_type($type, $count, $offset) {
switch ($type) {
case "pages":
$data = $this->get_pages($count, $offset);
break;
default:
$data = $this->get_books($count, $offset);
break;
}
return $data;
}
/**
* Export *things*; helper functions to be used by types such as books, pages.
*
* @param $type
* @return void
*/
protected function export_thing_paginated($type) {
$count = 100;
$processed = 0;
// Set the data type (books or pages)
$data = $this->set_type($type, $count, $processed);
if ( isset( $data->total ) ) {
$total = $data->total;
while ($processed < $total) {
// Get next set when processed > 0
if ($processed > 0) {
//$this->log( array("new {$type} query", $count, $processed, $this->last_status_message) );
$data = $this->set_type($type, $count, $processed);
}
if ( isset( $data->data ) ) {
$data = $data->data;
$this->create_file($type, $data, 'markdown');
}
$processed += $count;
}
}
}
/**
* Retrieve a list of *things*; helper functions to be used by types such as books, pages.
*
* @param $payload
* @return false|mixed
*/
protected function get_thing($type, $payload = array() ) {
return $this->get_thing_helper($type, $payload);
} }
/**
* Get information about a book by book ID.
*
* @param $id
* @return false|mixed
*/
function get_book($id) {
$book = $this->get_thing("books", ['id' => $id]);
return $book;
}
/**
* Retrieve a list of books based on a count + offset.
* Defaults to 100, 0.
*
* @param $count
* @param $offset
* @return false|mixed
*/
function get_books($count = 100, $offset = 0) {
$books = $this->get_thing("books", ['count' => $count, 'offset' => $offset]);
return $books;
}
/**
* Get the book (slug) name by book id.
*
* @param $id
* @return false
*/
function get_book_name($id) {
// Test cache first - less likely to hit rate limits
if (isset($this->cached_book_ids[$id])) {
return $this->cached_book_ids[$id];
}
// Query the book name (we use slug) from the ID
$book = $this->get_book($id);
if (isset($book->slug)) {
$this->cached_book_ids[$id] = $book->slug;
return $book->slug;
}
return false;
}
/** /**
* Creates a new book if it does not exists. * Creates a new book if it does not exists.
* $check_existing Default: true. When true checks if there is an existing book with the same name and returns * $check_existing Default: true. When true checks if there is an existing book with the same name and returns
...@@ -344,6 +521,44 @@ class BookStack_Client { ...@@ -344,6 +521,44 @@ class BookStack_Client {
return $book_id; return $book_id;
} }
/**
* Export all books to a file.
* @return void
*/
function export_all_books() {
$this->export_thing_paginated("books");
}
/**
* Retrieve a page by ID.
* @param $id
* @return false|mixed
*/
function get_page($id) {
$page = $this->get_thing("pages", ['id' => $id]);
return $page;
}
/**
* Retrieve a list of pages based on a count + offset.
* Defaults to 100, 0.
* @param $count
* @param $offset
* @return false|mixed
*/
function get_pages($count = 100, $offset = 0) {
$pages = $this->get_thing("pages", ['count' => $count, 'offset' => $offset]);
return $pages;
}
/**
* Export all pages to files; uses the book slug name as sub-directory to store each page.
* @return void
*/
function export_all_pages() {
$this->export_thing_paginated("pages");
}
/** /**
* Creates a page within a book. * Creates a page within a book.
* *
...@@ -375,13 +590,12 @@ class BookStack_Client { ...@@ -375,13 +590,12 @@ class BookStack_Client {
return false; return false;
} }
function starts_with( $haystack, $needle ) { public function starts_with( $haystack, $needle ) {
$length = strlen( $needle ); $length = strlen( $needle );
return substr( $haystack, 0, $length ) === $needle; return substr( $haystack, 0, $length ) === $needle;
} }
function ends_with( $haystack, $needle ) { public function ends_with($haystack, $needle ) {
$length = strlen( $needle ); $length = strlen( $needle );
if ( ! $length ) { if ( ! $length ) {
return true; return true;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment