paint-brush
Storybook v2 Comic Book Builder: Part 1, Gathering Contentby@bobnoxious

Storybook v2 Comic Book Builder: Part 1, Gathering Content

by Bob WrightMarch 23rd, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The basic concept for the comic builder is to first gather the materials which will constitute the content of the comic. There are three sorts of files which the builder may upload during this first stage in the builder process. The second stage process, the code that generates the comic, is the subject of the next, or second, of our three parts. The third part of this series will feature a print version of the actual web comic we will generate here.
featured image - Storybook v2 Comic Book Builder: Part 1, Gathering Content
Bob Wright HackerNoon profile picture

A sequentially paginated, accessible, performant, responsive design, illustrated serial content document generator, aka “my comic book builder”.

The basic concept for the comic builder is to first gather the materials which will constitute the content of the comic from the nominal author by either form text entry or file selection and upload. There are three sorts of files which the builder may upload during this first stage in the builder process, these are image files, text files, and audio files. This first article about Storybook V2 will describe this first stage of the builder in the sense of how it gathers and prepares this data, what it expects to upload as content to provide for the second stage process wherein this collated and prepared data is used to actually generate the comic itself. The second stage process, the code that generates the comic, is the subject of the next, or second, of our three parts. The third part of this series will feature a print version of the actual web comic (Hyenas2) we will generate here. The Builder code itself is largely PHP and mySQL along with JavaScript and all of the code is presented as open source in a GitHub repository.

Builder Goals, a Summary

Insofar as overarching considerations and goals we have set the following requirements for the Builder, that the comics or documents it makes


  • use a mobile-first responsive design.
  • are very quick to load.
  • are accessible by WCAG guidelines.
  • have semantic content and logical syntax.
  • are easy to navigate using only the keyboard.
  • support JPG or PNG or GIF or AVIF or WEBP images.
  • support GIF or WEBP or AVIF animations and MP3 audio content.
  • are portable to run on various media (eg thumb drives or CD-ROMS).
  • can be viewed on a wide range of devices and browsers.
  • use modern HTML and CSS and JavaScript in their design.


Another major design consideration is the relative ease of use of the Builder itself, and of course its utility in document generation, i.e. building web comics. There is a lot of data or information that goes into a typical web comic, and we want to make providing that data to the builder a non-traumatic user experience. We want a clean, straightforward, and fairly well self-documenting User Interface for our Builder.


Our final consideration here has to do with the overall design of the generated comics. What does the author need to provide to the builder, indeed, what even can the author provide to the builder? Obviously the object of our concern here is the content itself. How do we gather it, what is it actually, and how do we provide it to the Builder.


Let’s look at how these goals are achieved in our code.

The Builder Entry Point, index.php

The main Builder entry point is a program named index.php. index has as its most notable topological feature a collection or list of forms that serve to gather a lot of specifics about the comic we are generating, for example its title and some text information that is used to populate a “card” that we use as an entry in a gallery of comics. index also gathers two images, the web comic page background image and an image used as the cover image or banner image on the comics gallery card. Index also generates and saves the gallery card HTML file. index calls other PHP programs to process and filter the values it is presented, and it saves much of the data it gathers as PHP configuration files in a folder it creates for the comic’s contents.


The .index program presents this opening screen shown here next below.

.index opening screen

It then presents a list display of a number of forms like these shown for example here next. Note that the forms hopefully contain sufficient information to allow a user to easily discern what data is being sought.

some .index form fields

At the bottom of the screen above .index is requesting an author name. Let’s dip our toes into the bit of index.php code that makes this inquiry of the user.

echo
'<section class="d-flex col-sm-11 flex-column align-items-center shadow-md #b0bec5 blue-grey lighten-5 px-sm-0 infoBox">'.
'<p class="d-flex col-sm-11">Now we will enter a list of content creator names, each up to 32 characters in length, to display on the gallery card. The list of content creator names in the order of their display in the button list is Author, Script, Pencils, Inks, Colors, and Lettering.<br> All of the content creator values are optional, but Storybook suggests entering either a single Author name or some combination of the Script, Pencils, Inks, Colors, and Lettering entries. You can leave a value blank for no entry. These values will appear in the button list below the text description above.<br></p>';

// author's name calls authorName.php
if(isset($_SESSION["authorname"])){
	$authorname =  $_SESSION["authorname"];
	} else {$authorname = 'I. Aman Author';}
echo
'<section class="d-flex col-sm-11 flex-column align-items-center shadow-md #b0bec5 blue-grey lighten-5 px-sm-0 msgBox">'.
'<form id="authorName" action="authorName.php" method="post" enctype="text">'.
'Enter a <em>Author</em> for the comic up to 32 characters in length to display on the gallery card. The Author will appear in the button list below the text description above.<br>'.
'<label>The Comic Author (optional):<br><input type="text" name="authorname" size="32" value="'.$authorname.'" tabindex="13"></label>'.
'<br><input type="submit" value="Apply" tabindex="14">'.
'</form></section><br>';


That is all fairly straightforward PHP code that presents our form question and then calls another PHP program to process the form. This basic template is used for most of the .index form fields. Here next is the authorName.php program which we use to sanitize the user input.

<?php
/*
 * filename: authorName.php
 * this code saves the authorname in the session
*/

// disable error reporting for production code
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
// Start session
session_name("Storybook");
include("/var/www/session2DB/Zebra.php");
//	session_start();

// set fallback variable
// . \ + * ? ^ $ [ ] ( ) { } < > = ! | :
$authorname = 'author name'; //fallback
if (($_SERVER["REQUEST_METHOD"] == "POST") && ($_POST['authorname'] == '')) { // empty POST then fallback value
	$pattern = '/(\n|\r\n|\r)/';
	$replacement = '<br>';
	$authorname = preg_replace($pattern, $replacement, $authorname);
	$_SESSION["authorname"] = $authorname;
	header("Refresh: 0; URL=./index.php#authorName");
	echo "$authorname is a valid string.<br/><br/>";
}

if (($_SERVER["REQUEST_METHOD"] == "POST") && (isset($_POST['authorname'])) && ($_POST['authorname'] != '')) {
	$authorname = trim($_POST['authorname']);
	if(strlen($authorname) > 32) {$authorname = '';}
	//echo '<br>'.$authorname.'<br>';
	$pattern = '/(\n|\r\n|\r)/';
	$replacement = '[newline]';
	$authorname = preg_replace($pattern, $replacement, $authorname);
	//sanitize string
// FILTER_SANITIZE_STRING is deprecated
//	$authorname = filter_var($authorname, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK);
	$authorname = filter_var($authorname,  FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_BACKTICK);
	if ($authorname != '') {
		$pattern = '/\[newline\]/';
		$replacement = '<br>';
		$authorname = preg_replace($pattern, $replacement, $authorname);
		$_SESSION["authorname"] = $authorname;
		header("Refresh: 0; URL=./index.php#authorName");
		echo "$authorname is a valid string.<br/><br/>";
	} else {
		$_SESSION["authorname"] = '';
		header("Refresh: 0; URL=./index.php#authorName");
		echo "$authorname is <strong>NOT</strong> a valid string.<br/><br/>";
	}
}

?>


Notice that at this point we have our data stored as a PHP $_SESSION variable as shown here next below. This is not a viable permanent storage for our data but the $_SESSION concept is truly an elegantly lovely construct. Once we have “filled out” the form fields and selected the two images it requests, the card image and the comic page background image, these next summary screens will be presented at the bottom of the .index screen. There is an inset image of the actual gallery card that is created.

Comic configuration data


.index Summary Screens

The last thing .index does is of great import to our builder process, and that is to save this configuration and content data it has gathered.


As a quick aside, I was asked a short time ago what dev tools I use and I gave an inaccurate answer. I said I did not use any, but upon a bit of reflection it occurred to me that I actually do use a couple of “dev tools”. One of these tools is PHPmyadmin for use with mySQL server. It is a great way to see if mySQLi procedures work for example. Another tool I love is the closure detection and simple syntax assistance in both Notepad++ and VSCode editors. I got started with assembly language in the days before CP/M and MS-DOS when besides the text editor and assembler the only real dev tool was a debug ROM and the notion of “breakpoints”.


Today I use that concept as my biggest devtool, and my code is rife with these reporting breakpoints. In JavaScript the console.info() report is nice, and they make for good inline documentation. The Browser debug console is a good report display. I wanted to learn PHP and decided that a similar artifice, i.e. a reporting tool, would be a useful dev tool for my PHP exploits. So I wrote a tool I named dumpSession toward that end. Here next is a display screen from dumpSession after the .index program has run.


dumpSession after .index has run


The .index program calls saveConfig.php shown here next below to store these values.


<?php
/*
 * filename: saveConfig.php
 * this code gets the Comic config file name
*/

// disable error reporting for production code
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
// Start session
session_name("Storybook");
include("/var/www/session2DB/Zebra.php");
//	session_start();

$checkbox = '';
$configDir = '';
$clearedComic = '';
$clearedDB = '';
$noComicname = '';
$comicsDir = '/var/www/Comics/htdocs/';
$clearedCdb = '';
$clearedIdb = '';

