]> BookStack Code Mirror - api-scripts/blob - php-generate-tree/generate-tree.php
Added bsfs to community project list
[api-scripts] / php-generate-tree / generate-tree.php
1 #!/usr/bin/env php
2 <?php
3
4 // API Credentials
5 // You can either provide them as environment variables
6 // or hard-code them in the empty strings below.
7 $baseUrl = getenv('BS_URL') ?: '';
8 $clientId = getenv('BS_TOKEN_ID') ?: '';
9 $clientSecret = getenv('BS_TOKEN_SECRET') ?: '';
10
11 // Script logic
12 ////////////////
13
14 // Define the time we wait in between making API requests,
15 // to help keep within rate limits and avoid exhausting resources.
16 $apiPauseMicrosecs = 100;
17
18 // Clean up the base path
19 $baseUrl = rtrim($baseUrl, '/');
20
21 // Get all items from the system keyed by ID
22 $shelvesById = keyById(getAllOfAtListEndpoint("api/shelves", []));
23 $booksById = keyById(getAllOfAtListEndpoint("api/books", []));
24
25 // Fetch books that are on each shelf
26 foreach ($shelvesById as $id => $shelf) {
27     $shelvesById[$id]['books'] = getBooksForShelf($id);
28     usleep($apiPauseMicrosecs);
29 }
30
31 // For each book, fetch its contents list
32 foreach ($booksById as $id => $book) {
33     $booksById[$id]['contents'] = apiGetJson("api/books/{$id}")['contents'] ?? [];
34     usleep($apiPauseMicrosecs);
35 }
36
37 // Cycle through the shelves and display their contents
38 $isBookShownById = [];
39 foreach ($shelvesById as $id => $shelf) {
40     output($shelf, 'bookshelf', [false]);
41     $bookCount = count($shelf['books']);
42     for ($i=0; $i < $bookCount; $i++) {
43         $bookId = $shelf['books'][$i];
44         $book = $booksById[$bookId] ?? null;
45         if ($book) {
46             outputBookAndContents($book, [false, $i === $bookCount - 1]);
47             $isBookShownById[strval($book['id'])] = true;
48         }
49     }
50 }
51
52 // Cycle through books and display any that have not been
53 // part of a shelve's output
54 foreach ($booksById as $id => $book) {
55     if (isset($isBookShownById[$id])) {
56         continue;
57     }
58
59     outputBookAndContents($book, [false]);
60 }
61
62 /**
63  * Output a book for display, along with its contents.
64  */
65 function outputBookAndContents(array $book, array $depthPath): void
66 {
67     output($book, 'book', $depthPath);
68     $childCount = count($book['contents']);
69     for ($i=0; $i < $childCount; $i++) {
70         $child = $book['contents'][$i];
71         $childPath = array_merge($depthPath, [($i === $childCount - 1)]);
72         output($child, $child['type'], $childPath);
73         $pages = $child['pages'] ?? [];
74         $pageCount = count($pages);
75         for ($j=0; $j < count($pages); $j++) { 
76             $page = $pages[$j];
77             $innerPath = array_merge($childPath, [($j === $pageCount - 1)]);
78             output($page, 'page', $innerPath);
79         }
80     }
81 }
82
83 /**
84  * Output a single item for display.
85  */
86 function output(array $item, string $type, array $depthPath): void
87 {
88     $upperType = strtoupper($type);
89     $prefix = '';
90     $depth = count($depthPath);
91     for ($i=0; $i < $depth; $i++) { 
92         $isLastAtDepth = $depthPath[$i];
93         $end = ($i === $depth - 1);
94         if ($end) {
95             $prefix .= $isLastAtDepth ? '└' : '├';
96         } else {
97             $prefix .= $isLastAtDepth ? '    ' : '│   ';
98         }
99     }
100     echo $prefix . "── {$upperType} {$item['id']}: {$item['name']}\n";
101 }
102
103 /**
104  * Key an array of array-based data objects by 'id' value. 
105  */
106 function keyById(array $data): array 
107 {
108     $byId = [];
109     foreach ($data as $item) { 
110         $id = $item['id'];
111         $byId[$id] = $item;
112     }
113     return $byId;
114 }
115
116 /**
117  * Get the books for the given shelf ID.
118  * Returns an array of the book IDs.
119  */
120 function getBooksForShelf(int $shelfId): array
121 {
122     $resp = apiGetJson("api/shelves/{$shelfId}");
123     return array_map(function ($bookData) {
124         return $bookData['id'];
125     }, $resp['books'] ?? []);
126 }
127
128 /**
129  * Consume all items from the given API listing endpoint.
130  */
131 function getAllOfAtListEndpoint(string $endpoint, array $params): array
132 {
133     global $apiPauseMicrosecs;
134     $count = 100;
135     $offset = 0;
136     $all = [];
137
138     do {
139         $endpoint = $endpoint . '?' . http_build_query(array_merge($params, ['count' => $count, 'offset' => $offset]));
140         $resp = apiGetJson($endpoint);
141
142         $total = $resp['total'] ?? 0;
143         $new = $resp['data'] ?? [];
144         array_push($all, ...$new);
145         $offset += $count;
146         usleep($apiPauseMicrosecs);
147     } while ($offset < $total);
148
149     return $all;
150 }
151
152 /**
153  * Make a simple GET HTTP request to the API.
154  */
155 function apiGet(string $endpoint): string
156 {
157     global $baseUrl, $clientId, $clientSecret;
158     $url = rtrim($baseUrl, '/') . '/' . ltrim($endpoint, '/');
159     $opts = ['http' => ['header' => "Authorization: Token {$clientId}:{$clientSecret}"]];
160     $context = stream_context_create($opts);
161     return @file_get_contents($url, false, $context);
162 }
163
164 /**
165  * Make a simple GET HTTP request to the API &
166  * decode the JSON response to an array.
167  */
168 function apiGetJson(string $endpoint): array
169 {
170     $data = apiGet($endpoint);
171     $array = json_decode($data, true);
172
173     if (!is_array($array)) {
174         dd("Failed request to {$endpoint}", $data);
175     }
176
177     return $array;
178 }
179
180 /**
181  * DEBUG: Dump out the given variables and exit.
182  */
183 function dd(...$args)
184 {
185     foreach ($args as $arg) {
186         var_dump($arg);
187     }
188     exit(1);
189 }