# 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
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 `