// check that form was actually submitted
if ($_SERVER["REQUEST_METHOD"] == "POST") {
	if(isset($_POST['saveCheckbox'])) { $checkbox = 1; } else { $checkbox = 0;}
	if((isset($_SESSION["Comicname"])) && ($_SESSION["Comicname"] != '')){
		$Comicname = $_SESSION['Comicname'];
		// clear the Comic content?
		if($checkbox == 0) { //clean up Comic files?

			// Comics folder path
			//$comicsDir = '/var/www/Comics/htdocs/';
			require("./rrmdir.php");

			if(file_exists($comicsDir.$Comicname.'.zip')) {
				$result = unlink($comicsDir.$Comicname.'.zip');}
			//if(file_exists($comicsDir.$Comicname.'OGIMG.png')) {
				//$result = unlink($comicsDir.$Comicname.'OGIMG.png');}
			if(file_exists($comicsDir.$Comicname.'OGIMG.jpg')) {
				$result = unlink($comicsDir.$Comicname.'OGIMG.jpg');}
			if(file_exists($comicsDir.$Comicname.'OGIMG.webp')) {
				$result = unlink($comicsDir.$Comicname.'OGIMG.webp');}
			if(file_exists($comicsDir.$Comicname.'.html')) {
				$result = unlink($comicsDir.$Comicname.'.html');}
			if(file_exists($comicsDir.$Comicname.'.card')) {
				$result = unlink($comicsDir.$Comicname.'.card');}
			/*if(file_exists($comicsDir.'coversImg/'.$Comicname.'CARD.jpg')) {
				$result = unlink($comicsDir.'coversImg/'.$Comicname.'CARD.jpg');}
			if(file_exists($comicsDir.'coversImg/'.$Comicname.'CARD-m.jpg')) {
				$result = unlink($comicsDir.'coversImg/'.$Comicname.'CARD-m.jpg');}
			if(file_exists($comicsDir.'coversImg/'.$Comicname.'CARD-s.jpg')) {
				$result = unlink($comicsDir.'coversImg/'.$Comicname.'CARD-s.jpg');}
			if(file_exists($comicsDir.'coversImg/'.$Comicname.'CARD.webp')) {
				$result = unlink($comicsDir.'coversImg/'.$Comicname.'CARD.webp');}
			if(file_exists($comicsDir.'coversImg/'.$Comicname.'CARD-m.webp')) {
				$result = unlink($comicsDir.'coversImg/'.$Comicname.'CARD-m.webp');}
			if(file_exists($comicsDir.'coversImg/'.$Comicname.'CARD-s.webp')) {
				$result = unlink($comicsDir.'coversImg/'.$Comicname.'CARD-s.webp');}
			if(file_exists($comicsDir.'coversImg/'.$Comicname.'CARD.gif')) {
				$result = unlink($comicsDir.'coversImg/'.$Comicname.'CARD-s.gif');}*/
			//if(file_exists($comicsDir.'coversImg/'.$Comicname.'CARD.png')) {
				//$result = unlink($comicsDir.'coversImg/'.$Comicname.'CARD.png');}
			$folder = $comicsDir.$Comicname;
			rrmdir($folder);
			$clearedComic = "Cleared the ' . $Comicname . ' content.<br>";

			// clean the Storybook databases
		require("/var/www/includes/Comic.class.php");
			$comicbook = new Comic();
			$comicList = $comicbook->returnComicRecord($Comicname);
			$comicList = $comicbook->deleteComicRecord($Comicname);
			$clearedCdb = 'Cleared the Comic database<br>';
		require("/var/www/includes/ComicImages.class.php");
				$ComicImages = new ComicImages();
			$imageList = $ComicImages->listComicImages($Comicname, true);
			$imageList = $ComicImages->deleteComicImages($Comicname);
			$clearedIdb = 'Cleared the Comic Images database<br>';
		}

	if(!(is_dir($comicsDir.$Comicname))) {
		mkdir($comicsDir.$Comicname, 0775, true);
	}

	// build the configuration data file content
	$configContent = '';
	$configContent .= '<?php ';
	$configContent .= 'require_once("/var/www/session2DB/Zebra.php");';

	if(isset($_SESSION["siteurl"])){
		$siteurl = $_SESSION["siteurl"];
		$configContent .= '$_SESSION["siteurl"] = "'.$siteurl.'";';
		} else {
		$configContent .= '$_SESSION["siteurl"] = "";';
		}
	if(isset($_SESSION["cardTitle"])){
		$cardTitle = $_SESSION["cardTitle"];
		$configContent .= '$_SESSION["cardTitle"] = "'.$cardTitle.'";';
		} else {
		$configContent .= '$_SESSION["cardTitle"] = "";';
		}
	if(isset($_SESSION["Comicname"])){
		$Comicname =  $_SESSION["Comicname"];
		$configContent .= '$_SESSION["Comicname"] = "'.$Comicname.'";';
		} else {
		$configContent .= '$_SESSION["Comicname"] = "";';
		}
	if(isset($_SESSION["pageURL"])){
		$pageURL = $_SESSION["pageURL"];
		$configContent .= '$_SESSION["pageURL"] = "'.$pageURL.'";';
		} else {
		$configContent .= '$_SESSION["pageURL"] = "";';
		}
		if(isset($_SESSION["bkgndImage"])){
		$bkgndImage = $_SESSION["bkgndImage"];
		$configContent .= '$_SESSION["bkgndImage"] = "'.$bkgndImage.'";';
		} else {
		$configContent .= '$_SESSION["bkgndImage"] = "";';
		}
		if(isset($_SESSION["cardImage"])){
		$cardImage = $_SESSION["cardImage"];
		$configContent .= '$_SESSION["cardImage"] = "'.$cardImage.'";';
		} else {
		$configContent .= '$_SESSION["cardImage"] = "";';
		}
	if(isset($_SESSION["cardAlt"])){
		$cardAlt = $_SESSION["cardAlt"];
		$configContent .= '$_SESSION["cardAlt"] = "'.$cardAlt.'";';
		} else {
		$configContent .= '$_SESSION["cardAlt"] = "";';
		}
	if(isset($_SESSION["cardSubtitle"])){
		$cardSubtitle = $_SESSION["cardSubtitle"];
		$configContent .= '$_SESSION["cardSubtitle"] = "'.$cardSubtitle.'";';
		} else {
		$configContent .= '$_SESSION["cardSubtitle"] = "";';
		}
	if(isset($_SESSION["cardText"])){
		$cardText = $_SESSION["cardText"];
		$configContent .= '$_SESSION["cardText"] = "'.$cardText.'";';
		} else {
		$configContent .= '$_SESSION["cardText"] = "";';
		}
	if(isset($_SESSION["category"])){
		$category = $_SESSION["category"];
		$configContent .= '$_SESSION["category"] = "'.$category.'";';
		} else {
		$configContent .= '$_SESSION["category"] = "";';
		}
	if(isset($_SESSION["authorname"])){
		$authorname = $_SESSION["authorname"];
		$configContent .= '$_SESSION["authorname"] = "'.$authorname.'";';
		} else {
		$configContent .= '$_SESSION["authorname"] = "";';
		}
	if(isset($_SESSION["scriptname"])){
		$scriptname = $_SESSION["scriptname"];
		$configContent .= '$_SESSION["scriptname"] = "'.$scriptname.'";';
		} else {
		$configContent .= '$_SESSION["scriptname"] = "";';
		}
	if(isset($_SESSION["pencilsname"])){
		$pencilsname = $_SESSION["pencilsname"];
		$configContent .= '$_SESSION["pencilsname"] = "'.$pencilsname.'";';
		} else {
		$configContent .= '$_SESSION["pencilsname"] = "";';
		}
	if(isset($_SESSION["inksname"])){
		$inksname = $_SESSION["inksname"];
		$configContent .= '$_SESSION["inksname"] = "'.$inksname.'";';
		} else {
		$configContent .= '$_SESSION["inksname"] = "";';
		}
	if(isset($_SESSION["colorsname"])){
		$colorsname = $_SESSION["colorsname"];
		$configContent .= '$_SESSION["colorsname"] = "'.$colorsname.'";';
		} else {
		$configContent .= '$_SESSION["colorsname"] = "";';
		}
	if(isset($_SESSION["lettersname"])){
		$lettersname = $_SESSION["lettersname"];
		$configContent .= '$_SESSION["lettersname"] = "'.$lettersname.'";';
		} else {
		$configContent .= '$_SESSION["lettersname"] = "";';
		}
	if(isset($_SESSION["publisher"])){
		$publisher = $_SESSION["publisher"];
		$configContent .= '$_SESSION["publisher"] = "'.$publisher.'";';
		} else {
		$configContent .= '$_SESSION["publisher"] = "";';
		}
	if(isset($_SESSION["audience"])){
		$audience = $_SESSION["audience"];
		$configContent .= '$_SESSION["audience"] = "'.$audience.'";';
		} else {
		$configContent .= '$_SESSION["audience"] = "";';
		}
	if(isset($_SESSION["artistname"])){
		$artistname = $_SESSION["artistname"];
		$configContent .= '$_SESSION["artistname"] = "'.$artistname.'";';
		} else {
		$configContent .= '$_SESSION["artistname"] = "";';
		}
	if(isset($_SESSION["cardemail"])){
		$cardemail = $_SESSION["cardemail"];
		$configContent .= '$_SESSION["cardemail"] = "'.$cardemail.'";';
		} else {
		$configContent .= '$_SESSION["cardemail"] = "";';
		}

	if(isset($_SESSION["oauth_id"])){
		$oauth_id = $_SESSION["oauth_id"];
		$configContent .= '$_SESSION["oauth_id"] = "'.$oauth_id.'";';
		} else {
		$configContent .= '$_SESSION["oauth_id"] = "";';
		}

	if(isset($_SESSION["email_id"])){
		$email_id = $_SESSION["email_id"];
		$configContent .= '$_SESSION["email_id"] = "'.$email_id.'";';
		} else {
		$configContent .= '$_SESSION["email_id"] = "";';
		}
	$configContent .= '?>';
// now have the config data as a string

// build the gallery card file content for this comic
$cardContent = '';
$cardContent .= '<article class="col-md-4 d-flex flex-direction:column justify-content-between align-items-center" tabindex="-1">
';
$cardContent .= '<div class="card mb-4 shadow-md #cfd8dc blue-grey lighten-4" tabindex="-1">';
$cardContent .= '<a href="'.$siteurl.$Comicname.'.html" hreflang="en" tabindex="0" class="bg-image view overlay">';
$cardContent .= '			<!--Card image-->';
if (!(isset($_SESSION['cardImage']))) {
	$cardContent .= '<img class="bd-placeholder-img card-img-top w-100" width="100%" height="auto" src="../Comics/Img/WebComicConcept.jpg" alt="a placeholder image for the card image.">';
} else {
	$cardContent .= '<img class="bd-placeholder-img card-img-top w-100" width="100%" height="auto" src="../Comics/'.$Comicname.'/'.$cardImage.'" alt="'.$cardAlt.'">';
}
$cardContent .= '<div class="mask" style="background-color: rgba(0, 0, 0, 0.6)">';
$cardContent .= '	<div  class="d-flex justify-content-center align-items-center h-100 text-white mb-0" style="cursor
: pointer;"><p><big>Open<br>'.$cardTitle.'</big></p></div>>';
$cardContent .= '	</div>';
$cardContent .= '	</a>';

$cardContent .= '	<div class="card-body">';
$cardContent .= '	 <!--Title-->';
$cardContent .= '<h2 class="card-title">'.$cardTitle.'</h2>';
$cardContent .= '<h3 class="card-title">'.$cardSubtitle.'</h3>';
$cardContent .= '<!--Text-->';
$cardContent .= '<p class="card-text text-dark">'.$cardText.'</p>';
if($category != '') {
	$cardContent .= '<button title="Category" type="button" class="btn btn-indigo galleryButton">'.$category.'</button>';}
if($authorname != '') {
	$cardContent .= '<button title="Author" type="button" class="btn btn-deep-purple galleryButton">'.$authorname.'</button>';}
if($scriptname != '') {
	$cardContent .= '<button title="Script Writer" type="button" class="btn btn-unique galleryButton">'.$scriptname.'</button>';}
if($pencilsname != '') {
	$cardContent .= '<button title="Pencils" type="button" class="btn btn-unique galleryButton">'.$pencilsname.'</button>';}
if($inksname != '') {
	$cardContent .= '<button title="Inks" type="button" class="btn btn-unique galleryButton">'.$inksname.'</button>';}
if($colorsname != '') {
	$cardContent .= '<button title="Coloring" type="button" class="btn btn-unique galleryButton">'.$colorsname.'</button>';}
if($lettersname != '') {
	$cardContent .= '<button title="Lettering" type="button" class="btn btn-unique galleryButton">'.$lettersname.'</button>';}
if($publisher != '') {
	$cardContent .= '<button title="Brand/Publisher" type="button" class="btn btn-purple galleryButton">'.$publisher.'</button>';}
if($audience != '') {
	$cardContent .= '<button title="Audience Category" type="button" class="btn btn-deep-orange text-dark galleryButton">'.$audience.'</button>';}
	$cardContent .= '<br><br><button title="Comic Generator" type="button" class="text-dark btn btn-light-blue galleryButton">A Storybook Comic</button>';
$cardContent .= '		</div>';
$cardContent .= '	  </div>';
$cardContent .= '	  </article>';

	/*	TABLE `comicdata`
	 `comic_id`
	 `oauth_id`
	 `comic_name`
	 `comic_title`
	 `comic_subtitle`
	 `comic_category`
	 `comic_author`
	 `comic_script`
	 `comic_pencils`
	 `comic_inks`
	 `comic_coloring`
	 `comic_lettering`
	 `comic_publisher`
	 `comic_audience`
	 `artistname`
	 `cardemail`
	 `created`
	 `lastview`
	 `views`
	*/
	//$Comicbook = array();
	$Comicbook = array();
    $Comicbook['oauth_id'] = $oauth_id;
    $Comicbook['comic_name']   = $Comicname;
    $Comicbook['comic_title'] = $cardTitle;
    $Comicbook['comic_subtitle'] = $cardSubtitle;
    $Comicbook['comic_category']	 = $category;
    $Comicbook['comic_author']   = $authorname;
    $Comicbook['comic_script']   = $scriptname;
    $Comicbook['comic_pencils']   	= $pencilsname;
    $Comicbook['comic_inks']   = $inksname;
    $Comicbook['comic_coloring'] = $colorsname;
    $Comicbook['comic_lettering']	 = $lettersname;
    $Comicbook['comic_publisher']   = $publisher;
    $Comicbook['comic_audience']   = $audience;
    $Comicbook['artistname']   	= $artistname;
    $Comicbook['cardemail']   = $cardemail;
    
    // Store comic data in the session
    $_SESSION['Comicbook'] = $Comicbook;
	// Insert comic data to the database
		if($checkbox == 1) { //db not already open?
		require("/var/www/includes/Comic.class.php");
		$comicbook = new Comic(); }
	$Data = $comicbook->insertComic($Comicbook);

// write the configuration values and comics gallery card files
if($Comicname === '') {
	$noComicname = 'No Configuration Data!!!<br>';
	$deletedOldConfig = '';
	$wroteNewConfig = '';
	$deletedOldCard = '';
	$wroteNewCard = '';
	$clearedDB = '';
	$clearedComic = '';
} else {
	// write the config values file
	$file = $comicsDir.$Comicname.'/'.$Comicname.'.php';
	$deletedOldConfig = '';
	if(file_exists($file)) {
		unlink($file);
		$deletedOldConfig = "Deleted previous Configuration File<br>";}
	$return = file_put_contents($file, $configContent);
	$wroteNewConfig = '';
	if($return !== false) {
		$wroteNewConfig = 'Wrote a new Config File ' . $Comicname . '<br>'; }
	// write the comics gallery card file
	$cfile = $comicsDir.$Comicname.'.card';
	$deletedOldCard = '';
	if(file_exists($cfile)) {
		unlink($cfile);
		$deletedOldCard = "Deleted previous gallery Card File<br>";}
	$return = file_put_contents($cfile, $cardContent);
	$wroteNewCard = '';
	if($return !== false) {
		$wroteNewCard = 'Wrote a new gallery Card File ' . $Comicname . '<br>'; }
}
}
}
header("Refresh: 0; URL=./index.php#CfgSave");
if($Comicname === '') {
	echo "invalid operation.<br/><br/>";}
