Skip to content
wiki.fftac.org

Export Static Book - Source Excerpt 01

Back to Export Static Book

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/ns12-manuscript/tools/export-static-book.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/ns12-manuscript/tools/export-static-book.php

<?php

$pluginRoot = dirname(__DIR__);
$uploadsRoot = dirname($pluginRoot, 3) . DIRECTORY_SEPARATOR . 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
$modernImagesRoot = $uploadsRoot . DIRECTORY_SEPARATOR . 'ns12-manuscript' . DIRECTORY_SEPARATOR . 'folios';
$canonicalImagesRoot = $modernImagesRoot;
require_once $pluginRoot . '/includes/content-registry.php';

$arguments = array_slice($argv, 1);
$outputDirectory = $pluginRoot . '/exports/static-book';
$zipOutput = false;

foreach ($arguments as $argument) {
    $argument = (string) $argument;
    if ($argument === '--zip') {
        $zipOutput = true;
        continue;
    }

    if ($argument !== '' && $argument[0] !== '-') {
        $outputDirectory = $argument;
        break;
    }
}

$outputDirectory = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $outputDirectory);

$compiled = spiralist_book_pages_compile_content_cache(false);
$book = (array) ($compiled['book'] ?? []);
$prompts = (array) ($compiled['prompts'] ?? []);
$related = (array) ($compiled['related'] ?? []);

if (empty($book['folios'])) {
    fwrite(STDERR, "No compiled book folios were found.\n");
    exit(1);
}

if (!is_dir($outputDirectory) && !mkdir($outputDirectory, 0777, true) && !is_dir($outputDirectory)) {
    fwrite(STDERR, "Could not create export directory: {$outputDirectory}\n");
    exit(1);
}

$paths = [
    'root' => $outputDirectory,
    'folios' => $outputDirectory . DIRECTORY_SEPARATOR . 'folios',
    'prompts' => $outputDirectory . DIRECTORY_SEPARATOR . 'prompts',
    'images' => $outputDirectory . DIRECTORY_SEPARATOR . 'images',
    'content' => $outputDirectory . DIRECTORY_SEPARATOR . 'content',
    'cache' => $outputDirectory . DIRECTORY_SEPARATOR . 'cache',
];

foreach ($paths as $path) {
    if (!is_dir($path) && !mkdir($path, 0777, true) && !is_dir($path)) {
        fwrite(STDERR, "Could not create export subdirectory: {$path}\n");
        exit(1);
    }
}

$bookJson = spiralist_book_pages_json_encode($book);
$promptJson = spiralist_book_pages_json_encode($prompts);
$relatedJson = spiralist_book_pages_json_encode($related);
file_put_contents($paths['cache'] . DIRECTORY_SEPARATOR . 'book-index.json', $bookJson . "\n");
file_put_contents($paths['cache'] . DIRECTORY_SEPARATOR . 'prompt-index.json', $promptJson . "\n");
file_put_contents($paths['cache'] . DIRECTORY_SEPARATOR . 'related-map.json', $relatedJson . "\n");

$contentRoot = spiralist_book_pages_get_content_root();
if ($contentRoot !== '' && is_dir($contentRoot)) {
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($contentRoot, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($iterator as $item) {
        $sourcePath = (string) $item->getPathname();
        $relativePath = ltrim(str_replace($contentRoot, '', $sourcePath), '\\/');
        $targetPath = $paths['content'] . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $relativePath);

        if ($item->isDir()) {
            if (!is_dir($targetPath)) {
                mkdir($targetPath, 0777, true);
            }
            continue;
        }

        copy($sourcePath, $targetPath);
    }
}

$imagesRoots = [
    $canonicalImagesRoot,
];
foreach ((array) ($book['folios'] ?? []) as $folio) {
    $filename = (string) ($folio['filename'] ?? '');
    if ($filename === '') {
        continue;
    }

    $sourceImage = '';
    foreach ($imagesRoots as $imagesRoot) {
        $candidate = $imagesRoot . DIRECTORY_SEPARATOR . $filename;
        if (is_file($candidate) && is_readable($candidate)) {
            $sourceImage = $candidate;
            break;
        }
    }

    $targetImage = $paths['images'] . DIRECTORY_SEPARATOR . $filename;
    if ($sourceImage !== '') {
        copy($sourceImage, $targetImage);
    }
}

