diff --git a/.devcontainer/Caddyfile b/.devcontainer/Caddyfile new file mode 100644 index 0000000..803b9e1 --- /dev/null +++ b/.devcontainer/Caddyfile @@ -0,0 +1,7 @@ +:8080 { + root * xxxxxxxxxx + php_fastcgi 127.0.0.1:9000 + file_server + + try_files {path} {path}/ /index.php?{query} +} \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..0c01994 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +FROM alpine:3.21 + + +RUN apk add --no-cache git curl php83 php83-fpm php83-opcache caddy php83-curl php83-xdebug chromium-chromedriver + +ADD .devcontainer/start.sh /start.sh +RUN chmod +x /start.sh + +RUN apk add --no-cache php83-ctype php83-dom php83-fileinfo php83-gd php83-iconv php83-simplexml php83-xml php83-xmlreader php83-xmlwriter php83-zip php83-phar php83-openssl +RUN curl -sS https://getcomposer.org/installer | /usr/bin/php83 -- --install-dir=/usr/bin --filename=composer + +# caddy stuff +ADD .devcontainer/Caddyfile /etc/caddy/Caddyfile +RUN sed -i 's/nobody/caddy/g' /etc/php83/php-fpm.d/www.conf +RUN sed -i 's/E_ALL \& ~E_DEPRECATED \& ~E_STRICT/E_ALL \& ~E_DEPRECATED \& ~E_STRICT \& ~E_NOTICE \& ~E_WARNING/g' /etc/php83/php.ini + +# configure xdebug +RUN echo "zend_extension=xdebug.so" > /etc/php83/conf.d/xdebug.ini +RUN echo "xdebug.mode=debug" >> /etc/php83/conf.d/xdebug.ini +RUN echo "xdebug.start_with_request=yes" >> /etc/php83/conf.d/xdebug.ini +RUN echo "xdebug.client_host=127.0.0.1" >> /etc/php83/conf.d/xdebug.ini +RUN echo "xdebug.client_port=9003" >> /etc/php83/conf.d/xdebug.ini +RUN echo "xdebug.idekey=VSCODE" >> /etc/php83/conf.d/xdebug.ini diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..faf190b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,35 @@ +{ + "name": "Web Project Dev Container", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "forwardPorts": [8080], + "postCreateCommand": "/start.sh", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/ash", + "php.validate.executablePath": "/usr/bin/php", + "php.debug.listenPort": 9003, + "php.debug.log": true + }, + "extensions": [ + "ms-azuretools.vscode-docker", + "felixfbecker.php-debug", + "bmewburn.vscode-intelephense-client", + "otovo-oss.htmx-tags", + "devsense.phptools-vscode", + "bmewburn.vscode-intelephense-client", + "github.copilot", + "github.copilot-chat", + "anbuselvanrocky.bootstrap5-vscode", + "hansuxdev.bootstrap5-snippets", + "alefragnani.Bookmarks", + "eamodio.gitlens" + ] + } + }, + "remoteUser": "root" + } + \ No newline at end of file diff --git a/.devcontainer/start.sh b/.devcontainer/start.sh new file mode 100644 index 0000000..e32a137 --- /dev/null +++ b/.devcontainer/start.sh @@ -0,0 +1,23 @@ +WORKSPACE_PATH=$( pwd ) +# Escape the path for safe use in sed +ESCAPED_PATH=$(echo "$WORKSPACE_PATH" | sed 's/\//\\\//g') + +echo "[!] Folder is $WORKSPACE_PATH" + +echo ' [+] Starting php' +php-fpm83 + +echo ' [+] Starting Caddy' +sed -i "s|xxxxxxxxxx|$ESCAPED_PATH\/web|g" /etc/caddy/Caddyfile + +cd ${WORKSPACE_PATH}/src +composer install + +echo ' [+] Starting Chrome' +chromedriver --port=4444 & + + +chmod 777 ${WORKSPACE_PATH}/cache +chmod 777 ${WORKSPACE_PATH}/logs + +caddy run --config /etc/caddy/Caddyfile \ No newline at end of file diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 0000000..118e007 --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,59 @@ +name: ci + +on: + push: + tags: + - "v*.*.*" + pull_request: + branches: + - "master" + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + # list of Docker images to use as base name for tags + images: | + hascheksolutions/http2pic + ghcr.io/hascheksolutions/http2pic + # generate Docker tags based on the following events/attributes + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Login to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: docker/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..542060d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003 + } + ] +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..90c0d51 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.3' +services: + http2pic: + image: ghcr.io/hascheksolutions/http2pic:2 + restart: unless-stopped + + environment: + - URL=http://localhost:8080 + ports: + - 8080:80 diff --git a/docker/Caddyfile b/docker/Caddyfile index e1042d5..b4b5002 100644 --- a/docker/Caddyfile +++ b/docker/Caddyfile @@ -1,5 +1,7 @@ :80 { - root * /srv + root * /srv/web php_fastcgi 127.0.0.1:9000 file_server + + try_files {path} {path}/ /index.php?{query} } \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index b3e4ceb..eab82b8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,16 +1,29 @@ -FROM ghcr.io/surnet/alpine-wkhtmltopdf:3.20.3-0.12.6-full +FROM alpine:3.21 # Install PHP and necessary extensions -RUN apk add --no-cache php83 php83-fpm php83-opcache caddy php83-curl +RUN apk add --no-cache curl php83 php83-fpm php83-opcache caddy php83-curl php83-xdebug chromium-chromedriver RUN sed -i 's/nobody/caddy/g' /etc/php83/php-fpm.d/www.conf RUN sed -i 's/E_ALL \& ~E_DEPRECATED \& ~E_STRICT/E_ALL \& ~E_DEPRECATED \& ~E_STRICT \& ~E_NOTICE \& ~E_WARNING/g' /etc/php83/php.ini +# Install additional PHP extensions +RUN apk add --no-cache php83-ctype php83-dom php83-fileinfo php83-gd php83-iconv php83-simplexml php83-xml php83-xmlreader php83-xmlwriter php83-zip php83-phar php83-openssl +RUN curl -sS https://getcomposer.org/installer | /usr/bin/php83 -- --install-dir=/usr/bin --filename=composer + +ADD docker/start.sh /start.sh +RUN chmod +x /start.sh + # Copy the contents of the web/ directory to /srv -COPY web/ /srv +ADD . /srv + +# Cleanup +RUN rm -rf /srv/.git +RUN rm -rf /srv/.github +RUN rm -rf /srv/.vscode + # Copy the Caddyfile to the container COPY docker/Caddyfile /etc/caddy/Caddyfile -# Set the default command to start PHP-FPM and Caddy -CMD ["sh", "-c", "php-fpm83 && caddy run --config /etc/caddy/Caddyfile"] +# Run start script +CMD ["sh", "-c", "/start.sh"] diff --git a/docker/start.sh b/docker/start.sh new file mode 100644 index 0000000..5c28dbf --- /dev/null +++ b/docker/start.sh @@ -0,0 +1,25 @@ + +echo ' [+] Starting php' +php-fpm83 + +cd /srv/src +composer install + +echo ' [+] Starting Chrome' +chromedriver --port=4444 & + + +chmod 777 /srv/cache +chmod 777 /srv/logs + +echo ' [+] Building config' +_buildConfig() { + echo " src/inc/config.inc.php + +caddy run --config /etc/caddy/Caddyfile \ No newline at end of file diff --git a/logs/.gitignore b/logs/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..a725465 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +vendor/ \ No newline at end of file diff --git a/src/composer.json b/src/composer.json new file mode 100644 index 0000000..fd56a2d --- /dev/null +++ b/src/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php-webdriver/webdriver": "^1.15" + } +} diff --git a/src/composer.lock b/src/composer.lock new file mode 100644 index 0000000..88cbbe8 --- /dev/null +++ b/src/composer.lock @@ -0,0 +1,226 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "9c8ecf7ef31de7c4a3608d683b57a301", + "packages": [ + { + "name": "php-webdriver/webdriver", + "version": "1.15.2", + "source": { + "type": "git", + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "998e499b786805568deaf8cbf06f4044f05d91bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/998e499b786805568deaf8cbf06f4044f05d91bf", + "reference": "998e499b786805568deaf8cbf06f4044f05d91bf", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^7.3 || ^8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^5.0 || ^6.0 || ^7.0" + }, + "replace": { + "facebook/webdriver": "*" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.20.0", + "ondram/ci-detector": "^4.0", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Exception/TimeoutException.php" + ], + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", + "keywords": [ + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" + ], + "support": { + "issues": "https://github.com/php-webdriver/php-webdriver/issues", + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.2" + }, + "time": "2024-11-21T15:12:59+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T14:24:19+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/src/config.inc.php b/src/config.inc.php index b9bc6fc..a3c5475 100644 --- a/src/config.inc.php +++ b/src/config.inc.php @@ -1,3 +1,3 @@ renderPagePHANTOMJS(); - - //no? well ok how about WKHTMLToImage? - else if(RENDERINGENGINE=='wkhtmltoimage' && file_exists(WKHTMLTOIMAGEPATH)) - return $this->renderPageWKHTMLTOIMAGE(); - - //you're fucked - else - throw new Exception('No valid rendering engine found'); - } - - - /** - * Render using PhantomJS - **/ - function renderPagePHANTOMJS() - { - $cmd = 'timeout '.$this->params['timeout'].' '.PHANTOMJSPATH; - $cmd.= ' --ignore-ssl-errors=yes --ssl-protocol=any '.__DIR__.'/phantom.js '; - - $cmd.= ($this->params['url']); - $cmd.= ','.($this->params['file']); - $cmd.= ','.$this->params['vp_w']; - $cmd.= ','.$this->params['vp_h']; - $cmd.= ','.$this->params['js']; - - $cmd = escapeshellcmd($cmd); - shell_exec($cmd); - $this->params['cmd'] = $cmd; - - $this->postRender(); - if(DEBUG) - { - $fp = fopen('debug.log', 'a'); - fwrite($fp, $cmd."\n"); - fclose($fp); - } - return $cmd; + return $this->renderPageWKHTMLTOIMAGE(); } /** @@ -214,6 +175,10 @@ class http2pic //add storage path to cmd $cmd.=' '.escapeshellarg($this->params['file']); + + $cmd.=' --wait-for-network-idle'; + + var_dump($cmd); $cmd = escapeshellcmd($cmd); shell_exec($cmd); @@ -241,7 +206,8 @@ class http2pic // resize if necessary if($this->params['resizewidth']) $this->resizeImage($this->params['file']); - + + if(!file_exists($this->params['file'])) exit("Error: File not found"); //print image to user if ($this->params['type'] === 'png') { diff --git a/web/api.php b/web/api.php deleted file mode 100644 index 005a0b5..0000000 --- a/web/api.php +++ /dev/null @@ -1,23 +0,0 @@ -trim($url), - 'type'=>$type, - 'timeout'=>$timeout, - 'viewport'=>$viewport, - 'js'=>$js, - 'resizewidth'=>$resizewidth, - 'cache'=>$cache, - 'onfail'=>$onfail); - -$http2pic = new http2pic($params); -//echo nl2br(print_r($http2pic->debug(),true)); \ No newline at end of file diff --git a/web/index.php b/web/index.php index dc25e4b..ceba72b 100644 --- a/web/index.php +++ b/web/index.php @@ -1,10 +1,14 @@ addArguments(['--headless', '--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage']); + + $capabilities = DesiredCapabilities::chrome(); + $capabilities->setCapability(\Facebook\WebDriver\Chrome\ChromeOptions::CAPABILITY, $options); + + //disable javascript if $js is false + if(!$js) + $capabilities->setCapability('javascriptEnabled', false); + + + + $driver = RemoteWebDriver::create($serverUrl, $capabilities); + + $driver->get($target); + + //hide scroll bars + $driver->executeScript('document.body.style.overflow = "hidden";'); + + //set screenshot size to 1920x1080 + //$driver->manage()->window()->setSize(new \Facebook\WebDriver\WebDriverDimension(1024, 768)); + //if $viewport is set, set window size + if($viewport) + { + $viewport = explode('x',$viewport); + $driver->manage()->window()->setSize(new \Facebook\WebDriver\WebDriverDimension($viewport[0], $viewport[1])); + } + else + { + $driver->manage()->window()->setSize(new \Facebook\WebDriver\WebDriverDimension(1024, 768)); + } + + // take screenshot and save to file + //header for png + header('Content-Type: image/png'); + echo $driver->takeScreenshot(); + + break; case 'api': $url = $_GET['url']; - $type = $_GET['type']; - $timeout = $_GET['timeout']; - $viewport = $_GET['viewport']; - $js = $_GET['js']; - $resizewidth = $_GET['width']; + $type = $_GET['type']; + $timeout = $_GET['timeout']; + $viewport = $_GET['viewport']; + $js = $_GET['js']; + $resizewidth = $_GET['width']; $cache = $_GET['cache']; $onfail = rawurldecode($_GET['onfail']);