if(!($Comicname === '')) {
	echo "$Comicname is a valid filename string.<br/><br/>";}
echo "checkbox was ' . $checkbox . '<br>";
if(!($noComicname === '')) {
	echo $noComicname; }
if(!($deletedOldConfig === '')) {
	echo $deletedOldConfig; }
if(!($clearedComic === '')) {
	echo $clearedComic; }
if($clearedCdb != '') {
	echo $clearedCdb; }
if($clearedIdb != '') {
	echo $clearedIdb; }
if(!($wroteNewConfig === '')) {
	echo $wroteNewConfig; }
if(!($deletedOldCard === '')) {
	echo $deletedOldCard; }
if(!($wroteNewCard === '')) {
	echo $wroteNewCard; }

?>


The saveConfig program creates an appropriately named comic folder and stores or updates an associated configuration file in the folder. It also creates and writes an HTML file for the comic gallery card. And it also calls a Comic.class.php program to create a database for this comic. The complete folder structure for the comic as shown here next is constructed by the remaining Builder programs.

comic folder structure



The saveConfig program saves a configuration file named for the comic that has (un-minified) contents like this next.

<?php require_once("/var/www/session2DB/Zebra.php");
\$_SESSION["siteurl"] = "https://syntheticreality.net/Comics/";
\$_SESSION["cardTitle"] = "Hyenas2";
\$_SESSION["Comicname"] = "Hyenas2";
\$_SESSION["pageURL"] = "https://syntheticreality.net/Comics/Hyenas2.html";
\$_SESSION["bkgndImage"] = "Hyenas2BKGND.webp";
\$_SESSION["cardImage"] = "Hyenas2CARD.webp";
\$_SESSION["cardAlt"] = "aerial view of High City urban buildings";
\$_SESSION["cardSubtitle"] = "&#34;Through Nanzinger Gate Wormhole with SheLa&#34;";
\$_SESSION["cardText"] = "In this episode we meet one of &#34;the girls&#34;, SheLa, who is a member of an urban social club aptly named &#34;The Catty Chatters&#34;.<br>SheLa is telling the ladies about her just completed jump through the Nanzinger Gate Wormhole and her adventures beyond. She is greeted with interest, such response being reflective of the group. Let&#39;s see how this plays out then.";
\$_SESSION["category"] = "Adult SciFi Sitcom";
\$_SESSION["authorname"] = "Bob Wright";
\$_SESSION["scriptname"] = "";
\$_SESSION["pencilsname"] = "";
\$_SESSION["inksname"] = "";
\$_SESSION["colorsname"] = "";
\$_SESSION["lettersname"] = "";
\$_SESSION["publisher"] = "Raw Material Comics";
\$_SESSION["audience"] = "General Adult";
\$_SESSION["artistname"] = "Bob Wright";
\$_SESSION["cardemail"] = "[email protected]";
\$_SESSION["oauth_id"] = "";
\$_SESSION["email_id"] = "[email protected]";?>

Gathering content

The .index program then invokes a number of programs in a sequence that gathers further content for our comic. These subsequent programs then call subordinate programs to provide the functional dependencies for the content selecting program. For example, clicking Next at the foot of the .index screen directs us to a program named getComic.php which in turn invokes a JavaScript front end (vpb_uploader.js) that makes AJAX calls, one for each image, to a main image upload support program named imgUploader.php. Here next below is our getComic.php screen display.

getComic screen display


And here next we have the getComic.php program itself.

<?php
// disable error reporting for production code
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
// Start session
session_name("Storybook");
include("/var/www/session2DB/Zebra.php");
//	session_start();
// -----------------------
$panelcount = $_SESSION['panelcount'];	
$_SESSION['pageimage'] = 'Comic';
$head1 = <<< EOT1
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
	<title>Comic Images Upload</title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
	<meta NAME="Last-Modified" CONTENT="
EOT1;
echo $head1;
echo date ("F d Y H:i.", getlastmod()).'">';
$head2 = <<< EOT2
	<meta name="description" content="Web Image Comic Builder">
	<meta name="description" content="Multiple Image File Uploader">
	<meta name="copyright" content="Copyright 2019 by IsoBlock.com">
	<meta name="author" content="Bob Wright">
	<meta name="keywords" content="web page">
	<meta name="rating" content="general">
	<meta name="robots" content="index, follow"> 
	<base href="https://syntheticreality.net/Storybook/">
	<link href="../CSS/SiteFonts.css" media="screen" rel="stylesheet" type="text/css"/>
	<link href="../CSS/materialdesignicons.css" media="screen" rel="stylesheet" type="text/css"/>
	<link href="./css/bootstrap.css" rel="stylesheet" type="text/css" media="screen">
    <link href="./css/DT_bootstrap.css" rel="stylesheet" type="text/css" >
	<link href="./css/ComicCreator.css" rel="stylesheet" type="text/css">
	<link href="./css/ComicBuilder.css" rel="stylesheet" type="text/css">
<style>
input[type=text]:focus {
  background-color: GreenYellow;
}
input[type=submit]:focus {
  background-color: GreenYellow;
}
input[type=checkbox]:focus {
  background-color: GreenYellow;
}
input[type=file]:focus {
  background-color: GreenYellow;
}
textarea:focus {
  background-color: GreenYellow;
}
a:focus {
  background-color: GreenYellow;
}
</style>
</head>
<body>
	<script src="./js/jquery.min.js"></script>
	<script src="./js/bootstrap.js"></script>
	<script src="./js/jquery.dataTables.js" charset="utf-8"></script>
	<script src="./js/DT_bootstrap.js" charset="utf-8"></script>
	<script src="./js/vpb_uploader.js"></script>
<script>
$(document).ready(function()
{
	// Call the main function
	new vpb_multiple_file_uploader
	({
		vpb_form_id: "form_id", // Form ID
		autoSubmit: true,
		vpb_server_url: "imgUploader.php" 
	});
});
</script>
<main class="pageWrapper" id="container">
<h1 style="color:blue; text-align:center;">Comic Image Files Upload</h1>
<!-- quick display of info about the upload requirements -->
<div class="msgBox"><h2 style="color:purple;">This page will let you select and upload the images which will be included in the Comic.</h2>
<h3 style="color:purple;">Storybook Comic Builder allows the upload of up to <b>
EOT2;
echo $head2;
echo '<span id="panelcount">' . $panelcount . '</span>';
$head3 = <<< EOT3
</b> image files for inclusion in the Comic. Each image represents one full display screen in the Comic. The images will be ordered by their names.</h3></div><br>
<div class="infoBox"><p>Storybook Comic Builder allows <b>GIF, JPG,</b> and <b>PNG</b> image filetypes (case insensitive). Uploaded images must be in one of these three formats and have a valid filename of up to 255 characters. At this time filenames must contain <b>ONLY</b> the English alphabetic characters <b>A</b> through <b>Z</b>, <b>a</b> through <b>z</b>, and the digits <b>0</b> through <b>9</b>.  Nonalphanumeric or special characters are limited to <em>spaces, underscores, dashes,</em> or <em>periods</em> and a few others as normal filename use allows.</p>
<p>Storybook Comic Builder will not upload content located on another site by a URL; you may only upload image files from your device.</b> Image files are limited to 5 Megabytes (5 Megabytes = 5,242,880 Bytes) maximum filesize for <b>JPG</b> and <b>PNG</b> files while <b>GIF</b> files are limited to 2 Megabytes (2 Megabytes = 2,097,152 Bytes) maximum filesize. All <b>JPG</b> and <b>PNG</b> image width and height dimensions must be at least 400 but not more than 1600 pixels. A good "rule of thumb" would be a maximum of about 1440 pixels per side for JPG and PNG images. All <b>GIF</b> image width and height dimensions must be at least 120 but not more than 640 pixels. Note that all metadata content (<em>eg</em> EXIF data) will be removed from uploaded image files.</p></div>
<br>

<center>
<div class="infoBox">
<form name="form_id" id="form_id" action="javascript:void(0);" enctype="multipart/form-data">
	<label>Select and upload the Comic Image files:<br><input type="file" accept=".jpg,.JPG,.gif,.GIF,.png,.PNG" name="vasplus_multiple_files" id="vasplus_multiple_files" multiple="multiple" tabindex="1">
	<input type="submit" value="Upload Selected Files" id="Upload" tabindex="2"></label>
</form>
<br></div>
<hr class="new4">
<table class="table table-striped table-bordered" id="add_files">
	<thead>
		<tr>
			<th style="color:blue; text-align:center;">File Name</th>
			<th style="color:blue; text-align:center;">Status</th>
			<th style="color:blue; text-align:center;">File Size</th>
			<th style="color:blue; text-align:center;">File Date</th>
			<th style="color:blue; text-align:center;">Action</th>
		<tr>
	</thead>
	<tbody>
	
	</tbody>