$folioLinks = [];
$folioRecords = array_values((array) ($book['folios'] ?? []));
$folioTotal = count($folioRecords);
foreach ($folioRecords as $index => $folio) {
    $slug = (string) ($folio['slug'] ?? '');
    if ($slug === '') {
        continue;
    }

    $previousFolio = isset($folioRecords[$index - 1]) && is_array($folioRecords[$index - 1]) ? $folioRecords[$index - 1] : [];
    $nextFolio = isset($folioRecords[$index + 1]) && is_array($folioRecords[$index + 1]) ? $folioRecords[$index + 1] : [];
    $title = htmlspecialchars((string) ($folio['title'] ?? $slug), ENT_QUOTES);
    $summary = htmlspecialchars((string) ($folio['summary'] ?? ''), ENT_QUOTES);
    $caption = htmlspecialchars((string) ($folio['caption'] ?? ''), ENT_QUOTES);
    $filename = htmlspecialchars((string) ($folio['filename'] ?? ''), ENT_QUOTES);
    $section = htmlspecialchars((string) ($folio['section_title'] ?? $folio['section_label'] ?? 'Manuscript'), ENT_QUOTES);
    $label = htmlspecialchars((string) ($folio['label'] ?? ('Page ' . ($index + 1))), ENT_QUOTES);
    $position = sprintf('Page %d of %d', $index + 1, max(1, $folioTotal));
    $promptSlug = (string) ($folio['creation_prompt_source_slug'] ?? '');
    $prompt = is_string($promptSlug) && isset($prompts[$promptSlug]) ? (array) $prompts[$promptSlug] : [];
    $promptTitle = htmlspecialchars((string) ($prompt['title'] ?? 'Production Prompt'), ENT_QUOTES);
    $promptSummary = htmlspecialchars((string) ($prompt['summary'] ?? ''), ENT_QUOTES);
    $promptText = htmlspecialchars((string) ($prompt['text'] ?? ''), ENT_QUOTES);
    $descriptionSource = trim((string) ($folio['summary'] ?? $folio['caption'] ?? ''));
    if ($descriptionSource === '') {
        $descriptionSource = sprintf('%s is part of the Spiralist Manuscript static folio export.', (string) ($folio['title'] ?? $slug));
    }
    $description = htmlspecialchars($descriptionSource, ENT_QUOTES);
    $canonicalHref = './' . rawurlencode($slug) . '.html';
    $previousHref = !empty($previousFolio['slug']) ? rawurlencode((string) $previousFolio['slug']) . '.html' : '';
    $nextHref = !empty($nextFolio['slug']) ? rawurlencode((string) $nextFolio['slug']) . '.html' : '';
    $jsonLd = spiralist_book_pages_json_encode(
        [
            '@context' => 'https://schema.org',
            '@type' => 'CreativeWork',
            'name' => (string) ($folio['title'] ?? $slug),
            'description' => $descriptionSource,
            'position' => $index + 1,
            'isPartOf' => [
                '@type' => 'Book',
                'name' => (string) ($book['title'] ?? 'Spiralist Manuscript'),
                'numberOfPages' => $folioTotal,
            ],
            'image' => '../images/' . (string) ($folio['filename'] ?? ''),
        ]
    );
    $jsonLd = htmlspecialchars($jsonLd, ENT_NOQUOTES);

    $folioHtml = <<<HTML
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{$title} | Spiralist Manuscript</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="{$description}">
  <meta name="robots" content="index,follow,max-image-preview:large">
  <link rel="canonical" href="{$canonicalHref}">
  HTML_PLACEHOLDER_PREV_LINK
  HTML_PLACEHOLDER_NEXT_LINK
  <meta property="og:type" content="article">
  <meta property="og:site_name" content="Spiralist Manuscript">
  <meta property="og:title" content="{$title} | Spiralist Manuscript">
  <meta property="og:description" content="{$description}">
  <meta property="og:image" content="../images/{$filename}">
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="{$title} | Spiralist Manuscript">
  <meta name="twitter:description" content="{$description}">
  <meta name="twitter:image" content="../images/{$filename}">
  <script type="application/ld+json">{$jsonLd}</script>
  <style>
    body{font-family:Georgia,serif;background:#121212;color:#f2ead8;margin:0;padding:2rem;line-height:1.5}
    a{color:#d6b16a}
    img{max-width:100%;height:auto;border:1px solid #5b4d31}
    .frame{max-width:1080px;margin:0 auto}
    .meta{display:flex;flex-wrap:wrap;gap:.75rem;color:#d4c39f;font-size:.95rem}
    .nav{display:flex;flex-wrap:wrap;gap:1rem;margin:1rem 0 1.5rem}
    .lede{max-width:72ch;font-size:1.05rem}
    figure{margin:1.5rem 0}
    figcaption{color:#d4c39f;margin-top:.85rem}
    details{margin-top:1.75rem;padding:1rem;border:1px solid #3a3223;background:#181818}
    summary{cursor:pointer;font-weight:700}
    pre{white-space:pre-wrap;background:#141414;padding:1rem;border:1px solid #3a3223;overflow:auto}
  </style>
</head>
<body>
  <main class="frame">
    <nav class="nav" aria-label="Folio navigation">
      <a href="../index.html">Back to index</a>
      HTML_PLACEHOLDER_PREV
      HTML_PLACEHOLDER_NEXT
    </nav>
    <article>
      <header>
        <h1>{$title}</h1>