Zap stands for 'Zapped Artificial Picture', a collage made from Internet images. A Zap translates a set of words or a text into an image which reflects the visual representation of the words on the Internet.
This web application, Zap Machine, needs a number of words as input. It then takes the most relevant image that search engine Yahoo comes up with. After having collected all images the Zap Machine creates a given number of different collages from those images.
## installation
First you need to run a server with PHP. Please do not use a Windows machine for this purpose, while Windows suck. ApFab recommends Linux or Mac;)
ApFab Zap Machine needs some PHP extensions:
- JSON (default installed)
- CUrl (default installed)
- Imagick (Image Magick image processing Class)
You need to acquire the following API key and put it into `conf/conf.php`:
1.**Brave Search API Key** - https://api.search.brave.com/
See `conf/conf.php` for the exact constant names to use.
This document explains the internal architecture of ZapMachine and provides step‑by‑step instructions for extending it with new handlers, templates, and interactive elements.
All HTTP requests enter through `www/index.php`. The front controller reads the `mode` query parameter and dispatches to a class whose name is formed as `ZAP` + the ucfirst'd mode value.
`ZAPController` (in `lib/ZAPController.class.php`) constructs the class name from a prefix (`ZAP`) and the mode, verifies it exists via the autoloader, instantiates it, and calls `getContent()`:
`conf/init.php` registers an `spl_autoload_register` callback that searches every directory on the include path for both `{ClassName}.class.php` and `{ClassName}.interface.php`:
This means any `.class.php` or `.interface.php` file placed in one of those directories is automatically found — no manual `require_once` calls needed for library classes.
---
### Config Singleton
Configuration values that must be accessible from any class are managed through `Config` (`conf/Config.class.php`), a classic singleton:
```php
$config = Config::getInstance();
$config->register('engines', $search_engines); // store
$engines = $config->retrieve('engines'); // retrieve — returns false if not found
```
The singleton is wired in `www/index.php` immediately after `init.php` is loaded.
---
### Session Management
The `Session` class (`utils/Session.class.php`) wraps PHP's native sessions:
```php
Session::start('ZAP'); // Begin session (sessions named 'ZAP')
Available engines are registered as an array of names in `conf/conf.php` and wired through the Config singleton at runtime. `ImgSearch` randomly selects an engine from the registered list each time it runs:
```php
$config = Config::getInstance();
$engines = $config->retrieve('engines');
$this->engine = $engines[array_rand($engines)];
```
**Current engine:** `Brave` (`resource/Brave.class.php`) — uses the Brave Search API.
2. Add `'yourengine'` to the `$search_engines` array in `conf/conf.php`.
---
### Image Processing Pipeline
The image pipeline runs entirely server-side through three classes in `lib/`:
1.**`ImgSearch`** (`lib/ImgSearch.class.php`) — searches for an image URL via a registered engine, downloads the file, validates its MIME type (`image/jpeg`, `image/gif`, `image/png`), and saves it to the session folder.
2.**`ImgProcess`** (`lib/ImgProcess.class.php`) — constructs collages using ImageMagick. Each collage starts with `createBasicCanvas()` (zoom-crops the first source image) then layers additional images via `addToCanvas()` (random transparency + composite).
3.**`ImgIOTools`** (`lib/ImgIOTools.class.php`) — handles output: `onScreen()` streams an Imagick object as an HTTP image response; `urlOnScreen()` loads a file path first; `saveImg()` writes to disk.
CLI scripts (in `cli_scripts/`) bootstrap the application differently from web requests. Because `init.php` skips `Session::start()` when `php_sapi_name() === 'cli'`, CLI scripts must handle sessions manually if needed.
**Example — `cli_scripts/test.php`:**
```php
#!/usr/bin/env php
<?php
// Make sure relative paths in init.php resolve against this script's directory
chdir(__DIR__);
// Bootstrap the application (autoloader, constants, Config, but NOT session)
require_once __DIR__ . '/../conf/init.php';
// If you need sessions in CLI, call Session::start() manually:
// Session::start('ZAP');
// Now all classes, the autoloader, and Config singleton are available
$config = Config::getInstance();
$config->register('engines', $search_engines);
```
Key differences from the web entry point:
-`chdir(__DIR__)` is **required** before `require_once 'init.php'` because `init.php` uses `realpath('.')` to define `ZAP_APP_BASE_DIR`.
-`Session::start()` is **not called** — the `init.php` condition `php_sapi_name() !== 'cli'` guards against it.
- You must explicitly register engines with `Config::getInstance()->register('engines', …)` because `www/index.php` normally does this.
---
### Logging
The `Log` class (`utils/Log.class.php`) writes timestamped messages to a session folder. It is used by `ImgSearch` (controlled by the `ZAP_LOG` constant in `conf/conf.php`). Each log call appends to both `log.txt` (plain text) and `log.xml` (XML-friendly) in the session's output directory.
```php
if (ZAP_LOG == true) {
$log = new Log($this->folder);
$log->addToLog('ok', 'Search: ', "downloaded to $imageName");
$log->addToLog('error', 'Search: ', 'bad search data received');
}
```
---
## Template Rendering Pipeline
The template system is a minimal PHP-native pattern built around `ob_start()` / `include` / `extract()` / `ob_get_clean()`. There is no template engine — templates are plain PHP files that receive variables via the symbol table.
### How Templates Work
Every `ZAP{Mode}` handler follows the same rendering pattern:
```php
class ZAPHome {
private $template = 'index'; // corresponds to tpl/index.tpl
private $tpl = '';
public function __construct() {
$this->ver = ZAP_VERSION;
extract(get_object_vars($this)); // ← makes all private properties available as $variables
ob_start(); // ← start output buffer
include getZAPTemplate($this->template); // ← include the .tpl file
1. Properties are set in the constructor (often from `$_POST`, `$_SESSION`, or constants like `ZAP_VERSION`).
2.`extract(get_object_vars($this))` promotes every private property to a local variable in the current symbol table. A property `$this->template = 'index'` becomes `$template = 'index'`.
3.`ob_start()` begins capturing all output.
4.`include getZAPTemplate($this->template)` includes the file `tpl/{name}.tpl`. The included file inherits the current symbol table, so it can use `$ver`, `$folder`, `$words`, etc. directly.
5.`ob_get_clean()` retrieves the captured HTML and closes the buffer.
6.`getContent()` returns the accumulated output string, which `ZAPController::fetch()` returns to `index.php` for `print()`.
All templates live in the `tpl/` directory and have the `.tpl` extension. The helper resolves the absolute path so the `include` call works from any working directory.
### Adding a New Template
To add a new page/view:
1.**Create the `ZAP{Mode}` handler** in `lib/`. The class must be named `ZAP{Mode}` (case-sensitive, PascalCase after the prefix) and must implement a `getContent()` method that returns a string:
3.**Use the route:** Navigate to `index.php?mode=example`. The autoloader finds `ZAPExample` in `lib/`, the front controller instantiates it, and `getZAPTemplate()` loads `tpl/example.tpl`.
No registration step is required — the front controller's naming convention (`ZAP` + ucfirst(mode)) and the autoloader handle discovery automatically.
---
## Adding Interactive Elements
This section documents the two interactive-element patterns used in the application. Each pattern pairs a server-side template with a client-side JavaScript file.
### Pattern 1: Form with Client-Side Validation (`index.tpl` + `js/main.js`)
Used by `ZAPHome` to collect word inputs, validate them in the browser, and POST to the `display` mode.
**Server side (`ZAPHome` in `lib/ZAPHome.class.php`):**
The handler passes two template variables into `tpl/index.tpl`:
-`$ver` — the ZAP version string.
-`$status` — an optional status message from the query string (`?stat=...`).
The template renders a `<form id="form1">` that POSTs to `index.php?mode=display`. Each input is named `word01`, `word02`, … `word{MAX_WORDS}` (zeropadded to two digits). A `<select name="collages">` controls the number of collages, and width/height text inputs control dimensions.
**Client side (`js/main.js`):**
The `form1Submit()` function performs validation before the form is submitted:
```javascript
function form1Submit() {
// Minimum of two words required
var word1 = document.getElementById('word01').value;
var word2 = document.getElementById('word02').value;
var width = document.getElementById('width').value;
var height = document.getElementById('height').value;
if (word1.length <= 0) {
document.getElementById('status').innerHTML = 'Minimum of two words required';
return;
}
if (word2.length <= 0) {
document.getElementById('status').innerHTML = 'Minimum of two words required';
Used by the `display` and `zap` modes to show collage progress. The server streams individual image responses, and the client alternates between two `<img>` tags to give the appearance of continuous updating.
**Server side:**
`ZAPDisplay` (in `lib/ZAPDisplay.class.php`) sets session variables from `$_POST` and renders `tpl/display.tpl`. The template declares `onload="go(this,['img1','img2'],5000);"` on the `<body>` tag, which kicks off the polling loop.
`ZAPZap` (in `lib/ZAPZap.class.php`) handles each individual poll request. On each invocation it either:
- Downloads the next source image (if `$_SESSION['words']` is not empty) and streams it via `ImgIOTools::onScreen()`.
- Starts the next collage (if `$_SESSION['words']` is empty but `$_SESSION['collages'] > 0`).
- Adds the next source image to the current collage via `ImgProcess::addToCanvas()`.
- Signals completion by destroying the session and returning an error string (which triggers the `onerror` handler in JavaScript).
**Client side (`js/display.js`):**
```javascript
var go = function(b, img_id, interval) {
var i = 0;
var img0 = document.getElementById(img_id[0]);
var img1 = document.getElementById(img_id[1]);
var img = [img0, img1];
var activeImage = 1; // index of visible image
var url = 'index.php?mode=zap';
if (isNaN(interval)) {
interval = 5000;
}
// Trigger the next image fetch after the current one has loaded.
img[0].onload = img[1].onload = function() {
var inactiveImage = activeImage === 0 ? 1 : 0;
img[activeImage].style.display = 'none';
img[inactiveImage].style.display = '';
activeImage = inactiveImage;
setTimeout(function() {
img[inactiveImage].src = url + '&i=' + (i++);
}, interval);
};
// When the backend signals completion it returns a small HTML page.
// Show the ready link and stop fetching new images.
1.`body onload="go(this,['img1','img2'],5000)"` starts the loop with a 5s interval between successive fetches.
2. Two `<img>` tags (`#img1` and `#img2`) are used — one is visible, the other hidden.
3. When the visible image finishes loading (`img.onload`), the pair swaps: the loaded image is hidden, and the previously hidden one is shown. After a 5s delay (`setTimeout`), the newly hidden image's `src` is set to `index.php?mode=zap&i={counter}`.
4. Each request to `mode=zap` returns raw image data (a `Content-Type: image/jpeg` response). The browser renders it into the `<img>` tag, triggering `onload` again and continuing the cycle.
5. When `ZAPZap` finishes all collages, it destroys the session and returns an HTML error string instead of image data. The `<img>` tag fails to load an image, triggering `onerror`, which replaces the image area with a "Ready" link.
**To reproduce this pattern in a new handler:**
1. Create a template with two `<img>` elements and `<body onload="go(this,['id1','id2'],intervalMs)">`.
2. Include `js/display.js` (or copy the pattern into your own JS file).
3. Create a `ZAP{Mode}` handler that returns raw image data (set `Content-Type` headers and use `echo` with `readfile()` or Imagick's `getImageBlob()`).
4. Signal completion by returning a non-image response that will trigger `onerror` on the `<img>` tag.
---
## Summary: Creating a New Feature End-to-End
To add a completely new feature to ZapMachine, follow these steps:
1.**Add configuration** in `conf/conf.php` (constants, engine names) if needed.
2.**Create the handler**`lib/ZAP{Mode}.class.php` with `getContent()` returning a string.
3.**Create the template**`tpl/{mode}.tpl` using the `ob_start`/`include`/`extract`/`ob_get_clean` pattern.
4.**Choose an interactive pattern:**
- For form input with validation: add a form in the template, write a JS validation function, and include the script.
- For polling/image updates: use the two-`<img>` swap with `onload`/`onerror`.
5.**Create a JS file** in `www/js/` and reference it from the template's `<head>`.
6.**Test the route** at `index.php?mode={ModeName}`.
7.**Test from CLI** using the bootstrap pattern in `cli_scripts/test.php` (remember `chdir(__DIR__)` + manual `Session::start()` if needed).