</table>
</center>
EOT3;
echo $head3;
	echo
	'<footer class="d-flex col-sm-12 flex-column align-items-center shadow-md #b0bec5 blue-grey lighten-3 px-sm-0 infoBox" style="margin-left:0;" id="ComicFooter">'.
	'<nav id="ComicFooter"><p><a id="prevpagebutton" href="index.php" title="return to configuration settings" tabindex="4">❮ Previous</a>&emsp;&copy; 2020 by&nbsp;<span><svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="40px" height="40px" viewBox="0 0 40.000000 40.000000" preserveAspectRatio="xMidYMid meet">
	<g transform="translate(0.000000,40.000000) scale(0.100000,-0.100000)"
	fill="#000000" stroke="none">
	<path d="M97 323 l-97 -78 0 -122 0 -123 200 0 201 0 -3 127 -3 127 -93 73
	c-52 40 -97 73 -101 73 -4 0 -51 -35 -104 -77z m184 -9 c43 -30 79 -59 79 -63
	0 -6 -63 -41 -75 -41 -3 0 -5 14 -5 30 l0 30 -85 0 -85 0 0 -30 c0 -16 -4 -30
	-10 -30 -16 0 -60 23 -60 31 0 9 142 128 153 129 5 0 45 -25 88 -56z m97 -177
	c1 -48 -1 -87 -5 -87 -10 0 -163 94 -163 100 0 9 144 79 155 76 6 -1 11 -42
	13 -89z m-273 51 c36 -18 65 -35 65 -38 0 -6 -125 -101 -143 -108 -4 -2 -7 37
	-7 87 0 53 4 91 10 91 5 0 39 -14 75 -32z m174 -99 c45 -29 81 -56 81 -60 0
	-5 -73 -9 -161 -9 -149 0 -160 1 -148 17 17 19 130 103 140 103 4 0 44 -23 88
	-51z"/>
	</g>
	</svg></span>&nbsp;<a href="mailto:[email protected]">Bob Wright.</a>&nbsp;Last modified ';
		echo date ("F d Y H:i.", getlastmod()).'&emsp;<a id="nextpagebutton" href="./getAltText.php" title="get Alt text for each image" tabindex="3">Next ❯</a></p></nav>'.
		'</footer>';
$scr = <<< SCR1
      <script>
window.onload = function() {
			var panelcount = $panelcount;
			console.info(panelcount);
           $("input[type = 'submit']").click(function(){
               var fileUpload = $("input[type='file']");
               if (parseInt(fileUpload.get(0).files.length) > panelcount){
                  alert("You are only allowed to upload " + panelcount + " Comic image files!");
				  window.location.replace("https://syntheticreality.net/Storybook/getComic.php");
               }
            });
         };
      </script>
</main>
</body>
</html>
SCR1;
echo $scr;
?>


Here next is the vpb_uploader.js program which I appropriated to this use. It is a great introduction the AJAX calls

/***********************************************************************************************************
* This script was adapted from Vasplus Programming Blog
   Fancy Multiple File Upload using Ajax, Jquery and PHP
   Written by Vasplus Programming Blog
   Website: www.vasplus.info
   Email: [email protected]
  
  *********************************Copyright Information*****************************************************
   This script has been released with the aim that it will be useful.
   Please, do not remove this copyright information from the top of this page.
  If you want the copyright info to be removed from the script then you have to buy this script.
  This script must not be used for commercial purpose without the consent of Vasplus Programming Blog.
  This script must not be sold.
 All Copy Rights Reserved by Vasplus Programming Blog
 14*************************************************************************************************************/

function vpb_multiple_file_uploader(vpb_configuration_settings)
{
	this.vpb_settings = vpb_configuration_settings;
	this.vpb_files = "";
	this.vpb_browsed_files = []
	var self = this;
	var vpb_msg = "Sorry, your browser does not support this application. Thank You!";
	
	//Get all browsed file extensions
	function vpb_file_ext(file) {
		return (/[.]/.exec(file)) ? /[^.]+$/.exec(file.toLowerCase()) : '';
	}
	
	/* Display added files which are ready for upload */
	//with their file types, names, size, date last modified along with an option to remove an unwanted file
	vpb_multiple_file_uploader.prototype.vpb_show_added_files = function(vpb_value)
	{
		this.vpb_files = vpb_value;
		if(this.vpb_files.length > 0)
		{
			var vpb_added_files_displayer = vpb_file_id = "";
 			for(var i = 0; i<this.vpb_files.length; i++)
			{
				//Use the names of the files without their extensions as their ids
				var files_name_without_extensions = this.vpb_files[i].name.substr(0, this.vpb_files[i].name.lastIndexOf('.')) || this.vpb_files[i].name;
				//.replace(/[\s]/g, '_')
				vpb_file_id = files_name_without_extensions.replace(/[^a-z0-9\s]/gi, '').replace(/[\s]/g, '_');
				
				var vpb_file_to_add = vpb_file_ext(this.vpb_files[i].name);
				var vpb_class = $("#added_class").val();
				var vpb_file_icon;
				
				//Check and display File Size
				var vpb_fileSize = (this.vpb_files[i].size / 1024);
				if (vpb_fileSize / 1024 > 1)
				{
					if (((vpb_fileSize / 1024) / 1024) > 1)
					{
						vpb_fileSize = (Math.round(((vpb_fileSize / 1024) / 1024) * 100) / 100);
						var vpb_actual_fileSize = vpb_fileSize + " GB";
					}
					else
					{
						vpb_fileSize = (Math.round((vpb_fileSize / 1024) * 100) / 100)
						var vpb_actual_fileSize = vpb_fileSize + " MB";
					}
				}
				else
				{
					vpb_fileSize = (Math.round(vpb_fileSize * 100) / 100)
					var vpb_actual_fileSize = vpb_fileSize  + " KB";
				}
				
				//Check and display the date that files were last modified
				var vpb_date_last_modified = new Date(this.vpb_files[i].lastModifiedDate);
				var hh = vpb_date_last_modified.getHours();
				var nn = vpb_date_last_modified.getMinutes();
				if (nn <= 9) {nn = '0' + nn;}
				var dd = vpb_date_last_modified.getDate();
				var mm = vpb_date_last_modified.getMonth() + 1;
				var yyyy = vpb_date_last_modified.getFullYear();
				var vpb_date_last_modified_file = mm + '/' + dd + '/' + yyyy + '@' + hh + ':' + nn;
				
				//File Display Classes
				if( vpb_class == 'vpb_blue' ) { 
					var new_classc = 'vpb_white';
				} else {
					var new_classc = 'vpb_blue';
				}
				
				if(typeof this.vpb_files[i] != undefined && this.vpb_files[i].name != "")
				{
					//Check for the type of file browsed so as to represent each file with the appropriate file icon
					
					vpb_file_icon = '<img src="images/general.png" width="60px" align="absmiddle" border="0" alt="" />';

					if( vpb_file_to_add == "jpg" || vpb_file_to_add == "JPG" || vpb_file_to_add == "jpeg" || vpb_file_to_add == "JPEG" || vpb_file_to_add == "gif" || vpb_file_to_add == "GIF" || vpb_file_to_add == "png" || vpb_file_to_add == "PNG" || vpb_file_to_add == "mp3" || vpb_file_to_add == "MP3" ) 
					{
						vpb_file_icon = '<img src="images/images_file.gif" width="60px" align="absmiddle" border="0" alt="" />';
					}
					if( vpb_file_to_add == "txt" || vpb_file_to_add == "TXT") 
					{
						vpb_file_icon = '<img src="images/txt.png" width="60px" align="absmiddle" border="0" alt="" />';
					}
					
					//Assign browsed files to a variable so as to later display them below
					vpb_added_files_displayer += '<tr id="add_fileID'+vpb_file_id+'" class="'+new_classc+'"><td>'+vpb_file_icon+' '+this.vpb_files[i].name.substring(0, 48)+'</td><td><span id="uploading_'+vpb_file_id+'"><span style=color:blue;>Ready</span></span></td><td>'+vpb_actual_fileSize+'</td><td>'+vpb_date_last_modified_file+'</td><td><span id="remove'+vpb_file_id+'"><span class="vpb_files_remove_left_inner" onclick="vpb_remove_this_file(\''+vpb_file_id+'\',\''+this.vpb_files[i].name+'\');">Remove</span></span></td></tr></div>';
					
				}
			}
			//Display browsed files on the screen to the user who wants to upload them
			$("#add_files").append(vpb_added_files_displayer);
			$("#added_class").val(new_classc);
		}
	}
	
	//File Reader
	vpb_multiple_file_uploader.prototype.vpb_read_file = function(vpb_e) {
		if(vpb_e.target.files) {
			self.vpb_show_added_files(vpb_e.target.files);
			self.vpb_browsed_files.push(vpb_e.target.files);
		} else {
			alert('Sorry, a file you have specified could not be read.');
		}
	}
	
	function addEvent(type, el, fn){
	if (window.addEventListener){
		el.addEventListener(type, fn, false);
	} else if (window.attachEvent){
		var f = function(){
		  fn.call(el, window.event);
		};			
		el.attachEvent('on' + type, f)
	}
}
	
	//Get the ids of all added files and also start the upload when called
	vpb_multiple_file_uploader.prototype.vpb_starter = function() {
		if (window.File && window.FileReader && window.FileList && window.Blob) {		
			 var vpb_browsed_file_ids = $("#"+this.vpb_settings.vpb_form_id).find("input[type='file']").eq(0).attr("id");
			 document.getElementById(vpb_browsed_file_ids).addEventListener("change", this.vpb_read_file, false);
			 document.getElementById(this.vpb_settings.vpb_form_id).addEventListener("submit", this.vpb_submit_added_files, true);
		} 
		else { alert(vpb_msg); }
	}
	
	//Call the uploading function on the upload button
	vpb_multiple_file_uploader.prototype.vpb_submit_added_files = function(){ self.vpb_upload_bgin(); }
	
	//Start uploads
	vpb_multiple_file_uploader.prototype.vpb_upload_bgin = function() {
		if(this.vpb_browsed_files.length > 0) {
			for(var k=0; k<this.vpb_browsed_files.length; k++){
				var file = this.vpb_browsed_files[k];
				this.vasPLUS(file,0);
			}
		}
	}
	
	//Main file uploader
	vpb_multiple_file_uploader.prototype.vasPLUS = function(file,file_counter)
	{
		if(typeof file[file_counter] != undefined && file[file_counter] != '')
		{
			//Use the file names without their extensions as their ids
			var files_name_without_extensions = file[file_counter].name.substr(0, file[file_counter].name.lastIndexOf('.')) || file[file_counter].name;
			var ids = files_name_without_extensions.replace(/[^a-z0-9\s]/gi, '').replace(/[\s]/g, '_');
			var vpb_browsed_file_ids = $("#"+this.vpb_settings.vpb_form_id).find("input[type='file']").eq(0).attr("id");
			
			var removed_file = $("#"+ids).val();
			
			if ( removed_file != "" && removed_file != undefined && removed_file == ids )
			{
				self.vasPLUS(file,file_counter+1);
			}
			else
			{
				var dataString = new FormData();
				dataString.append('upload_file',file[file_counter]);
				dataString.append('upload_file_ids',ids);
					
				$.ajax({
					type:"POST",
					url:this.vpb_settings.vpb_server_url,
					data:dataString,
					cache: false,
					contentType: false,
					processData: false,
					beforeSend: function() 
					{
						$("#uploading_"+ids).html('<div align="left"><img src="images/progress.gif" width="60px" align="absmiddle"></div>');
						$("#remove"+ids).html('<div align="center" style="font-family:Verdana, Geneva, sans-serif;color:blue;">Uploading...</div>');
						//alert("here we are");
					},
					success:function(response) 
					{
						setTimeout(function() {
							var response_brought = response.indexOf(ids);
							if ( response_brought != -1) {
								$("#uploading_"+ids).html('<div align="left" style="font-family:Verdana, Geneva, sans-serif;color:green;">Completed</div>');
								$("#remove"+ids).html('<div align="center" style="font-family:Verdana, Geneva, sans-serif;color:green;">Uploaded</div>');
							} else {
								var fileType_response_brought = response.indexOf('file_type_error');
								if ( fileType_response_brought != -1) {
									
									var filenamewithoutextension = response.replace('file_type_error&', '').substr(0, response.replace('file_type_error&', '').lastIndexOf('.')) || response.replace('file_type_error&', '');
									var fileID = filenamewithoutextension.replace(/[^a-z0-9\s]/gi, '').replace(/[\s]/g, '');
									$("#uploading_"+fileID).html('<div align="left" style="font-family:Verdana, Geneva, sans-serif;color:red;">Invalid File</div>');
									$("#remove"+fileID).html('<div align="center" style="font-family:Verdana, Geneva, sans-serif;color:orange;">Cancelled</div>');
									
								} else {
									var filesize_response_brought = response.indexOf('file_size_error');
									if ( filesize_response_brought != -1) {
										var filenamewithoutextensions = response.replace('file_size_error&', '').substr(0, response.replace('file_size_error&', '').lastIndexOf('.')) || response.replace('file_size_error&', '');
										var fileID = filenamewithoutextensions.replace(/[^a-z0-9\s]/gi, '').replace(/[\s]/g, '');
										$("#uploading_"+fileID).html('<div align="left" style="font-family:Verdana, Geneva, sans-serif;color:red;">Exceeded Size</div>');
										$("#remove"+fileID).html('<div align="center" style="font-family:Verdana, Geneva, sans-serif;color:orange;">Cancelled</div>');
									} else {
										var general_response_brought = response.indexOf('general_system_error');
										if ( general_response_brought != -1) {
											alert('Sorry, the file was not uploaded...');
											$("#uploading_"+ids).html('<div align="left" style="font-family:Verdana, Geneva, sans-serif;color:red;">Failed...</div>');
											$("#remove"+ids).html('<div align="center" style="font-family:Verdana, Geneva, sans-serif;color:red;">Upload failed...</div>');
										}
										else { /* Do nothing */}
									}
								}
							}
							if (file_counter+1 < file.length ) {
								self.vasPLUS(file,file_counter+1); 
							} 
							else {}
						},2000);
					}
				});
			 }
		} 
		else { alert('Sorry, this system could not verify the identity of the file you were trying to upload.'); }
	}
	this.vpb_starter();
}

