5 // You can either provide them as environment variables
6 // or hard-code them in the empty strings below.
7 $apiUrl = getenv('BS_URL') ?: '';
8 $clientId = getenv('BS_TOKEN_ID') ?: '';
9 $clientSecret = getenv('BS_TOKEN_SECRET') ?: '';
12 // Can be provided as a arguments when calling the script
13 // or be hard-coded as strings below.
14 $bookSlug = $argv[1] ?? '';
15 $outFolder = $argv[2] ?? './out';
20 if (empty($bookSlug) || empty($outFolder)) {
21 errorOut("Both a book slug and output folder must be provided");
24 if (!is_dir($outFolder)) {
25 mkdir($outFolder, 0777, true);
28 $outDir = realpath($outFolder);
29 $book = getBookBySlug($bookSlug);
32 errorOut("Could not find book with the URL slug: {$bookSlug}");
35 $chapters = getAllOfAtListEndpoint("api/chapters", ['filter[book_id]' => $book['id']]);
36 $pages = getAllOfAtListEndpoint("api/pages", ['filter[book_id]' => $book['id']]);
38 foreach ($pages as $index => $page) {
39 $pages[$index] = apiGetJson("api/pages/{$page['id']}");
42 if (!is_dir($outDir . "/images")) {
43 mkdir($outDir . "/images", 0777, true);
46 $directBookPages = array_filter($pages, function($page) {
47 return empty($page['chapter_id']);
50 // Create book index file
51 $bookIndex = getBookContent($book, $chapters, $directBookPages);
52 file_put_contents($outDir . "/index.html", $bookIndex);
54 foreach ($chapters as $chapter) {
55 $childPages = array_filter($pages, function($page) use ($chapter) {
56 return $page['chapter_id'] == $chapter['id'];
58 $chapterPage = getChapterContent($chapter, $childPages);
59 file_put_contents($outDir . "/chapter-{$chapter['slug']}.html", $chapterPage);
61 foreach ($childPages as $childPage) {
62 $childPageContent = getPageContent($childPage, $chapter);
63 $childPageContent = extractImagesFromHtml($childPageContent);
64 file_put_contents($outDir . "/page-{$childPage['slug']}.html", $childPageContent);
68 foreach ($directBookPages as $directPage) {
69 $directPageContent = getPageContent($directPage, null);
70 $directPageContent = extractImagesFromHtml($directPageContent);
71 file_put_contents($outDir . "/page-{$directPage['slug']}.html", $directPageContent);
74 function extractImagesFromHtml(string $html): string {
77 preg_match_all('/<img.*?src=["\'](.*?)[\'"].*?>/i', $html, $matches);
78 foreach (array_unique($matches[1] ?? []) as $url) {
79 $image = file_get_contents($url);
80 $name = basename($url);
83 while (file_exists($outDir . "/images/" . $fileName)) {
84 $fileName = $count . '-' . $name;
86 file_put_contents($outDir . "/images/" . $fileName, $image);
87 $html = str_replace($url, "./images/" . $fileName, $html);
92 function getImageFile($url): string {
94 if (strpos(strtolower($url), strtolower($apiUrl)) === 0) {
95 $url = substr($url, strlen($apiUrl));
98 return file_get_contents($url);
101 function getBookContent(array $book, array $chapters, array $pages): string {
102 $content = "<h1>{$book['name']}</h1>";
103 $content .= "<p>{$book['description']}</p>";
105 if (count($chapters) > 0) {
106 $content .= "<h3>Chapters</h3><ul>";
107 foreach ($chapters as $chapter) {
108 $content .= "<li><a href='./chapter-{$chapter['slug']}.html'>{$chapter['name']}</a></li>";
112 if (count($pages) > 0) {
113 $content .= "<h3>Pages</h3><ul>";
114 foreach ($pages as $page) {
115 $content .= "<li><a href='./page-{$page['slug']}.html'>{$page['name']}</a></li>";
122 function getChapterContent(array $chapter, array $pages): string {
123 $content = "<p><a href='./index.html'>Back to book</a></p>";
124 $content .= "<h1>{$chapter['name']}</h1>";
125 $content .= "<p>{$chapter['description']}</p>";
127 if (count($pages) > 0) {
128 $content .= "<h3>Pages</h3><ul>";
129 foreach ($pages as $page) {
130 $content .= "<li><a href='./page-{$page['slug']}.html'>{$page['name']}</a></li>";
137 function getPageContent(array $page, ?array $parentChapter): string {
138 if (is_null($parentChapter)) {
139 $content = "<p><a href='./index.html'>Back to book</a></p>";
141 $content = "<p><a href='./chapter-{$parentChapter['slug']}.html'>Back to chapter</a></p>";
143 $content .= "<h1>{$page['name']}</h1>";
144 $content .= "<div>{$page['html']}</div>";
149 * Get a single book by the slug or return null if not exists.
151 function getBookBySlug(string $slug): ?array {
152 $endpoint = 'api/books?' . http_build_query(['filter[slug]' => $slug]);
153 $resp = apiGetJson($endpoint);
154 $book = $resp['data'][0] ?? null;
156 if (!is_null($book)) {
157 $book = apiGetJson("api/books/{$book['id']}") ?? null;
163 * Get all books from the system API.
165 function getAllOfAtListEndpoint(string $endpoint, array $params): array {
172 $endpoint = $endpoint . '?' . http_build_query(array_merge($params, ['count' => $count, 'offset' => $offset]));
173 $resp = apiGetJson($endpoint);
175 $total = $resp['total'] ?? 0;
176 $new = $resp['data'] ?? [];
177 array_push($all, ...$new);
179 } while ($offset < $total);
185 * Make a simple GET HTTP request to the API.
187 function apiGet(string $endpoint): string {
188 global $apiUrl, $clientId, $clientSecret;
189 $url = rtrim($apiUrl, '/') . '/' . ltrim($endpoint, '/');
190 $opts = ['http' => ['header' => "Authorization: Token {$clientId}:{$clientSecret}"]];
191 $context = stream_context_create($opts);
192 return file_get_contents($url, false, $context);
196 * Make a simple GET HTTP request to the API &
197 * decode the JSON response to an array.
199 function apiGetJson(string $endpoint): array {
200 $data = apiGet($endpoint);
201 return json_decode($data, true);
205 * DEBUG: Dump out the given variables and exit.
207 function dd(...$args) {
208 foreach ($args as $arg) {
214 function errorOut(string $text) {
215 echo "ERROR: " . $text;