diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a5b4848
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+sessions/*
+ignore
diff --git a/README.md b/README.md
index abcb080..b539f72 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,53 @@
# ZapMachine
-Revival of the old ZapMachine. A collage machine that merges random images layer by layer remaining about 50% of the original images
\ No newline at end of file
+Revival of the old ZapMachine. A collage machine that merges random images layer by layer remaining about 50% of the original images
+
+########################################################
+#### ApFab - Zap Machine V0.9.0 ###
+########################################################
+🄯 2009 - 2026 Fabian de Boer, Vincent Bruijne
+www.apfab.com
+
+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.
+
+Adjust permission (chmod 777) for Sessions folder, run index.html, fill in the form and voila.
+
+
+
+# how it works
+
+- 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)
+
+
+
+# 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/changelog.txt b/changelog.txt
new file mode 100755
index 0000000..264f813
--- /dev/null
+++ b/changelog.txt
@@ -0,0 +1,48 @@
+ZAP is a refactorment of the Zap Machine V0.3.2 from ApFab
+It's more like a framework now.
+
+todo ZAP
+- add .htaccess file
+
+# ZAP
+
+0.5.0
+- updated the search engines removed Yahoo, added Bing
+
+# ZAPV
+
+0.1.0
+- new public release of ZAP
+
+0.0.9
+- made logging dependent
+- made adult searches config dependent
+
+0.0.8
+- refactored the ImgProcess class and removed code to two other classes
+- added ImgIOTools and ImgTools. They contain static functions
+
+0.0.7
+- refactored the whole search engine part
+- made an interface for future implementations of other search engines
+
+0.0.6
+- added a Config class
+
+0.0.5
+- split the search code to separate functions
+- added ZAPCurl util class
+
+0.0.4
+- split the zap code to separate functions
+
+0.0.3
+- refactored all classes to more readable code
+- created Session utils class
+
+0.0.2
+- created new index.php and front controller
+- added conf.php and init.php
+
+0.0.1
+- reworked frontend to separate html and js
diff --git a/cli_scripts/reset.php b/cli_scripts/reset.php
new file mode 100755
index 0000000..6cd3f1f
--- /dev/null
+++ b/cli_scripts/reset.php
@@ -0,0 +1,28 @@
+session reset
';
+
+if ($_GET['allcollages'] == 'clear') {
+ $sessions = glob('./Sessions/*');
+ foreach($sessions as $folder){
+ $files = glob($folder.'/*');
+ foreach($files as $fileToDelete){
+ echo "$fileToDelete deleted ";
+ unlink($fileToDelete);
+ }
+ echo "$folder deleted ";
+ rmdir($folder);
+ }
+}
+echo '
';
+
+session_destroy();
+?>
\ No newline at end of file
diff --git a/cli_scripts/test.php b/cli_scripts/test.php
new file mode 100755
index 0000000..3a618cd
--- /dev/null
+++ b/cli_scripts/test.php
@@ -0,0 +1,192 @@
+ #!/usr/bin/env php
+register('engines', $search_engines);
+
+ $search = new ImgSearch($folder, $word);
+ $data = $search->getImageData();
+
+ echo "Raw JSON output:\n";
+ echo json_encode($data, JSON_PRETTY_PRINT) . "\n\n";
+
+ test('Result is an array', is_array($data));
+ test('Has result key', isset($data['result']));
+
+ if (isset($data['result']) && $data['result'] === true) {
+ test('Search returned results', true);
+ test('Has results array', isset($data['results']));
+ if (isset($data['results'])) {
+ $count = count($data['results']);
+ echo " Results count: $count\n";
+ }
+ } else {
+ echo " âš No results returned. Check your API key and configuration.\n";
+ }
+}
+
+/**
+ * Test Brave search JSON output
+ */
+function testBraveSearch() {
+ echo "\n=== Brave Search JSON Test ===\n";
+ echo "Query: 'test' | Results: 3\n\n";
+
+ $brave = new Brave();
+ $brave->setQuery('test');
+ $brave->setParam('size', 3);
+
+ $json = $brave->getData();
+
+ echo "Raw JSON output:\n";
+ echo json_encode($json, JSON_PRETTY_PRINT) . "\n\n";
+
+ // Validate structure
+ test('Result is an array', is_array($json));
+ test('Has result key', isset($json['result']));
+
+ if (isset($json['result']) && $json['result'] === true) {
+ test('Search returned results', true);
+ test('Has results array', isset($json['results']));
+ test('Engine is Brave', isset($json['engine']) && $json['engine'] === 'Brave');
+
+ if (isset($json['results'])) {
+ $count = count($json['results']);
+ test("Results count is 3 (got $count)", $count === 3);
+
+ // Check first result structure
+ if ($count > 0) {
+ $first = $json['results'][0];
+ test('First result has clickurl', isset($first['clickurl']));
+ test('First result has thumb', isset($first['thumb']));
+ test('First result has referer', isset($first['referer']));
+ }
+ }
+ } else {
+ echo "\n âš No results returned. Check your API key and CX.\n";
+ }
+}
+
+// Main execution
+if (in_array('--search', $argv)) {
+ testImgSearch();
+} elseif (in_array('--brave', $argv)) {
+ testBraveSearch();
+} else {
+ runBasicTests();
+
+ // Ask if user wants to run Brave test
+ if (defined('BRAVE_API_KEY') && BRAVE_API_KEY !== 'YOUR_BRAVE_API_KEY') {
+ echo "\nRun Brave search test? (y/n): ";
+ $handle = fopen("php://stdin", "r");
+ $line = fgets($handle);
+ if (trim(strtolower($line)) === 'y') {
+ testBraveSearch();
+ }
+ fclose($handle);
+ } else {
+ echo "\n âš Skipping Brave test: API key not configured\n";
+ }
+}
+
+// Summary
+echo "\n=== Results ===\n";
+echo "Passed: $passed\n";
+echo "Failed: $failed\n";
+echo ($failed === 0 ? "All tests passed! ✓" : "Some tests failed.") . "\n\n";
+
+exit($failed > 0 ? 1 : 0);
+?>
\ No newline at end of file
diff --git a/conf/Config.class.php b/conf/Config.class.php
new file mode 100755
index 0000000..34c5acb
--- /dev/null
+++ b/conf/Config.class.php
@@ -0,0 +1,35 @@
+config[$key] = $value;
+ return;
+ }
+
+ public function retrieve($key) {
+ if (isset($this->config[$key])) {
+ return $this->config[$key];
+ } else {
+ return false;
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/conf/conf.php b/conf/conf.php
new file mode 100755
index 0000000..f213c65
--- /dev/null
+++ b/conf/conf.php
@@ -0,0 +1,25 @@
+
diff --git a/conf/init.php b/conf/init.php
new file mode 100755
index 0000000..ed0dbc1
--- /dev/null
+++ b/conf/init.php
@@ -0,0 +1,99 @@
+getMessage();
+ die();
+ }
+ }
+
+ $include_path = get_include_path();
+ $include_path_tokens = explode(':', $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;
+ }
+ }
+ }
+});
+
+/**
+ * Define remaining app-wide constants
+ */
+define('ZAP_MOMENT', date("Y-m-d-H_i_s"));
+define('ZAP_VERSION', '0.9.0');
+define('ZAP_SESSION_NAME', 'ZAP');
+
+// Only start session in web mode (not CLI)
+if (php_sapi_name() !== 'cli') {
+ Session::start(ZAP_SESSION_NAME);
+}
+
+/**
+ * returns string of path of template file
+ * @return string
+ */
+function getZAPTemplate($tplname) {
+ if (!defined('ZAP_APP_BASE_DIR')) {
+ throw new Exception(ERROR_PREFIX . 'No base directory defined?!');
+ }
+ $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';
+ }
+}
+?>
\ No newline at end of file
diff --git a/lib/ImgIOTools.class.php b/lib/ImgIOTools.class.php
new file mode 100755
index 0000000..a41127f
--- /dev/null
+++ b/lib/ImgIOTools.class.php
@@ -0,0 +1,50 @@
+scaleImage(self::$maxScreenWidth, self::$maxScreenHeight, true);
+
+ //$img = self::scaleByLength($img, $this->maxScreenWidth, $this->maxScreenHeight);
+ header('Content-type: image/' . ZAP_IMG_TYPE);
+ echo $img->getImageBlob();
+ }
+
+ /**
+ * Puts image from $imgUrl (if exists) onscreen
+ * @param string $imgUrl
+ * @return boolean or imagedata
+ */
+ public static function urlOnScreen($imgUrl) {
+ if (file_exists($imgUrl)) {
+ $img = new Imagick($imgUrl);
+ self::onScreen($img);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Saves active image from $im as $filename.$filetype in $folder
+ * @param Imagick $im
+ * @param string $folder
+ * @param string $filename
+ * @param string $filetype
+ * @return boolean
+ */
+ public static function saveImg(Imagick $img, $folder, $filename) {
+ $path = $folder . $filename . '.' . ZAP_IMG_TYPE;
+ return $img->writeImage($path);
+ }
+}
+?>
\ No newline at end of file
diff --git a/lib/ImgProcess.class.php b/lib/ImgProcess.class.php
new file mode 100755
index 0000000..20f8544
--- /dev/null
+++ b/lib/ImgProcess.class.php
@@ -0,0 +1,92 @@
+folder = $imgFolder;
+
+ $this->zapWidth = $width;
+ $this->zapHeight = $height;
+
+ $this->canvasName = DIRECTORY_SEPARATOR . ZAP_CANVAS_NAME;
+ $this->canvasName .= $_SESSION['date'] . "_nr_" . sprintf("%02d", $nr);
+
+ $tmpImg = new Imagick();
+ $tmpAr = $tmpImg->getQuantumRange();
+ $this->maxRGB = $tmpAr['quantumRangeLong'];
+ $tmpImg->destroy();
+ }
+
+ /**
+ * Makes a basic canvasFile from sourceImage
+ * and saves it into imageFolder
+ * returns imageBLOB from saved image
+ * @param string $sourceImgName
+ * @return void
+ */
+ public function createBasicCanvas($sourceImgName) {
+ $canvas = new Imagick($sourceImgName);
+ $canvas = ImgTools::zoomCrop($canvas, $this->zapWidth, $this->zapHeight, TRUE);
+
+ ImgIOTools::saveImg($canvas, $this->folder, $this->canvasName);
+ if (ZAP_DELETE_SOURCE == true) {
+ unlink($sourceImgName);
+ }
+ ImgIOTools::onScreen($canvas);
+ }
+
+ /**
+ * adds processed version of $sourceImage to canvas
+ * @param string $sourceImgName
+ * @return void
+ */
+ public function addToCanvas($sourceImgName) {
+ $canvasUri = $this->folder . $this->canvasName . '.' . ZAP_IMG_TYPE;
+ $canvas = new Imagick($canvasUri);
+
+ $source_im = new Imagick($sourceImgName);
+
+ $newCanvas_im = $this->zapToCanvas($source_im, $canvas);
+ ImgIOTools::saveImg($newCanvas_im, $this->folder, $this->canvasName);
+ if (ZAP_DELETE_SOURCE == true) {
+ unlink($sourceImgName);
+ }
+ ImgIOTools::onScreen($newCanvas_im);
+ }
+
+ /**
+ * @param Imagick $source_im
+ * @param Imagick $canvas
+ * @return Imagick Object
+ */
+ private function zapToCanvas(Imagick $source_im, $canvas) {
+ //$val = ImgTools::imgCenterValueProm($source_im, $this->maxRGB);
+
+ //merge resized random transparised image on top of canvas
+ $transparent = ImgTools::transparantRandom($source_im, $this->maxRGB);
+ $resized = ImgTools::resize($transparent, $this->zapWidth, $this->zapHeight);
+
+ $canvas->compositeImage($resized, imagick::COMPOSITE_OVER, 0, 0);
+ return $canvas;
+ }
+}
+?>
diff --git a/lib/ImgSearch.class.php b/lib/ImgSearch.class.php
new file mode 100755
index 0000000..c1aaf46
--- /dev/null
+++ b/lib/ImgSearch.class.php
@@ -0,0 +1,145 @@
+folder = $imgFolder;
+ $this->word = $word;
+
+ $this->hitPosition = (int) rand(0,10);
+
+ if (ZAP_LOG == true) {
+ $this->log = new Log($this->folder);
+ }
+
+ $config = Config::getInstance();
+ $engines = $config->retrieve('engines');
+ $this->engine = $engines[array_rand($engines)];
+ }
+
+ /**
+ * download image that corresponds to $keyWord
+ * returns filename of image if downloaded succeeded, or false if failed
+ * @return string $localUrl / boolean
+ */
+ public function getImage() {
+ $imgData = $this->getImageData();
+ if ($imgData == false) {
+ $this->addLog('error','Search: ', 'bad search data received' );
+ return false;
+ }
+ $this->addLog('ok', 'Engine: ', $imgData['engine']);
+
+ $localUrl = $this->imageToTmp($imgData['results'], $this->folder);
+
+ return $localUrl;
+ }
+
+ /**
+ * Returns an array that contains searchdata for $word
+ * @return array $searchData or false
+ */
+ public function getImageData() {
+ $clazz = ucfirst($this->engine);
+ $search = new $clazz();
+ $search->setQuery(urlencode($this->word));
+ $search->setParam('start', $this->hitPosition);
+ $search->setParam('size', $this->numberResults);
+
+ $searchData = $search->getData();
+
+ return $searchData['result'] == false ? false : $searchData;
+ }
+
+ /**
+ * Saves image or returns false
+ * @param array $results
+ * @param string $tmpDir
+ * @param integer $hit
+ * @return mixed
+ */
+ private function imageToTmp($results, $tmpDir, $hit = 0) {
+ $first = $results[$hit]['clickurl'];
+ $imageName = $tmpDir . DIRECTORY_SEPARATOR . $this->filename($first);
+
+ $myCurl = new ZAPCurl($first, true);
+ $imageData = $myCurl->fetchCurlData();
+
+ if($imageData == false) {
+ $this->addLog('error', 'Search: ', 'received empty file' . PHP_EOL);
+ return false;
+ }
+
+ if(file_put_contents($imageName, $imageData)) {
+ if($this->imageTypeOk($imageName)) {
+ $this->addLog('ok', 'Search: ', "downloaded to $imageName");
+ $this->addLog('ok', 'Search: ', 'source: ' . $results[$hit]['referer']);
+ $this->addLog('ok', 'Search: ', 'thumb: ' . $results[$hit]['thumb'] . PHP_EOL);
+ return $imageName;
+ } else {
+ $this->addLog('error', 'Search: ', 'wrong file type: image removed');
+ unlink($imageName);
+
+ $nextHit = $hit + 1;
+ return $this->imageToTmp($results, $tmpDir, $nextHit);
+ }
+ } else {
+ $this->addLog('error', 'Search: ', 'failed saving image');
+ return false;
+ }
+ }
+
+ /**
+ * Check mime type of image
+ * @param string $fileUri
+ * @return boolean
+ */
+ private function imageTypeOk($fileUri) {
+ $mimeType = mime_content_type($fileUri);
+ return in_array($mimeType, $this->allowedFileTypes);
+ }
+
+ /**
+ * Gets filename from a url
+ * @param string $urlString
+ * @return string
+ */
+ private function filename($urlString) {
+ $ar = explode('/', $urlString);
+ $filename = array_pop($ar);
+ // Strip query string so the saved file has a real extension
+ $filename = explode('?', $filename)[0];
+ return $filename;
+ }
+
+ /**
+ * Log messages
+ */
+ private function addLog($type, $head, $message) {
+ if (ZAP_LOG == true) {
+ $this->log->addToLog($type, $head, $message);
+ }
+ return;
+ }
+}
+?>
\ No newline at end of file
diff --git a/lib/ImgTools.class.php b/lib/ImgTools.class.php
new file mode 100755
index 0000000..995ad9f
--- /dev/null
+++ b/lib/ImgTools.class.php
@@ -0,0 +1,180 @@
+getImageWidth());
+ $yPoint = mt_rand(0,$img->getImageHeight());
+ $randomPixel = $img->getImagePixelColor($xPoint, $yPoint);
+ $randomPixelColor = $randomPixel->getColorAsString();
+
+ //make transparency
+ $img->transparentPaintImage($randomPixelColor, 0.0, $randFuzz, false);
+ return $img;
+ }
+
+ /**
+ * random Rotate image
+ * @param Imagick $im
+ * @return Imagick $im
+ */
+ public static function randomRotate(Imagick $img) {
+ $transp = new ImagickPixel('#ffffff');
+ $ar = array(0,90,180,270);
+ $img->rotateImage($transp, $ar[mt_rand(0,3)]);
+ return $img;
+ }
+
+ /**
+ * Resize image
+ * @param Imagick $im
+ * @param integer $retWidth
+ * @param integer $retHeight
+ * @return Imagick $im
+ */
+ public static function resize(Imagick $img, $retWidth, $retHeight) {
+ $img->resizeImage((int)$retWidth, (int)$retHeight, imagick::FILTER_POINT, 0);
+ return $img;
+ }
+
+ /**
+ * Recolor image
+ * @param Imagick $im
+ * @return Imagick $im
+ */
+ public static function recolor(Imagick $img){
+ $recolorValue = mt_rand(4, 250);
+ $times = mt_rand(1, 4);
+ for($i = 0; $i <= $times; $i++) {
+ $img->cycleColormapImage($recolorValue);
+ }
+ return $img;
+ }
+
+ /**
+ * returns MagickWand with randomly zoomed part of image $im with size $retWidth * $retHeight
+ * @param Imagick $im
+ * @param integer $retWidth
+ * @param integer $retHeight
+ * @param boolean $rotate
+ */
+ public static function zoomCrop(Imagick $img, $retWidth, $retHeight, $rotate = false) {
+ if ($rotate == true) {
+ $img = self::randomRotate($img);
+ }
+
+ $fact = mt_rand(10, 40) / 100; //zoomfactor 10 - 40%
+ $width = $img->getImageWidth();
+ $height = $img->getImageHeight();
+ $newWidth = (int)($width * $fact);
+ $newHeight = (int)($height * $fact);
+ //crop $im
+ //x and y offset random 0 - width (or height) of blown up image minus crop area (of course!)
+ $img->cropImage($newWidth, $newHeight,
+ mt_rand(0, (int)($width - $newWidth)),
+ mt_rand(0, (int)($height - $newHeight)));
+
+ $img->resizeImage((int)$retWidth, (int)$retHeight, imagick::FILTER_POINT, 0);
+ return $img;
+ }
+
+ /**
+ * converts all gif images in array to png images
+ * returns Array with converted filenames
+ * @param array $imgAr
+ * @return array $newArray
+ */
+ public static function convertGifsToPng($imgAr) {
+ $newArray = array();
+ while ($filename = array_shift($imgAr)) {
+ $imgInfo = getimagesize($filename);
+ $mimeType = $imgInfo['mime'];
+ //find gif
+ if ($mimeType == "image/gif") {
+ $imagetype = 'png';
+ //full image name
+ $Dot = '.';
+ $newFilename = "$filename$Dot$imagetype";
+ //convert
+ $img = new Imagick($filename);
+
+ if($img->writeImage($newFilename)) {
+ //Remove original and rename new to save space
+ unlink($filename);
+ array_push($newArray, $newFilename);
+ }
+ } else {
+ array_push($newArray, $filename);
+ }
+
+ }
+ return $newArray;
+ }
+
+ /**
+ * LEGACY?
+ * INT (0 - 1000) -> promilage of average RGB value of center pixel of $im related to $maxRGB
+ * @param Imagick $im
+ * @param int $maxRGB
+ * @return int
+ */
+ public static function imgCenterValueProm(Imagick $img, $maxRGB) {
+ $centerX = $img->getImageWidth() / 2;
+ $centerY = $img->getImageHeight() / 2;
+
+ //take average of the RGB of most middle pixel of each image and put it into an array
+ $centerPixel = $img->getImagePixelColor($centerX,$centerY);
+ $colors = $centerPixel->getColor();
+
+ $averageColor = round( ($colors['r'] + $colors['g'] + $colors['b']) / 3);
+
+ return round(($averageColor / $maxRGB) * 1000);
+ }
+
+ /**
+ * LEGACY?
+ * returns magickWand scaled proportionaly to $maxW or $maxH
+ * @param Imagick $im
+ * @param integer $maxW
+ * @param integer $maxH
+ * @return Imagick $im
+ */
+ public static function scaleByLength(Imagick $img, $maxW, $maxH) {
+ $width = $img->getImageWidth();
+ $height = $img->getImageHeight();
+ if(($width <= self::$maxScreenWidth) && ($height <= self::$maxScreenHeight)) {
+ return $img;
+ } else {
+ if ($width >= $height) { //horizontal image
+ $new_x = $maxW;
+ $new_y = round(($new_x / $width) * $height, 0);
+ } else { //vertical image
+ $new_y = $maxH;
+ $new_x = round(($new_y / $height) * $width, 0);
+ }
+ return self::resize($img, $new_x, $new_y);
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/lib/ZAPController.class.php b/lib/ZAPController.class.php
new file mode 100755
index 0000000..27214f1
--- /dev/null
+++ b/lib/ZAPController.class.php
@@ -0,0 +1,41 @@
+mode = ucfirst($mode);
+ $clazz = $this->prefix . $this->mode;
+ if (!class_exists($clazz)) {
+ die('Wrong parameter');
+ }
+ $this->action = new $clazz();
+ }
+
+ /**
+ * @return string content
+ */
+ public function fetch() {
+ return $this->action->getContent();
+ }
+}
+?>
\ No newline at end of file
diff --git a/lib/ZAPDisplay.class.php b/lib/ZAPDisplay.class.php
new file mode 100755
index 0000000..a1398ab
--- /dev/null
+++ b/lib/ZAPDisplay.class.php
@@ -0,0 +1,91 @@
+folder = $this->createFolder();
+ $this->words = $this->handleWords();
+
+ $_SESSION['words'] = $this->words;
+ $_SESSION['folder'] = $this->folder;
+ $_SESSION['collages'] = (int) $_POST['collages']; //number of zaps
+ $_SESSION['width'] = (int) $_POST['width'];
+ $_SESSION['height'] = (int) $_POST['height'];
+ $_SESSION['date'] = ZAP_MOMENT;
+
+ $ver = ZAP_VERSION;
+ include getZAPTemplate($this->template);
+ $this->tpl .= ob_get_clean();
+ }
+
+ /**
+ * Create folder and return path
+ * @return string $folder path or throws exception
+ */
+ private function createFolder() {
+ try {
+ $folder = ZAP_APP_BASE_DIR . DIRECTORY_SEPARATOR
+ . ZAP_SESSIONS_DIR . DIRECTORY_SEPARATOR . 'zap_' . ZAP_MOMENT;
+ if (!@mkdir($folder, 0777)) {
+ throw new Exception('' . ERROR_PREFIX . 'Unable to create base dir: ' . $folder . '
');
+ }
+ return $folder;
+ } catch (Exception $e) {
+ $this->tpl .= $e->getMessage();
+ return;
+ }
+ }
+
+ /**
+ * Create an array based on post data
+ * @return array $words
+ */
+ private function handleWords() {
+ $words = array();
+ $ix = 1;
+ $postix = sprintf("%02d", $ix);
+ $wd = 'word' . $postix;
+
+ while(!empty($_POST[$wd])) {
+ array_push($words, $_POST[$wd]);
+ $ix++;
+ $postfix = sprintf("%02d", $ix);
+ $wd = 'word' . $postfix;
+ }
+ return $words;
+ }
+
+ /**
+ * return rendered content
+ */
+ public function getContent() {
+ return $this->tpl;
+ }
+
+}
+?>
\ No newline at end of file
diff --git a/lib/ZAPHome.class.php b/lib/ZAPHome.class.php
new file mode 100755
index 0000000..38764b5
--- /dev/null
+++ b/lib/ZAPHome.class.php
@@ -0,0 +1,40 @@
+ver = ZAP_VERSION;
+ extract(get_object_vars($this));
+
+ ob_start();
+ include getZAPTemplate($this->template);
+ $this->tpl .= ob_get_clean();
+ }
+
+ /**
+ * @return cotent string
+ */
+ public function getContent() {
+ return $this->tpl;
+ }
+}
+?>
\ No newline at end of file
diff --git a/lib/ZAPZap.class.php b/lib/ZAPZap.class.php
new file mode 100755
index 0000000..175f10a
--- /dev/null
+++ b/lib/ZAPZap.class.php
@@ -0,0 +1,121 @@
+destroySession();
+ $this->tpl .= ob_get_clean();
+ return;
+ }
+
+ $this->width = $_SESSION['width'];
+ $this->height = $_SESSION['height'];
+ $this->folder = $_SESSION['folder'];
+ $this->words = $_SESSION['words'];
+
+ if (!empty($this->words)) {
+ // Still downloading source images for the words.
+ $this->showFoundImage();
+ } elseif (empty($_SESSION['images'])) {
+ // Start a new collage.
+ $this->buildNextCollage();
+ } else {
+ // Add another source image to the current collage.
+ $imgName = array_shift($_SESSION['images']);
+ $image = $this->createImage($_SESSION['collageNr']);
+ $image->addToCanvas($imgName);
+ }
+ $this->tpl .= ob_get_clean();
+ }
+
+ /**
+ * Build the next collage if any remain, otherwise finish.
+ */
+ private function buildNextCollage() {
+ if ($_SESSION['collages'] <= 0) {
+ $this->destroySession();
+ return;
+ }
+
+ // On the very first collage, load the downloaded source images.
+ // On later collages, reuse the original list.
+ if (!isset($_SESSION['imgOriginal'])) {
+ $_SESSION['images'] = ImgTools::convertGifsToPng($this->retrieveImages());
+ $_SESSION['imgOriginal'] = $_SESSION['images'];
+ } else {
+ $_SESSION['images'] = $_SESSION['imgOriginal'];
+ shuffle($_SESSION['images']);
+ }
+
+ if (empty($_SESSION['images'])) {
+ $this->destroySession();
+ return;
+ }
+
+ // Track the current collage number separately so addToCanvas always
+ // reads/writes the same file while this collage is being built.
+ $_SESSION['collageNr'] = $_SESSION['collages'];
+ $_SESSION['collages']--;
+
+ $firstIm = array_shift($_SESSION['images']);
+ $image = $this->createImage($_SESSION['collageNr']);
+ $image->createBasicCanvas($firstIm);
+ }
+
+ private function createImage($collages) {
+ return new ImgProcess($this->folder, $this->width, $this->height, $collages);
+ }
+
+ private function destroySession() {
+ if (Session::destroy()) {
+ $this->tpl .= "" . ERROR_PREFIX . "Image ready
";
+ } else {
+ $this->tpl .= "" . ERROR_PREFIX . "Strange... Could not end PHP session";
+ }
+ return;
+ }
+
+ private function setSessionVar($key, $value) {
+ $_SESSION[$key] = $value;
+ }
+
+ private function showFoundImage() {
+ $keyWord = array_shift($this->words);
+ $search = new ImgSearch($this->folder, $keyWord);
+ $imageUrl = $search->getImage();
+
+ if ($imageUrl) {
+ ImgIOTools::urlOnScreen($imageUrl);
+ } else {
+ ImgIOTools::urlOnScreen(ZAP_APP_BASE_DIR . DIRECTORY_SEPARATOR
+ . ZAP_WEB_DIR . DIRECTORY_SEPARATOR
+ . 'images' . DIRECTORY_SEPARATOR . 'error_noHit.png');
+ }
+ $this->setSessionVar('words', $this->words);
+ }
+
+ private function retrieveImages() {
+ $img_ar = glob('{' . $this->folder . DIRECTORY_SEPARATOR .'*.jpg,'
+ . $this->folder . DIRECTORY_SEPARATOR .'*.png,'
+ . $this->folder . DIRECTORY_SEPARATOR .'*.jpeg,'
+ . $this->folder . DIRECTORY_SEPARATOR .'*.JPG,'
+ . $this->folder . DIRECTORY_SEPARATOR .'*.gif'
+ . '}', GLOB_BRACE);
+ shuffle($img_ar);
+ return $img_ar;
+ }
+
+ public function getContent() {
+ return $this->tpl;
+ }
+}
+?>
diff --git a/resource/Brave.class.php b/resource/Brave.class.php
new file mode 100644
index 0000000..a55f164
--- /dev/null
+++ b/resource/Brave.class.php
@@ -0,0 +1,70 @@
+adultPrefix .= 'off';
+ } else {
+ $this->adultPrefix .= 'strict';
+ }
+ }
+
+ public function setQuery($word) {
+ $this->word = $word;
+
+ $request = $this->q_prefix . urlencode($word);
+ if (isset($this->start)) {
+ $request .= '&offset=' . $this->start;
+ }
+ $request .= $this->count_prefix . (isset($this->size) ? (int)$this->size : 3);
+ $request .= $this->adultPrefix;
+
+ $this->myCurl = new ZAPCurl($this->base_url . $request, false);
+ $this->myCurl->setHeader('X-Subscription-Token', $this->key);
+ $this->myCurl->setHeader('Accept', 'application/json');
+ }
+
+ public function setParam($key, $value) {
+ $this->$key = $value;
+ }
+
+ public function getData() {
+ $return_array = $this->prepareData();
+ return $return_array;
+ }
+
+ /**
+ * Prepare a project wide universal array with results
+ */
+ private function prepareData() {
+ $json = $this->myCurl->fetchCurlData();
+ $se_data = json_decode($json, true);
+
+ if (!isset($se_data['results'][0]['properties']['url'])) {
+ $data['result'] = false;
+ return $data;
+ }
+
+ $data['result'] = true;
+ $data['engine'] = __CLASS__;
+ $i = 0;
+ foreach($se_data['results'] as $result) {
+ $data['results'][$i]['clickurl'] = $result['properties']['url'];
+ $data['results'][$i]['referer'] = isset($result['url']) ? $result['url'] : '';
+ $data['results'][$i]['thumb'] = isset($result['thumbnail']['src']) ? $result['thumbnail']['src'] : '';
+ $i++;
+ }
+ return $data;
+ }
+}
+?>
\ No newline at end of file
diff --git a/resource/Search.interface.php b/resource/Search.interface.php
new file mode 100755
index 0000000..8d57937
--- /dev/null
+++ b/resource/Search.interface.php
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/tpl/display.tpl b/tpl/display.tpl
new file mode 100755
index 0000000..a573168
--- /dev/null
+++ b/tpl/display.tpl
@@ -0,0 +1,24 @@
+
+
+
+
+ Zap Machine
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tpl/index.tpl b/tpl/index.tpl
new file mode 100755
index 0000000..a5fc6f4
--- /dev/null
+++ b/tpl/index.tpl
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Zap ZapMachine
+
+
+
+
+ Zap Machine
+
+
+
+
+
+
+
diff --git a/utils/Log.class.php b/utils/Log.class.php
new file mode 100755
index 0000000..db162db
--- /dev/null
+++ b/utils/Log.class.php
@@ -0,0 +1,28 @@
+folder = $imgFolder;
+
+ $this->logFileName = $imgFolder . DIRECTORY_SEPARATOR . 'log.txt';
+ $this->logFileXmlName = $imgFolder . DIRECTORY_SEPARATOR . 'log.xml';
+ }
+
+ public function addToLog($type, $head, $addString) {
+ $addToLog = "$head $addString" . PHP_EOL;
+ $addToXmlLog = "" . PHP_EOL . "\t
$head " . PHP_EOL;
+ $addToXmlLog .= "\t
$addString
" . PHP_EOL . "
" . PHP_EOL;
+
+ $txtOk = @file_put_contents($this->logFileName, $addToLog, FILE_APPEND);
+ $xmlOk = @file_put_contents($this->logFileXmlName, $addToXmlLog, FILE_APPEND);
+ }
+}
+?>
\ No newline at end of file
diff --git a/utils/Session.class.php b/utils/Session.class.php
new file mode 100755
index 0000000..15f660e
--- /dev/null
+++ b/utils/Session.class.php
@@ -0,0 +1,63 @@
+
\ No newline at end of file
diff --git a/utils/ZAPCurl.class.php b/utils/ZAPCurl.class.php
new file mode 100755
index 0000000..f86a0b6
--- /dev/null
+++ b/utils/ZAPCurl.class.php
@@ -0,0 +1,64 @@
+url = $url;
+ $this->binary = $binary;
+ }
+
+ /**
+ * Set a custom header for the request
+ * @param string $key Header name
+ * @param string $value Header value
+ */
+ public function setHeader($key, $value) {
+ $this->headers[] = $key . ': ' . $value;
+ }
+
+ /**
+ * Curl wrapper
+ * @param string $url
+ * @param boolean $binary
+ * @return mixed $curlData
+ */
+ public function fetchCurlData() {
+ $cUrl = curl_init();
+ curl_setopt($cUrl, CURLOPT_URL, $this->url);
+ curl_setopt($cUrl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($cUrl, CURLOPT_TIMEOUT, ZAP_CURL_TIMEOUT);
+ if (isset($this->referer)) {
+ curl_setopt($cUrl, CURLOPT_REFERER, $this->referer);
+ }
+ if (!empty($this->headers)) {
+ curl_setopt($cUrl, CURLOPT_HTTPHEADER, $this->headers);
+ }
+ //curl_setopt($cUrl, CURLOPT_PROXY, $this->proxyIp);
+
+ $curlData = curl_exec($cUrl);
+ $this->error = curl_error($cUrl);
+ $this->httpCode = curl_getinfo($cUrl, CURLINFO_HTTP_CODE);
+ // curl_close() is deprecated as of PHP 8.5 (no effect since PHP 8.0)
+
+ if ($this->error) {
+ error_log('ZAPCurl error: ' . $this->error . ' URL: ' . $this->url);
+ } elseif ($this->httpCode >= 400) {
+ error_log('ZAPCurl HTTP ' . $this->httpCode . ' URL: ' . $this->url);
+ }
+
+ return $curlData;
+ }
+}
+?>
\ No newline at end of file
diff --git a/www/css/style.css b/www/css/style.css
new file mode 100755
index 0000000..17e8438
--- /dev/null
+++ b/www/css/style.css
@@ -0,0 +1,89 @@
+body, html {
+ margin:0;
+ padding:0;
+ color:#000;
+ background-color: #fff;
+}
+
+img {
+ padding: 12px;
+ border: #bbb dotted thin;
+}
+
+h1 {
+ font-family: Georgia, serif;
+ letter-spacing: 3px;
+ font-size: 16px;
+ position: relative;
+ margin-left: 22%;
+}
+
+h2 {
+ font-family: Georgia, serif;
+ font-size: 14px;
+ position: relative;
+ margin-left: 22%;
+}
+
+.inpSection {
+ margin-top: 6%;
+ position: relative;
+ border: #bbb dotted thin;
+}
+
+.inp {
+ font-family: sans-serif;
+ font-size: 10px;
+ display: block;
+}
+
+.inpTxt {
+ width: 18px;
+ text-align: right;
+ overflow: visible;
+ margin-right: 20px;
+ display: inline;
+}
+
+form {
+ padding: 7px 0 20px 20%;
+}
+
+input {
+ margin: 12px 15px 0 15px;
+}
+
+select {
+ margin: 12px 0 0 15px;
+}
+
+#status {
+ font-size: 10px;
+ color: red;
+ margin:15px 20px 0 0;
+ position: relative;
+ left: 20px;
+}
+
+#loading {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ position: absolute;
+ left: 0;
+ top: 0;
+ visibility: hidden;
+ background: white url('../images/downloading.gif') no-repeat fixed center center;
+}
+
+#ready {
+ text-align: center;
+ font-size: 14px;
+ text-decoration: blink;
+}
+
+.btn {
+ font-family: sans-serif;
+ font-size: 10px;
+}
+
\ No newline at end of file
diff --git a/www/images/._downloading.gif b/www/images/._downloading.gif
new file mode 100755
index 0000000..82662ee
Binary files /dev/null and b/www/images/._downloading.gif differ
diff --git a/www/images/._error_noHit.png b/www/images/._error_noHit.png
new file mode 100755
index 0000000..f309d1e
Binary files /dev/null and b/www/images/._error_noHit.png differ
diff --git a/www/images/downloading.gif b/www/images/downloading.gif
new file mode 100755
index 0000000..87b5ce8
Binary files /dev/null and b/www/images/downloading.gif differ
diff --git a/www/images/error_noHit.png b/www/images/error_noHit.png
new file mode 100755
index 0000000..d6d6429
Binary files /dev/null and b/www/images/error_noHit.png differ
diff --git a/www/index.php b/www/index.php
new file mode 100755
index 0000000..52fdba0
--- /dev/null
+++ b/www/index.php
@@ -0,0 +1,42 @@
+register('engines', $search_engines);
+
+$frontcontroller = new ZAPController($mode);
+$output = $frontcontroller->fetch();
+
+print($output);
+exit;
+?>
diff --git a/www/js/display.js b/www/js/display.js
new file mode 100755
index 0000000..616ec21
--- /dev/null
+++ b/www/js/display.js
@@ -0,0 +1,33 @@
+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++);
+};
diff --git a/www/js/main.js b/www/js/main.js
new file mode 100755
index 0000000..5ad5859
--- /dev/null
+++ b/www/js/main.js
@@ -0,0 +1,54 @@
+function IsNumeric(sText) {
+ var ValidChars = "0123456789";
+ var IsNumber=true;
+ var Char;
+ for (i = 0; i < sText.length && IsNumber == true; i++) {
+ Char = sText.charAt(i);
+ if (ValidChars.indexOf(Char) == -1) {
+ IsNumber = false;
+ }
+ }
+ return IsNumber;
+}
+
+function form1Submit() {
+ //check if form is well filled
+ //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;
+ }
+ //tests ok
+ document.getElementById('status').innerHTML = ' ';
+ //download image visible and form invisible
+ document.getElementById('loading').style.visibility = 'visible';
+ document.getElementById('form1').style.visibility = 'hidden';
+ //document.getElementById('inpNrs').style.visibility = 'hidden';
+
+ //go for it!
+ document.getElementById('form1').submit();
+}
\ No newline at end of file