function vpb_remove_this_file(id, filename)
{
	if(confirm('If you are sure to remove the file: '+filename+' then click on OK otherwise, Cancel it.'))
	{
		$("#vpb_removed_files").append('<input type="hidden" id="'+id+'" value="'+id+'">');
		$("#add_fileID"+id).slideUp();
	}
	return false;
}


And finally we invoke the imgUploader.php program that does most of our various image uploading and preparation tasks. In fact imgUploader was used directly by index.php to upload the card image and the web comic page background images.

<?php
/*
 * filename: imgUploader.php
 * this code processes the image files uploaded by the Upload form
 * we got here by an AJAX call from the javascript form handler
*/
// Start session
session_name("Storybook");
include("/var/www/session2DB/Zebra.php");
//	session_start();
// Include Comic class
require_once("/var/www/includes/ComicImages.class.php");

// set some variables 
// $Comicname = $_SESSION['Comicname'];
// localpath for the image files
$uploadDir = "/var/www/uploads/";
$_SESSION['uploadDir'] = $uploadDir;
$comicsDir = "/var/www/Comics/htdocs/";
// filename for the clean image file 
$cleanImage = 'KleenCImuge.jpg'; 
// data for verbose report
$rindex = 0;
$report = array();
$Errindex = 0;
$ErrReport = array();
$vpb_size_total = 0;
// ***
// process the upload
// ***
////echo '<script> alert("here we are"); </script>';
// see if POST and uploaded file
if (($_SERVER["REQUEST_METHOD"] == "POST") && (!(empty($_POST) && empty($_FILES) && ($_SERVER['CONTENT_LENGTH'] > 0)))) {
	if ((file_exists($_FILES["upload_file"]["tmp_name"])) && (is_uploaded_file($_FILES["upload_file"]["tmp_name"]))) {
	$vpb_file_name = strip_tags($_FILES['upload_file']['name']); //File Name
	$vpb_file_id = strip_tags($_POST['upload_file_ids']); // File id is gotten from the file list
	$vpb_final_location = $uploadDir . $vpb_file_name; //Directory to save file plus the file to be saved
	$_SESSION['vpb_final_location'] = $vpb_final_location;
		// move the uploaded file to our folder
	if (file_exists($_FILES["upload_file"]["tmp_name"])) {	$_SESSION['tmpname'] = $_FILES["upload_file"]["tmp_name"];}
	if (move_uploaded_file(strip_tags($_FILES['upload_file']['tmp_name']), $vpb_final_location)) {
		$report[$rindex] = "Uploaded Filename: " . $vpb_file_name . "<br>";
		$rindex = $rindex+1;
	$xmlFile = pathinfo($vpb_file_name);
	$fname = $xmlFile['filename'];
	$vpb_file_type = $xmlFile['extension'];
$_SESSION['fname'] = $fname;
	// check the filetype/extension, filename length, invalid filenames, allowed characters, and minimum-maximum file size
	if (!(preg_match('/jpeg|jpg|gif|png/i', ($xmlFile['extension'])) && ($_SESSION['pageimage'] != ''))) {
		$ErrReport[$Errindex] = "Invalid filetype/extension. ".$vpb_file_name."<br>";$Errindex = $Errindex+1;}
	$vpb_file_nameLen = strlen($fname);
		$report[$rindex] = "Uploaded Filename length: " . $vpb_file_nameLen . "<br>";
		$rindex = $rindex+1;
	if (($vpb_file_nameLen > 255) || ($vpb_file_nameLen = 0)) {
			$ErrReport[$Errindex] = "Invalid filename length.<br>";$Errindex = $Errindex+1;}
	if (!(preg_match('/^(?!^(?:PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d)(?:\..+)?$)(?:\.*?(?!\.))[^\x00-\x1f\\?*:\";|\/<>]+(?<![\s.])$/', $xmlFile['filename']))) {
			$ErrReport[$Errindex] = "Invalid filename.<br>";$Errindex = $Errindex+1;}
	if (!(preg_match('/^([A-Za-z0-9]*[!_\+\.\(\)\s^]?)*$/', $fname))) {
			// removed dashes we use as delimiters from permitted list
			$ErrReport[$Errindex] = "Invalid filename characters.<br>";$Errindex = $Errindex+1;}
	if (preg_match('/\s/', $fname)) {
		$fname = preg_replace("/\s+/", "_", $fname); // convert spaces
		rename($uploadDir.$vpb_file_name, $uploadDir.$fname.'.'.$vpb_file_type);
			$vpb_file_name = $fname.'.'.$vpb_file_type;
			$report[$rindex] = "Replaced spaces: " . $uploadDir.$fname.'.'.$vpb_file_type . "<br>";
			$rindex = $rindex+1;}

	list($width, $height, $type, $attr) = getimagesize($uploadDir.$vpb_file_name);
		$report[$rindex] = "Uploaded File type code: " . $type . "<br>";
		$rindex = $rindex+1;
	$_SESSION['wxh'] = $width.'x'.$height;

if (!(($width == 1 && $height == 1) && (($_SESSION['pageimage'] == 'Comic') || ($_SESSION['pageimage'] == 'altImgs')))) { //skip for our special noimage caption only image file
	// 16 Megabytes = 16,777,215 10 Megabytes = 10485760 Bytes, 8 Megabytes = 8388608 Bytes
	// 5 Megabytes = 5,242,880 Bytes, 3 Megabytes = 3,145,728, 2 Megabytes = 2,097,152,
	// 1 Megabytes = 1,048,576, 500 Kbytes = 524,288
   switch ($type)
     {case 1:   //   gif
		$vpb_file_type = 'gif';
        $minW = 120; $minH = 120; $minSize = 1024;
		$maxW = 800; $maxH= 800; $maxSize = 8388608;
        break;
      case 2:   //   jpeg
		$vpb_file_type = 'jpg';
        $minW = 300; $minH = 300; $minSize = 1024;
		$maxW = 1600; $maxH= 1600; $maxSize = 5242880;
        break;
      case 3:  //   png
		$vpb_file_type = 'png';
		if($width = 1 && $height = 1) {
        $minW = 1; $minH = 1; $minSize = 10;
		} else {
        $minW = 300; $minH = 300; $minSize = 1024; }
		$maxW = 1600; $maxH= 1600; $maxSize = 5242880;
        break;
     }
		$report[$rindex] = "Uploaded File type: " . $vpb_file_type . "<br>";
		$rindex = $rindex+1;

	$vpb_file_size = $_FILES["upload_file"]["size"];
			$report[$rindex] = "Uploaded Image file size: " . $vpb_file_size . "<br>";
			$rindex = $rindex+1;
	if (($vpb_file_size < $minSize) || ($vpb_file_size > $maxSize)) {
			// echo "File Size out of range."
			$ErrReport[$Errindex] = "File Size out of range.<br>";$Errindex = $Errindex+1;}

	if ((($width >= $minW) && ($height >= $minH)) || (($width <= $maxW) && ($height <= $maxH))) {
		$report[$rindex] = "Image pixel dimensions: " . $width . " wide X " . $height . " high<br>";
		$rindex = $rindex+1;
	} else {
		// echo "File Size out of range."
		$ErrReport[$Errindex] = "File pixel dimensions are out of range " . $width . " wide X " . $height . " high<br>";
		$Errindex = $Errindex+1;
	}
//	$_SESSION['srcdims'] = $width . " wide X " . $height . " high";

/*
* We save png as jpg images and save jpg images as jpg images and save gif images as gif images
* we save copies of png and jpg and gif images as webp images
*
* Initially we made 3 image sizes for these width breakpoints
* we prefer to downscale from larger to smaller scale,
* but we enlarge images smaller than 1024 to 1200 width
*
* Small (smaller than 577px) we made a 480 edge image
* Medium (577px to 1079px) we made a 720 edge image
* Large (1080px and larger) we made a 1200 edge image
*
* That worked well enough to prompt the addition of two more srcset sizes
* and a readjustment of the breakpoints and sizes to get more granularity
* These values are based upon the Bootstrap breakpoint choices
* breakpoint names	X- Small Small   Medium  Large   X- Large XX- Large
* breakpoints       <576px	≥576px	≥768px	≥992px	≥1200px	≥1400px
* .container		100%	540px	720px	960px	1140px	1320px
* image sizes				576px	768px	992px	1200px	1400px
*
*/
	//$srcname = $uploadDir.$vpb_file_name;
	if($vpb_file_type == 'gif') {
		// imagecreatefromgif doesn't like animations
		//$img = imagecreatefromgif($uploadDir.$vpb_file_name);
		//imagegif($img, $uploadDir.$cleanImage);}
	//exec('gif2webp -q ' . $quality . ' ' . $jpegFileName . ' -o ' . $webpFileName);
	$srcname = $uploadDir.$vpb_file_name;
		$report[$rindex] = "srcname: " . $srcname . "<br>";
		$rindex = $rindex+1;
	$outname = $uploadDir.$fname;
		$report[$rindex] = "outname: " . $outname . "<br>";
		$rindex = $rindex+1;
	$g2wcommand = 'gif2webp -q 75 "'.$srcname.'" -o "'.$outname.'.webp"';
		$report[$rindex] = "g2wcommand: " . $g2wcommand . "<br>";
		$rindex = $rindex+1;
	exec($g2wcommand);	// now have a webp of uploaded gif image
	// ffmpeg -i input.gif -strict -1 -f yuv4mpegpipe -pix_fmt yuv444p - | avifenc --stdin  output.avif
	$g2acommand = 'ffmpeg -i "'.$srcname.'" -strict -1 -f yuv4mpegpipe -pix_fmt yuv444p - | avifenc --stdin "'.$outname.'.avif"';
		$report[$rindex] = "g2acommand: " . $g2acommand . "<br>";
		$rindex = $rindex+1;
	exec($g2acommand);	// now have a avif of uploaded gif image
	}
/*	if($vpb_file_type == 'jpg'){ // regenerate jpgs
		$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$madejpeg = imagejpeg($img, $uploadDir.$vpb_file_name, 100);
		//imagewebp($uploadDir.$vpb_file_name, $uploadDir.$ifilename.'.webp', 100);
	// now have a recreated jpg instead of uploaded jpg image
	 $_SESSION['savedjpg'] = $madejpeg;
	} */
	if($vpb_file_type == 'png'){ // regenerate pngs as jpgs
		$img = imagecreatefrompng($uploadDir.$vpb_file_name);
		imageAlphaBlending($img, true);
		imageSaveAlpha($img, true);
		//imagepng($img, $uploadDir.$vpb_file_name);
		$madejpg2 = imagejpeg($img, $uploadDir.$fname.'.jpg', 100); // now our png is also a jpg
		unlink($uploadDir.$fname.'.png');
		$vpb_file_name = $fname.'.jpg';
		$vpb_file_type = 'jpg';
		$report[$rindex] = "Saved ".$fname." png file as jpg<br>";
		$rindex = $rindex+1;
	// now have a jpg instead of uploaded png image
	}
	 // now have GIF and webp and avif for uploaded gif, or jpg for uploaded jpg or png

	// ---------------------------
	// check the image dimensions if not OG or LOGO image OR gif
	if(!((($_SESSION['pageimage']) == 'LOGO') || (($_SESSION['pageimage']) == 'OGIMG')) || ($vpb_file_type != 'gif')) {
		if(($width < 1024) && ($height < 1024)) {
			$ErrReport[$Errindex] = "Image Dimension out of range. One of h or w must be >= 1024: " . $width . " w X " . $height . " h<br>";
			$Errindex = $Errindex+1;
		}
	// $_SESSION['BrkPt8'] = "BP8";
	}
	if(($_SESSION['pageimage'] == 'CARD') && !($vpb_file_type == 'gif')) { // scale non GIF card image
		//$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuwidth = 768; $nuheight = intval((768/$width)*$height);
			$tagheight = $nuheight;
			if(($nuheight >= 100) && ($nuheight <= 999)) {$tagheight = '0' . $nuheight;}
			if($nuheight <= 99) {$tagheight = '00' . $nuheight;}
		$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuimg = imagescale($img, $nuwidth, $nuheight); //imagescale($img, $nuwidth, $nuheight, IMG_BICUBIC) does not work
		$csrcname = $fname;
		$csrcjpeg = $fname.'.jpg';
		$csrc = $uploadDir . $csrcjpeg;
			imagejpeg($nuimg, $csrc, 100);
			$report[$rindex] = "csrcfile: " . $csrcfile . "<br>";
			$rindex = $rindex+1;
			$report[$rindex] = "CARD Image pixel dimensions: " . $nuwidth . " wide X " . $nuheight . " high<br>";
			$rindex = $rindex+1;
		$outname = $uploadDir.$csrcname;
			$report[$rindex] = "outname: " . $outname . "<br>";
			$rindex = $rindex+1;
		$nuwebp = imagewebp($nuimg, $outname.'.webp', -1);
		// imageavif($nuimg, $outname.'.avif', -1, -1); //does not work
		exec('convert '.$csrc.' -quality 35% '.$outname.'.avif');
		// now have a medium size jpg and a webp and an avif of a static CARDimage
	}

if(!((($_SESSION['pageimage']) == 'LOGO') || (($_SESSION['pageimage']) == 'OGIMG') || ($_SESSION['pageimage'] == 'CARD') || ($vpb_file_type == 'gif'))) { // scale other non GIF images
	//  breakpoint image widths 576px	768px	992px	1200px	1400px
	//  container widths 540px	720px	960px	1140px	1320px
	//  create jpg and webp and avif for each breakpoint size
 		if($width <> 1400) { // scale the image to 1400 width
			// can use -1 for imagescale height maintains aspect ratio
			//if($width >= $height) {$nuwidth = 1400; $nuheight = -1;}
			//if($height > $width) {$nuwidth = (1400/$height)*$width; $nuheight = 1400;}
			$nuwidth = 1400; $nuheight = intval((1400/$width)*$height);
		} else { // already 1400 pixels wide
			$nuwidth = 1400; $nuheight = $height;
		}
			$tagheight = $nuheight;
			if(($nuheight >= 100) && ($nuheight <= 999)) {$tagheight = '0' . $nuheight;}
			if($nuheight <= 99) {$tagheight = '00' . $nuheight;}
		$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuimg = imagescale($img, $nuwidth, $nuheight); //imagescale($img, $nuwidth, $nuheight, IMG_BICUBIC) does not work
		$Xsrcname = $fname.'-X-' . $tagheight;
		$Xsrcjpeg = $fname.'-X-' . $tagheight .'.jpg';
		$Xsrc = $uploadDir . $Xsrcjpeg;
			imagejpeg($nuimg, $Xsrc, 100);
			$report[$rindex] = "Xsrcfile: " . $Xsrcfile . "<br>";
			$rindex = $rindex+1;
			$report[$rindex] = "XX-Large Image pixel dimensions: " . $nuwidth . " wide X " . $nuheight . " high<br>";
			$rindex = $rindex+1;
		$outname = $uploadDir.$Xsrcname;
			$report[$rindex] = "outname: " . $outname . "<br>";
			$rindex = $rindex+1;
		$nuwebp = imagewebp($nuimg, $outname.'.webp', -1);
		// imageavif($nuimg, $outname.'.avif', -1, -1); //does not work
		exec('convert '.$Xsrc.' -quality 35% '.$outname.'.avif');
	// now have a jpg and a webp and an avif at 1400 width extra-extra-large size

		//$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuwidth = 1200; $nuheight = intval((1200/$width)*$height);
			$tagheight = $nuheight;
			if(($nuheight >= 100) && ($nuheight <= 999)) {$tagheight = '0' . $nuheight;}
			if($nuheight <= 99) {$tagheight = '00' . $nuheight;}
		$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuimg = imagescale($img, $nuwidth, $nuheight);
		$xsrcname = $fname.'-x-' . $tagheight;
		$xsrcjpeg = $fname.'-x-' . $tagheight .'.jpg';
		$xsrc = $uploadDir . $xsrcjpeg;
			imagejpeg($nuimg, $xsrc, 100);
			$report[$rindex] = "xsrcfile: " . $xsrcfile . "<br>";
			$rindex = $rindex+1;
			$report[$rindex] = "X-Large Image pixel dimensions: " . $nuwidth . " wide X " . $nuheight . " high<br>";
			$rindex = $rindex+1;
		$outname = $uploadDir.$xsrcname;
			$report[$rindex] = "outname: " . $outname . "<br>";
			$rindex = $rindex+1;
		imagewebp($nuimg, $outname.'.webp', -1);
		exec('convert '.$xsrc.' -quality 35% '.$outname.'.avif');
	// now have a jpg and a webp and an avif of extra-large size image

		//$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuwidth = 992; $nuheight = intval((992/$width)*$height);
		//$nuimg = $img;
			$tagheight = $nuheight;
			if(($nuheight >= 100) && ($nuheight <= 999)) {$tagheight = '0' . $nuheight;}
			if($nuheight <= 99) {$tagheight = '00' . $nuheight;}
		$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuimg = imagescale($img, $nuwidth, $nuheight);
		$lsrcname = $fname.'-l-' . $tagheight;
		$lsrcjpeg = $fname.'-l-' . $tagheight .'.jpg';
		$lsrc = $uploadDir . $lsrcjpeg;
			imagejpeg($nuimg, $lsrc, 100);
			$report[$rindex] = "lsrcname: " . $lsrcfile . "<br>";
			$rindex = $rindex+1;
			$report[$rindex] = "Large Image pixel dimensions: " . $nuwidth . " wide X " . $nuheight . " high<br>";
			$rindex = $rindex+1;
		$outname = $uploadDir.$lsrcname;
			$report[$rindex] = "outname: " . $outname . "<br>";
			$rindex = $rindex+1;
		imagewebp($nuimg, $outname.'.webp', -1);
		exec('convert '.$lsrc.' -quality 35% '.$outname.'.avif');
	// now have a jpg and a webp and an avif of large size image

		//$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuwidth = 768; $nuheight = intval((768/$width)*$height);
		//$nuimg = $img;
			$tagheight = $nuheight;
			if(($nuheight >= 100) && ($nuheight <= 999)) {$tagheight = '0' . $nuheight;}
			if($nuheight <= 99) {$tagheight = '00' . $nuheight;}
		$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuimg = imagescale($img, $nuwidth, $nuheight);
		$msrcname = $fname.'-m-' . $tagheight;
		$msrcjpeg = $fname.'-m-' . $tagheight .'.jpg';
		$msrc = $uploadDir . $msrcjpeg;
			imagejpeg($nuimg, $msrc, 100);
			$report[$rindex] = "msrcfile: " . $msrcfile . "<br>";
			$rindex = $rindex+1;
			$report[$rindex] = "Medium Image pixel dimensions: " . $nuwidth . " wide X " . $nuheight . " high<br>";
			$rindex = $rindex+1;
		$outname = $uploadDir.$msrcname;
			$report[$rindex] = "outname: " . $outname . "<br>";
			$rindex = $rindex+1;
		imagewebp($nuimg, $outname.'.webp', -1);
		exec('convert '.$msrc.' -quality 35% '.$outname.'.avif');
	// now have a jpg and a webp and an avif of medium size image

		//$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuwidth = 576; $nuheight = intval((576/$width)*$height);
		//$nuimg = $img;
			$tagheight = $nuheight;
			if(($nuheight >= 100) && ($nuheight <= 999)) {$tagheight = '0' . $nuheight;}
			if($nuheight <= 99) {$tagheight = '00' . $nuheight;}
		$img = imagecreatefromjpeg($uploadDir.$vpb_file_name);
		$nuimg = imagescale($img, $nuwidth, $nuheight);
		$ssrcname = $fname.'-s-' . $tagheight;
		$ssrcjpeg = $fname.'-s-' . $tagheight .'.jpg';
		$ssrc = $uploadDir . $ssrcjpeg;
			imagejpeg($nuimg, $ssrc, 100);
			$report[$rindex] = "ssrcname: " . $ssrcfile . "<br>";
			$rindex = $rindex+1;
			$report[$rindex] = "Small Image pixel dimensions: " . $nuwidth . " wide X " . $nuheight . " high<br>";
			$rindex = $rindex+1;
		$outname = $uploadDir.$ssrcname;
			$report[$rindex] = "outname: " . $outname . "<br>";
			$rindex = $rindex+1;
		imagewebp($nuimg, $outname.'.webp', -1);
		exec('convert '.$ssrc.' -quality 35% '.$outname.'.avif');
	// now have a jpg and a webp and an avif of small size image

if (isset($img)) { imagedestroy ($img);}
}
} // end image not 1 x 1

	// ---------------------------------
	// if not a LOGO or OG image now have
	// set of GIF and webp of uploaded gifs, or set of jpg and webp and an avif of uploaded png or jpg
	// some info not presently used
	$tmp = file_get_contents($uploadDir.$vpb_file_name);
	$imageHash = hash('sha256', $tmp);
			$report[$rindex] = "Clean Image file hash: " . $imageHash . "<br>";
			$rindex = $rindex+1;
	$imageKey = hash('sha256', $tmp . strval(time()));
			$report[$rindex] = "Image file key: " . $imageKey . "<br>";
			$rindex = $rindex+1;
	$vpb_file_size = strlen($tmp);
			$report[$rindex] = "Clean Image file size: " . $vpb_file_size . "<br>";
			$rindex = $rindex+1;
	// have six categories of images
	// BKGND LOGO OGIMG CARD Comic altImg
	$Comicname = $_SESSION['Comicname'];
	$pageimage = $_SESSION['pageimage']; // this image category
	// destination setup
	$comicsDir = '/var/www/Comics/htdocs/';
	if(!(is_dir($comicsDir.$Comicname))) {
		mkdir($comicsDir.$Comicname, 0775, true);}
	$targetDir = $comicsDir.$Comicname.'/';

	//logo images
if(($_SESSION['pageimage']) == 'LOGO'){ //LOGO images not processed much
		$logoCount = $_SESSION['logoCount'];
		$logoCount = $logoCount + 1;
		$_SESSION['logoCount'] = $logoCount;
		$logoimgfile = $Comicname.$pageimage.$logoCount;
		rename($uploadDir.$vpb_file_name, $targetDir.$logoimgfile.'.'.$vpb_file_type); //leave filetype, might be GIF
			$report[$rindex] = "Target file: " . $targetDir.$logoimgfile.".".$vpb_file_type. "<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$fname.'.webp', $targetDir.$logoimgfile.'.webp');
			$report[$rindex] = "Target file: " . $targetDir.$logoimgfile.".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$fname.'.avif', $targetDir.$logoimgfile.'.avif');
			$report[$rindex] = "Target file: " . $targetDir.$logoimgfile.".avif<br>";
			$rindex = $rindex+1;
			// write the Comic logo file list for use if we get multiple logo files
		$response = file_put_contents($targetDir.$Comicname.'.LL', $logoimgfile, FILE_APPEND);
		if ($response == FALSE) {
			$ErrReport[$Errindex] = "Failed to write logolist file.<br>";$Errindex = $Errindex+1;}
	$comic_name = $Comicname.$pageimage;
}
	// an OG Meta Tag image for social media shares
if(($_SESSION['pageimage']) == 'OGIMG'){ //OG Meta tag Image not processed much
		$ogimgfile = $Comicname.$pageimage;
		//rename($uploadDir.$vpb_file_name, '/var/www/Comics/htdocs/'.$ogimgfile.'.jpg');
		rename($uploadDir.$vpb_file_name, $comicsDir.$ogimgfile.'.jpg');
			$report[$rindex] = "Target file: " .$comicsDir.$ogimgfile . ".jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$fname.'.webp', $comicsDir.$ogimgfile.'.webp');
			$report[$rindex] = "Target file: " .$comicsDir.$ogimgfile . ".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$fname.'.avif', $comicsDir.$ogimgfile.'.avif');
			$report[$rindex] = "Target file: " .$comicsDir.$ogimgfile . ".avif<br>";
			$rindex = $rindex+1;
	$comic_name = $Comicname.$pageimage;
}
	// page background image
if(($_SESSION['pageimage']) == 'BKGND'){ //BKGND or background image
		//$Comicname = $_SESSION['Comicname'];
		$bkgndimagename = $Comicname.$pageimage;
		if($vpb_file_type == 'gif') {
			rename($uploadDir.$fname.'.gif', $targetDir.$bkgndimagename.'.gif');
			$report[$rindex] = "Target file: " . $targetDir.$bkgndimagename . ".gif<br>";
			$rindex = $rindex+1;
			rename($uploadDir.$fname.'.webp', $targetDir.$bkgndimagename.'.webp');
			$report[$rindex] = "Target file: " . $targetDir.$bkgndimagename . ".webp<br>";
			$rindex = $rindex+1;
			rename($uploadDir.$fname.'.avif', $targetDir.$bkgndimagename.'.avif');
			$report[$rindex] = "Target file: " . $targetDir.$bkgndimagename . ".avif<br>";
			$rindex = $rindex+1;
		}
		if($vpb_file_type == 'jpg') {
		rename($uploadDir.$Xsrcname.'.jpg', $targetDir.$bkgndimagename.'-X.jpg');
			$report[$rindex] = "Target file X: " . $targetDir.$bkgndimagename . "-X.jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$xsrcname.'.jpg', $targetDir.$bkgndimagename.'-x.jpg');
			$report[$rindex] = "Target file x: " . $targetDir.$bkgndimagename . "-x.jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$lsrcname.'.jpg', $targetDir.$bkgndimagename.'-l.jpg');
			$report[$rindex] = "Target file L: " . $targetDir.$bkgndimagename . "-l.jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$msrcname.'.jpg', $targetDir.$bkgndimagename.'-m.jpg');
			$report[$rindex] = "Target file M: " . $targetDir.$bkgndimagename . "-m.jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$ssrcname.'.jpg', $targetDir.$bkgndimagename.'-s.jpg');
			$report[$rindex] = "Target file S: " . $targetDir.$bkgndimagename . "-s.jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$Xsrcname.'.webp', $targetDir.$bkgndimagename.'-X.webp');
			$report[$rindex] = "Target file X: " . $targetDir.$bkgndimagename . "-X.webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$xsrcname.'.webp', $targetDir.$bkgndimagename.'-x.webp');
			$report[$rindex] = "Target file x: " . $targetDir.$bkgndimagename . "-x.webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$lsrcname.'.webp', $targetDir.$bkgndimagename.'-l.webp');
			$report[$rindex] = "Target file L: " . $targetDir.$bkgndimagename . "-l.webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$msrcname.'.webp', $targetDir.$bkgndimagename.'-m.webp');
			$report[$rindex] = "Target file M: " . $targetDir.$bkgndimagename . "-m.webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$ssrcname.'.webp', $targetDir.$bkgndimagename.'-s.webp');
			$report[$rindex] = "Target file S: " . $targetDir.$bkgndimagename . "-s.webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$Xsrcname.'.avif', $targetDir.$bkgndimagename.'-X.avif');
			$report[$rindex] = "Target file X: " . $targetDir.$bkgndimagename . "-X.avif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$xsrcname.'.avif', $targetDir.$bkgndimagename.'-x.avif');
			$report[$rindex] = "Target file x: " . $targetDir.$bkgndimagename . "-x.avif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$lsrcname.'.avif', $targetDir.$bkgndimagename.'-l.avif');
			$report[$rindex] = "Target file L: " . $targetDir.$bkgndimagename . "-l.avif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$msrcname.'.avif', $targetDir.$bkgndimagename.'-m.avif');
			$report[$rindex] = "Target file M: " . $targetDir.$bkgndimagename . "-m.avif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$ssrcname.'.avif', $targetDir.$bkgndimagename.'-s.avif');
			$report[$rindex] = "Target file S: " . $targetDir.$bkgndimagename . "-s.avif<br>";
			$rindex = $rindex+1;
		}
	$_SESSION['bkgndImage'] = $bkgndimagename.'.webp'; // default background
	$comic_name = $Comicname.$pageimage;
}
	// a Gallery Card image
if(($_SESSION['pageimage']) == 'CARD'){ //gallery card Image
		$cardImgFname = $Comicname.$pageimage;
		if($vpb_file_type == 'gif') {
		rename($uploadDir.$fname.'.gif', $targetDir.$cardImgFname.'.gif');
			$report[$rindex] = "Target file: " .$targetDir.$cardImgFname . ".gif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$fname.'.webp', $targetDir.$cardImgFname.'.webp');
			$report[$rindex] = "Target file: " .$targetDir.$cardImgFname . ".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$fname.'.avif', $targetDir.$cardImgFname.'.avif');
			$report[$rindex] = "Target file: " .$targetDir.$cardImgFname . ".avif<br>";
			$rindex = $rindex+1;
		}
		if($vpb_file_type == 'jpg') {
		rename($uploadDir.$csrcname.'.jpg', $targetDir.$cardImgFname.'.jpg');
			$report[$rindex] = "Target file: " . $targetDir.$cardImgFname . ".jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$csrcname.'.webp', $targetDir.$cardImgFname.'.webp');
			$report[$rindex] = "Target file: " . $targetDir.$cardImgFname . ".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$csrcname.'.avif', $targetDir.$cardImgFname.'.avif');
			$report[$rindex] = "Target file: " .$targetDir.$cardImgFname . ".avif<br>";
			$rindex = $rindex+1;
		}
	$_SESSION['cardImage'] = $cardImgFname.'.webp'; // default
	$comic_name = $Comicname.$pageimage;
}
	// Comic image collection
if(($_SESSION['pageimage']) == 'Comic'){ //images for the Comic pages
		if($width == 1 && $height == 1) { // special 1x1 for caption only panel
			rename($uploadDir.$vpb_file_name, $comicsDir.$Comicname.'/'.$vpb_file_name);
			$report[$rindex] = "Target file: " . $comicsDir.$Comicname.'/'.$vpb_file_name;
			$rindex = $rindex+1;
		} else {
		if($vpb_file_type == 'gif') {
			if(($height >= 100) && ($height <= 999)) {$height = '0' . $height;}
			if($height <= 99) {$height = '00' . $height;}
			if(($width >= 100) && ($width <= 999)) {$width = '0' . $width;}
			if($width <= 99) {$width = '00' . $width;}

			rename($uploadDir.$fname.'.gif', $comicsDir.$Comicname.'/'.$fname.'-w'.$width.'-h'.$height.'.gif');
			$report[$rindex] = "Target file: " . $comicsDir.$Comicname.'/'.$fname.'-w'.$width.'-h'.$height.".gif<br>";
			$rindex = $rindex+1;
			rename($uploadDir.$fname.'.webp', $comicsDir.$Comicname.'/'.$fname.'-w'.$width.'-h'.$height.'.webp');
			$report[$rindex] = "Target file: " . $comicsDir.$Comicname.'/'.$fname.'-w'.$width.'-h'.$height.".webp<br>";
			$rindex = $rindex+1;
			rename($uploadDir.$fname.'.avif', $comicsDir.$Comicname.'/'.$fname.'-w'.$width.'-h'.$height.'.avif');
			$report[$rindex] = "Target file: " . $comicsDir.$Comicname.'/'.$fname.'-w'.$width.'-h'.$height.".avif<br>";
			$rindex = $rindex+1;
		}
		if($vpb_file_type == 'jpg') {
		rename($uploadDir.$Xsrcname.'.jpg', $comicsDir.$Comicname.'/'.$Xsrcname.'.jpg');
			$report[$rindex] = "Target file X: " . $comicsDir.$Comicname.'/'.$Xsrcname . ".jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$xsrcname.'.jpg', $comicsDir.$Comicname.'/'.$xsrcname.'.jpg');
			$report[$rindex] = "Target file x: " . $comicsDir.$Comicname.'/'.$xsrcname . ".jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$lsrcname.'.jpg', $comicsDir.$Comicname.'/'.$lsrcname.'.jpg');
			$report[$rindex] = "Target file L: " . $comicsDir.$Comicname.'/'.$lsrcname . ".jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$msrcname.'.jpg', $comicsDir.$Comicname.'/'.$msrcname.'.jpg');
			$report[$rindex] = "Target file M: " . $comicsDir.$Comicname.'/'.$msrcname . ".jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$ssrcname.'.jpg', $comicsDir.$Comicname.'/'.$ssrcname.'.jpg');
			$report[$rindex] = "Target file S: " . $comicsDir.$Comicname.'/'.$ssrcname . ".jpg<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$Xsrcname.'.webp', $comicsDir.$Comicname.'/'.$Xsrcname.'.webp');
			$report[$rindex] = "Target file X: " . $comicsDir.$Comicname.'/'.$Xsrcname . ".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$xsrcname.'.webp', $comicsDir.$Comicname.'/'.$xsrcname.'.webp');
			$report[$rindex] = "Target file x: " . $comicsDir.$Comicname.'/'.$xsrcname . ".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$lsrcname.'.webp', $comicsDir.$Comicname.'/'.$lsrcname.'.webp');
			$report[$rindex] = "Target file L: " . $comicsDir.$Comicname.'/'.$lsrcname . ".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$msrcname.'.webp', $comicsDir.$Comicname.'/'.$msrcname.'.webp');
			$report[$rindex] = "Target file M: " . $comicsDir.$Comicname.'/'.$msrcname . ".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$ssrcname.'.webp', $comicsDir.$Comicname.'/'.$ssrcname.'.webp');
			$report[$rindex] = "Target file S: " . $comicsDir.$Comicname.'/'.$ssrcname . ".webp<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$Xsrcname.'.avif', $comicsDir.$Comicname.'/'.$Xsrcname.'.avif');
			$report[$rindex] = "Target file X: " . $comicsDir.$Comicname.'/'.$Xsrcname . ".avif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$xsrcname.'.avif', $comicsDir.$Comicname.'/'.$xsrcname.'.avif');
			$report[$rindex] = "Target file x: " . $comicsDir.$Comicname.'/'.$xsrcname . ".avif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$lsrcname.'.avif', $comicsDir.$Comicname.'/'.$lsrcname.'.avif');
			$report[$rindex] = "Target file L: " . $comicsDir.$Comicname.'/'.$lsrcname . ".avif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$msrcname.'.avif', $comicsDir.$Comicname.'/'.$msrcname.'.avif');
			$report[$rindex] = "Target file M: " . $comicsDir.$Comicname.'/'.$msrcname . ".avif<br>";
			$rindex = $rindex+1;
		rename($uploadDir.$ssrcname.'.avif', $comicsDir.$Comicname.'/'.$ssrcname.'.avif');
			$report[$rindex] = "Target file S: " . $comicsDir.$Comicname.'/'.$ssrcname . ".avif<br>";
			$rindex = $rindex+1;
		}
		}
	$_SESSION['comicImage'] = $fname.'.webp'; // default
	$comic_name = $Comicname;
}
	// ALTIMG image collection
if(($_SESSION['pageimage']) == 'altImgs'){ //alt images for the Comic
	// destination setup
	if(!(is_dir($comicsDir.$Comicname.'/'.$pageimage))) {
		mkdir($comicsDir.$Comicname.'/'.$pageimage, 0775, true);}
	$targetDir = $comicsDir.$Comicname.'/'.$pageimage.'/';

	if($width == 1 && $height == 1) { // special 1x1 for caption only panel
		rename($uploadDir.$vpb_file_name, $targetDir.$vpb_file_name);
		$report[$rindex] = "Target file: " . $targetDir.$vpb_file_name;
		$rindex = $rindex+1;
	} else {
	if($vpb_file_type == 'gif') {
		rename($uploadDir.$fname.'.gif', $targetDir.$fname.'-'.$width.'-'.$height.'.gif');
		$report[$rindex] = "Target file: " . $targetDir.$fname.'-'.$width.'-'.$height.".gif<br>";
		$rindex = $rindex+1;
		rename($uploadDir.$fname.'.webp', $targetDir.$fname.'-'.$width.'-'.$height.'.webp');
		$report[$rindex] = "Target file: " . $targetDir.$fname.'-'.$width.'-'.$height.".webp<br>";
		$rindex = $rindex+1;
		rename($uploadDir.$fname.'.avif', $targetDir.$fname.'-'.$width.'-'.$height.'.avif');
		$report[$rindex] = "Target file: " . $targetDir.$fname.'-'.$width.'-'.$height.".avif<br>";
		$rindex = $rindex+1;
	}
	if($vpb_file_type == 'jpg') {
	rename($uploadDir.$Xsrcname.'.jpg', $targetDir.$Xsrcname.'.jpg');
		$report[$rindex] = "Target file X: " . $targetDir.$Xsrcname . ".jpg<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$xsrcname.'.jpg', $targetDir.$xsrcname.'.jpg');
		$report[$rindex] = "Target file : " . $targetDir.$xsrcname . ".jpg<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$lsrcname.'.jpg', $targetDir.$lsrcname.'.jpg');
		$report[$rindex] = "Target file L: " . $targetDir.$lsrcname . ".jpg<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$msrcname.'.jpg', $targetDir.$msrcname.'.jpg');
		$report[$rindex] = "Target file M: " . $targetDir.$msrcname . ".jpg<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$ssrcname.'.jpg', $targetDir.$ssrcname.'.jpg');
		$report[$rindex] = "Target file S: " . $targetDir.$ssrcname . ".jpg<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$Xsrcname.'.webp', $targetDir.$Xsrcname.'.webp');
		$report[$rindex] = "Target file X: " . $targetDir.$Xsrcname . ".webp<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$xsrcname.'.webp', $targetDir.$xsrcname.'.webp');
		$report[$rindex] = "Target file x: " . $targetDir.$xsrcname . ".webp<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$lsrcname.'.webp', $targetDir.$lsrcname.'.webp');
		$report[$rindex] = "Target file L: " . $targetDir.$lsrcname . ".webp<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$msrcname.'.webp', $targetDir.$msrcname.'.webp');
		$report[$rindex] = "Target file M: " . $targetDir.$msrcname . ".webp<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$ssrcname.'.webp', $targetDir.$ssrcname.'.webp');
		$report[$rindex] = "Target file S: " . $targetDir.$ssrcname . ".webp<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$Xsrcname.'.avif', $targetDir.$Xsrcname.'.avif');
		$report[$rindex] = "Target file X: " . $targetDir.$Xsrcname . ".avif<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$xsrcname.'.avif', $targetDir.$xsrcname.'.avif');
		$report[$rindex] = "Target file x: " . $targetDir.$xsrcname . ".avif<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$lsrcname.'.avif', $targetDir.$lsrcname.'.avif');
		$report[$rindex] = "Target file L: " . $targetDir.$lsrcname . ".avif<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$msrcname.'.avif', $targetDir.$msrcname.'.avif');
		$report[$rindex] = "Target file M: " . $targetDir.$msrcname . ".avif<br>";
		$rindex = $rindex+1;
	rename($uploadDir.$ssrcname.'.avif', $targetDir.$ssrcname.'.avif');
		$report[$rindex] = "Target file S: " . $targetDir.$ssrcname . ".avif<br>";
		$rindex = $rindex+1;
	}
	}
	$comic_name = $Comicname.$pageimage;
}

	  $objects = scandir($uploadDir);
	  foreach ($objects as $object) {
		if ($object != "." && $object != "..") {
		  unlink($uploadDir."/".$object);}
	  }
	  //reset($objects);
	$_SESSION['report'] = $report;
	$_SESSION['ErrReport'] = $ErrReport;
	//$_SESSION['Comicname'] = $Comicname;

	if(empty($ErrReport)) { 
    // set up the Comic image data array
	$ComicData = array();
    $ComicData['comic_name']   = $comic_name;
    $ComicData['oauth_id'] = $_SESSION['oauth_id'];
    $ComicData['image_hash'] = $imageHash;
    $ComicData['image_key']	 = $imageKey;
    $ComicData['filename']   = $vpb_file_name;
    $ComicData['filetype']   = $vpb_file_type;
    $ComicData['width']   	= $width;
    $ComicData['height']   = $height;
    // Store image data in the session
    $_SESSION['ComicData'] = $ComicData;
	//if(!(($_SESSION['pageimage']) == 'altimgs')) { //alt images don't go in database
    // Initialize Image Comic class
    $comic = new ComicImages();
	// Insert image data to the database
	$Data = $comic->insertComicImages($ComicData);
	//if(file_exists($uploadDir.$vpb_file_name)) {
	//unlink($uploadDir.$vpb_file_name);}
	//rmdircontent($uploadDir);
	}
	if(empty($ErrReport)) { 
		//Display the file id
		echo $vpb_file_id;
	} else {
		//Display general system error
		echo 'general_system_error';
	}

}; // got an upload
};
}; // end POST
return;
?>


