From fb0321bfd5550724150a43a33d67484422eecbf5 Mon Sep 17 00:00:00 2001 From: Fabian de Boer Date: Fri, 17 Jul 2026 01:21:19 +0200 Subject: [PATCH 1/3] README: replace outdated how-it-works with ZAP Framework Guide (todo #9) Replaces the legacy 'how it works' section (which referenced non-existent index.html, zap.php, display.inc.php, init.php) with a comprehensive 'ZAP Framework Guide' documenting the full application architecture: - Front-controller routing (?mode=X -> ZAPController -> ZAP{Mode}) - PSR-0 autoloader in conf/init.php - Config singleton pattern - Session management (verbatim keys) - Plugin-based search engines (Search interface, Brave) - Image processing pipeline (ImgSearch, ImgProcess, ImgIOTools, ImgTools) - CLI bootstrap pattern (chdir + require_once, session_start skipped in CLI) - Logging via Log class Template rendering pipeline documented with: - ob_start/include/extract/ob_get_clean pattern - getZAPTemplate() helper - Step-by-step guide for adding new templates Interactive elements documented: - Pattern 1: Form with client-side validation (index.tpl + js/main.js) - Pattern 2: Polling image display (display.tpl + js/display.js, onload-driven two-img swap with 5s cadence) Includes end-to-end summary for creating new features. --- README.md | 491 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 475 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index b539f72..11d43f8 100644 --- a/README.md +++ b/README.md @@ -28,26 +28,485 @@ You need to acquire the following API key and put it into `conf/conf.php`: See `conf/conf.php` for the exact constant names to use. -Adjust permission (chmod 777) for Sessions folder, run index.html, fill in the form and voila. +Adjust permission (chmod 777) for Sessions folder, run index.php in the www/ directory, fill in the form and voila. +# ZAP Framework Guide -# how it works +This document explains the internal architecture of ZapMachine and provides step‑by‑step instructions for extending it with new handlers, templates, and interactive elements. -- index.html contains user interface -- user input put into Session variables in init.php -- display.inc.php dynamically displays two img tags that are loaded in turn with image data from zap.php using display.js -- zap.php downloads images one by one using imgSearch Class -- zap.php processes collages one by one using imgProcess Class -- All downloaded images, collage images and logfiles(feature is far from ready yet) are put in a folder named 'Session/zap'.date() -- the machine is ready when an error is loaded into img document class. (a weak point in our construction) +## Architecture Overview +### Front-Controller Routing (`?mode=X` → `ZAPController` → `ZAP{Mode}`) +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. -# yet to come -- Logfile also generated in imgProcess Class (0.9.2) -- Number and camera filename (virgin images) input (0.9.3) -- Proper stop meganism (0.9.1) -- better error handling: dead links give dynamicly generated image thats allso used in collage (0.9.3) -- visualisation of logfile, so viewers can see what is happening (0.9.1) -- replacing all 'random' image processing parameters (0.7) +```php +// www/index.php (simplified) +$mode = isset($_GET['mode']) ? $_GET['mode'] : 'home'; +require_once '..' . DIRECTORY_SEPARATOR . 'conf' . DIRECTORY_SEPARATOR . 'init.php'; +$config = Config::getInstance(); +$config->register('engines', $search_engines); +$frontcontroller = new ZAPController($mode); +$output = $frontcontroller->fetch(); +print($output); +``` + +`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()`: + +```php +class ZAPController { + private $prefix = 'ZAP'; + + public function __construct($mode = 'home') { + $mode = trim(strip_tags($mode)); + $this->mode = ucfirst($mode); + $clazz = $this->prefix . $this->mode; + if (!class_exists($clazz)) { + die('Wrong parameter'); + } + $this->action = new $clazz(); + } + + public function fetch() { + return $this->action->getContent(); + } +} +``` + +**Registered modes and their handlers:** + +| `?mode=` | Class | Template | Purpose | +|----------|-------|----------|---------| +| `home` (default) | `ZAPHome` | `tpl/index.tpl` | Word-input form | +| `display` | `ZAPDisplay` | `tpl/display.tpl` | Collage-generation progress screen | +| `zap` | `ZAPZap` | *(raw image output)* | Downloads images & processes collages, returns image blobs | + +--- + +### Autoloader + +`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`: + +```php +spl_autoload_register(function ($class_name) { + $include_path_tokens = explode(PATH_SEPARATOR, get_include_path()); + foreach ($include_path_tokens as $prefix) { + $path[0] = $prefix . DIRECTORY_SEPARATOR . $class_name . '.interface.php'; + $path[1] = $prefix . DIRECTORY_SEPARATOR . $class_name . '.class.php'; + foreach ($path as $thisPath) { + if (file_exists($thisPath)) { + require_once $thisPath; + return; + } + } + } +}); +``` + +The include path is built from these directories (relative to the project root): + +| Constant | Directory | +|----------|-----------| +| `ZAP_RESOURCE_DIR` | `resource/` — search-engine implementations | +| `ZAP_UTILS_DIR` | `utils/` — utility classes (Session, ZAPCurl, Log) | +| `ZAP_SESSIONS_DIR` | `sessions/` | +| `ZAP_LIB_DIR` | `lib/` — core handlers and image processing | +| `ZAP_TPL_DIR` | `tpl/` — templates | +| `ZAP_CONF_DIR` | `conf/` — configuration | + +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') +Session::reset(); // Clear $_SESSION array +Session::deleteCookie(); // Remove client-side session cookie +Session::destroy(); // Reset + delete cookie + session_destroy() +``` + +Session data is stored verbatim in `$_SESSION` and uses these keys: + +| Key | Type | Set by | Purpose | +|-----|------|--------|---------| +| `words` | `string[]` | `ZAPDisplay` | Keyword list for image search | +| `folder` | `string` | `ZAPDisplay` | Absolute path to session folder | +| `collages` | `int` | `ZAPDisplay` | Number of collages to generate | +| `width` | `int` | `ZAPDisplay` | Collage width in pixels | +| `height` | `int` | `ZAPDisplay` | Collage height in pixels | +| `images` | `string[]` | `ZAPZap` | Queue of source image paths for current collage | +| `imgOriginal` | `string[]` | `ZAPZap` | Immutable copy of the original image list | +| `collageNr` | `int` | `ZAPZap` | Current collage sequence number | + +**CLI note:** Session is only started automatically in web mode. See [CLI Bootstrap](#cli-bootstrap) below. + +--- + +### Plugin-Based Search Engines + +Search engines implement the `Search` interface (`resource/Search.interface.php`): + +```php +interface Search { + public function setQuery($word); + public function setParam($key, $value); + public function getData(); +} +``` + +Each engine class lives in `resource/` and returns data in a universal array format: + +```php +$data['result'] = true; // Boolean: success / failure +$data['engine'] = __CLASS__; // Engine name, e.g. 'Brave' +$data['results'][0]['clickurl'] = '...'; // Image URL to download +$data['results'][0]['referer'] = '...'; // Source page URL +$data['results'][0]['thumb'] = '...'; // Thumbnail URL +``` + +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. + +To add a new engine: +1. Create `resource/YourEngine.class.php` implementing `Search`. +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. + +4. **`ImgTools`** (`lib/ImgTools.class.php`) — utility functions: `zoomCrop()`, `transparantRandom()`, `randomRotate()`, `resize()`, `recolor()`, `convertGifsToPng()`. + +--- + +### CLI Bootstrap + +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 +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 + $this->tpl .= ob_get_clean(); // ← capture buffer content, clean buffer + } + + public function getContent() { + return $this->tpl; + } +} +``` + +**Step by step:** + +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()`. + +### The `getZAPTemplate()` Helper + +```php +function getZAPTemplate($tplname) { + $tplfile = $tplname . '.tpl'; + $tplfilepath = ZAP_APP_BASE_DIR . DIRECTORY_SEPARATOR + . ZAP_TPL_DIR . DIRECTORY_SEPARATOR . $tplfile; + if (file_exists($tplfilepath)) { + return $tplfilepath; + } else { + return ERROR_PREFIX . 'No template'; + } +} +``` + +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: + + ```php + pageTitle = 'My Example Page'; + extract(get_object_vars($this)); + + ob_start(); + include getZAPTemplate($this->template); + $this->tpl .= ob_get_clean(); + } + + public function getContent() { + return $this->tpl; + } + } + ``` + +2. **Create the template** at `tpl/example.tpl`: + + ```php + + + Example — <?php echo $pageTitle; ?> + +

+

ZAP version

+ + + ``` + +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 `
` that POSTs to `index.php?mode=display`. Each input is named `word01`, `word02`, … `word{MAX_WORDS}` (zeropadded to two digits). A `` 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'; - return; - } - if (width.length <= 0) { - document.getElementById('status').innerHTML = 'Value width required'; - return; - } - if (height.length <= 0) { - document.getElementById('status').innerHTML = 'Value height required'; - return; - } - if (!IsNumeric(width)) { - document.getElementById('status').innerHTML = 'Value width needs numeric input'; - return; - } - if (!IsNumeric(height)) { - document.getElementById('status').innerHTML = 'Value height needs numeric input'; - return; - } - - // Hide form, show loading indicator, then submit - document.getElementById('status').innerHTML = ' '; - document.getElementById('loading').style.visibility = 'visible'; - document.getElementById('form1').style.visibility = 'hidden'; - document.getElementById('form1').submit(); -} -``` - -**To reproduce this pattern in a new handler:** - -1. Create a form in your `.tpl` that POSTs to `index.php?mode=yourmode`. -2. Create a `ZAPYourMode` handler in `lib/` that reads `$_POST` values in its constructor and stores them in `$_SESSION`. -3. Write a `js/your.js` file with a validation function, and include `` in the template's ``. -4. Use a `
` element in the template to display validation errors. - ---- - -### Pattern 2: Polling Image Display (`display.tpl` + `js/display.js`) - -Used by the `display` and `zap` modes to show collage progress. The server streams individual image responses, and the client alternates between two `` 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 `` 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. - img[0].onerror = img[1].onerror = function() { - document.getElementById('ready').innerHTML = 'Ready'; - }; - - // Kick off the first request. - img[activeImage].src = url + '&i=' + (i++); -}; -``` - -**How the polling cadence works:** - -1. `body onload="go(this,['img1','img2'],5000)"` starts the loop with a 5 s interval between successive fetches. -2. Two `` 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 5 s 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 `` 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 `` 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 `` elements and ``. -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 `` 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-`` swap with `onload`/`onerror`. -5. **Create a JS file** in `www/js/` and reference it from the template's ``. -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). +# yet to come +- Logfile also generated in imgProcess Class (0.9.2) +- Number and camera filename (virgin images) input (0.9.3) +- Proper stop meganism (0.9.1) +- better error handling: dead links give dynamicly generated image thats allso used in collage (0.9.3) +- visualisation of logfile, so viewers can see what is happening (0.9.1) +- replacing all 'random' image processing parameters (0.7) diff --git a/docs/framework_guide.md b/docs/framework_guide.md new file mode 100644 index 0000000..4bff8d8 --- /dev/null +++ b/docs/framework_guide.md @@ -0,0 +1,479 @@ +# ZAP Framework Guide + +This document explains the internal architecture of ZapMachine and provides step‑by‑step instructions for extending it with new handlers, templates, and interactive elements. + +## Architecture Overview + +### Front-Controller Routing (`?mode=X` → `ZAPController` → `ZAP{Mode}`) + +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. + +```php +// www/index.php (simplified) +$mode = isset($_GET['mode']) ? $_GET['mode'] : 'home'; +require_once '..' . DIRECTORY_SEPARATOR . 'conf' . DIRECTORY_SEPARATOR . 'init.php'; +$config = Config::getInstance(); +$config->register('engines', $search_engines); +$frontcontroller = new ZAPController($mode); +$output = $frontcontroller->fetch(); +print($output); +``` + +`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()`: + +```php +class ZAPController { + private $prefix = 'ZAP'; + + public function __construct($mode = 'home') { + $mode = trim(strip_tags($mode)); + $this->mode = ucfirst($mode); + $clazz = $this->prefix . $this->mode; + if (!class_exists($clazz)) { + die('Wrong parameter'); + } + $this->action = new $clazz(); + } + + public function fetch() { + return $this->action->getContent(); + } +} +``` + +**Registered modes and their handlers:** + +| `?mode=` | Class | Template | Purpose | +|----------|-------|----------|---------| +| `home` (default) | `ZAPHome` | `tpl/index.tpl` | Word-input form | +| `display` | `ZAPDisplay` | `tpl/display.tpl` | Collage-generation progress screen | +| `zap` | `ZAPZap` | *(raw image output)* | Downloads images & processes collages, returns image blobs | + +--- + +### Autoloader + +`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`: + +```php +spl_autoload_register(function ($class_name) { + $include_path_tokens = explode(PATH_SEPARATOR, get_include_path()); + foreach ($include_path_tokens as $prefix) { + $path[0] = $prefix . DIRECTORY_SEPARATOR . $class_name . '.interface.php'; + $path[1] = $prefix . DIRECTORY_SEPARATOR . $class_name . '.class.php'; + foreach ($path as $thisPath) { + if (file_exists($thisPath)) { + require_once $thisPath; + return; + } + } + } +}); +``` + +The include path is built from these directories (relative to the project root): + +| Constant | Directory | +|----------|-----------| +| `ZAP_RESOURCE_DIR` | `resource/` — search-engine implementations | +| `ZAP_UTILS_DIR` | `utils/` — utility classes (Session, ZAPCurl, Log) | +| `ZAP_SESSIONS_DIR` | `sessions/` | +| `ZAP_LIB_DIR` | `lib/` — core handlers and image processing | +| `ZAP_TPL_DIR` | `tpl/` — templates | +| `ZAP_CONF_DIR` | `conf/` — configuration | + +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') +Session::reset(); // Clear $_SESSION array +Session::deleteCookie(); // Remove client-side session cookie +Session::destroy(); // Reset + delete cookie + session_destroy() +``` + +Session data is stored verbatim in `$_SESSION` and uses these keys: + +| Key | Type | Set by | Purpose | +|-----|------|--------|---------| +| `words` | `string[]` | `ZAPDisplay` | Keyword list for image search | +| `folder` | `string` | `ZAPDisplay` | Absolute path to session folder | +| `collages` | `int` | `ZAPDisplay` | Number of collages to generate | +| `width` | `int` | `ZAPDisplay` | Collage width in pixels | +| `height` | `int` | `ZAPDisplay` | Collage height in pixels | +| `images` | `string[]` | `ZAPZap` | Queue of source image paths for current collage | +| `imgOriginal` | `string[]` | `ZAPZap` | Immutable copy of the original image list | +| `collageNr` | `int` | `ZAPZap` | Current collage sequence number | + +**CLI note:** Session is only started automatically in web mode. See [CLI Bootstrap](#cli-bootstrap) below. + +--- + +### Plugin-Based Search Engines + +Search engines implement the `Search` interface (`resource/Search.interface.php`): + +```php +interface Search { + public function setQuery($word); + public function setParam($key, $value); + public function getData(); +} +``` + +Each engine class lives in `resource/` and returns data in a universal array format: + +```php +$data['result'] = true; // Boolean: success / failure +$data['engine'] = __CLASS__; // Engine name, e.g. 'Brave' +$data['results'][0]['clickurl'] = '...'; // Image URL to download +$data['results'][0]['referer'] = '...'; // Source page URL +$data['results'][0]['thumb'] = '...'; // Thumbnail URL +``` + +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. + +To add a new engine: +1. Create `resource/YourEngine.class.php` implementing `Search`. +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. + +4. **`ImgTools`** (`lib/ImgTools.class.php`) — utility functions: `zoomCrop()`, `transparantRandom()`, `randomRotate()`, `resize()`, `recolor()`, `convertGifsToPng()`. + +--- + +### CLI Bootstrap + +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 +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 + $this->tpl .= ob_get_clean(); // ← capture buffer content, clean buffer + } + + public function getContent() { + return $this->tpl; + } +} +``` + +**Step by step:** + +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()`. + +### The `getZAPTemplate()` Helper + +```php +function getZAPTemplate($tplname) { + $tplfile = $tplname . '.tpl'; + $tplfilepath = ZAP_APP_BASE_DIR . DIRECTORY_SEPARATOR + . ZAP_TPL_DIR . DIRECTORY_SEPARATOR . $tplfile; + if (file_exists($tplfilepath)) { + return $tplfilepath; + } else { + return ERROR_PREFIX . 'No template'; + } +} +``` + +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: + + ```php + pageTitle = 'My Example Page'; + extract(get_object_vars($this)); + + ob_start(); + include getZAPTemplate($this->template); + $this->tpl .= ob_get_clean(); + } + + public function getContent() { + return $this->tpl; + } + } + ``` + +2. **Create the template** at `tpl/example.tpl`: + + ```php + + + Example — <?php echo $pageTitle; ?> + +

+

ZAP version

+ + + ``` + +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 `` that POSTs to `index.php?mode=display`. Each input is named `word01`, `word02`, … `word{MAX_WORDS}` (zeropadded to two digits). A `