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 <?php echo $ver; ?> + + + + + + + + + + + +
+ + +
+
+
+ + 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 <?php echo $ver; ?> + + + + +

Zap Machine

+ +
+

Input words

+
+ + +
+ word +
+ +
+ # Zaps + +
+
+ width px +
+
+ height px +
+
+ + +
+
+
+ +
+
+ + 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