An examination of the code above shows that it serves to upload a number of different sorts of images, so besides the background image and card image and the comic page images we have just seen, imgUploader is also invoked to upload images for the alternate image panels including animations, as well as for example an optional OG Image. We can also see that imgUploader does a good bit of image format conversion and scaling of our uploaded images and these sets of modified images will later be used to create “source sets” (srcset) for our images.


The Builder uses the notion of a pilot image or base image for each panel, and this pilot image name is stored in our comic images database, ComicImages.class.php. The database contains the names of each image as a base format and size source, either JPG or GIF, and the other sizes and file format images used, for example the AVIF and WEBP formats, are not uploaded, but are instead generated and stored as files by imgUploader as shown in the code

Besides the comic page images, each such page may have a caption panel and an alternate image content panel which also has an optional caption panel. And all of these images should each have an alternate (alt attribute) text for accessibility.


The Builder also supports MP3 audio files as alternate content for the panel images, and the audio files should also have alt text and preferably an audio transcript as well. So in addition to the imgUploader program we also have a txtUploader.php file for such text content as say the captions, alt image captions, the alt text and so forth and it is invoked by getComicCaptions.php for example to select and upload the captions for each comic panel and by getAltText.php for the panel images alt text attribute. In addition we have the getAltImgMP3.php program which invokes mp3Uploader.php to select and upload our sound file alternate content.


Another function of the index.php program is to invoke the comicFonts.php program to select the various font and caption features for our comic.

Some “Soft” Considerations

The image and text files for our comics are uploaded and collated or sorted alphanumerically. So image panel 01.jpg will nominally appear before image panel 02.jpg and so forth in the page order. Now in the captions folder for example the file 01.txt will appear before 02.txt, but moreover image panel 01.jpg will have caption file 01.txt associated with it. In similar fashion our alternate image panels are related in this same basic naming scheme. This means that we want to name our image panel files in some way that is collatable or that may be ordered by this alphanumeric scheme, and of course we want to parlay that scheme into our other content arrangement and naming.


Authors have different overall processes or methods they use to generate content like say these comics. For myself here using the builder application, the technique, probably the first consideration is the storyline. I get a general idea of the story I want to tell and I then begin to gather or create the images I feel might serve well to tell the story. As I draw or even just collect my page images I give a lot of consideration to the image file names. First is our sort tag, I usually just use three digits as the first filename characters. So long as the tags are in order they don’t have to be sequential, hence say 05.jpg would still come after 02.jpg and before 09.jpg. This allows me to kind of group image sections and the gaps make for easy rearrangement if needed. Another consideration is to try to use descriptive or meaningful image names. For example “01.jpg” doesn’t have as much semantic meaning as say “01-Huge_red_dragon_on_a_rock.jpg” and the latter certainly serves as a superior alt text for the image.


After I have gathered my comic panel images and named them appropriately, I use a Windows BATCH file to generate a set of “dummy” files for my panel captions. The BAT file simply creates a file named to correlate with the panel images and which contains the filename without its extension as content. Then I edit these dummy caption files to reflect my desired caption for that image. You can see that this BAT file by itself may be sufficient to generate our alt text content for each image and that it can also be used to create the alternate image caption files. This is the makeTxtFiles.bat file I use to accomplish this file creation.


@echo off
::for %%f in (*.*) do if /i "%%~xf" neq ".txt" echo whatever > %%~nf.txt
for %%f in (*.*) do if /i "%%~xf" == ".jpg" echo %%~nf > "%%~nf.txt"
for %%f in (*.*) do if /i "%%~xf" == ".gif" echo %%~nf > "%%~nf.txt"

Builder’s Uploads Conclusion

This places us at the point where we have selected and uploaded and then collated and arranged the content we want to use in our comic and so concludes the first stage of the Builder process. Next we can set in motion the generator stage of our Builder which will actually construct the comic document from our content. That process is described next in Storybook v2 Comic Book Builder: Part 2.


As always, Criticisms, Comments, and Suggestions are welcome. Thanks for reading.