One Hat Cyber Team
Your IP:
216.73.216.30
Server IP:
198.54.114.155
Server:
Linux server71.web-hosting.com 4.18.0-513.18.1.lve.el8.x86_64 #1 SMP Thu Feb 22 12:55:50 UTC 2024 x86_64
Server Software:
LiteSpeed
PHP Version:
5.6.40
Create File
|
Create Folder
Execute
Dir :
~
/
proc
/
self
/
root
/
proc
/
thread-self
/
cwd
/
View File Name :
browsershot.tar
src/Exceptions/FileUrlNotAllowed.php 0000644 00000000345 15111214066 0013517 0 ustar 00 <?php namespace Spatie\Browsershot\Exceptions; use Exception; class FileUrlNotAllowed extends Exception { public static function make() { return new static("An URL is not allow to start with file://"); } } src/Exceptions/CouldNotTakeBrowsershot.php 0000644 00000001401 15111214066 0014754 0 ustar 00 <?php namespace Spatie\Browsershot\Exceptions; use Exception; class CouldNotTakeBrowsershot extends Exception { public static function chromeOutputEmpty(string $screenShotPath, string $output, array $command = []) { $command = json_encode($command); $message = <<<CONSOLE For some reason Chrome did not write a file at `{$screenShotPath}`. Command ======= {$command} Output ====== {$output} CONSOLE; return new static($message); } public static function outputFileDidNotHaveAnExtension(string $path) { return new static("The given path `{$path}` did not contain an extension. Please append an extension."); } } src/Exceptions/HtmlIsNotAllowedToContainFile.php 0000644 00000000403 15111214066 0015767 0 ustar 00 <?php namespace Spatie\Browsershot\Exceptions; use Exception; class HtmlIsNotAllowedToContainFile extends Exception { public static function make() { return new static("The specified HTML contains `file://`. This is not allowed."); } } src/Exceptions/FileDoesNotExistException.php 0000644 00000000354 15111214066 0015233 0 ustar 00 <?php namespace Spatie\Browsershot\Exceptions; use Exception; class FileDoesNotExistException extends Exception { public function __construct($file) { parent::__construct("The file `{$file}` does not exist"); } } src/Exceptions/UnsuccessfulResponse.php 0000644 00000000375 15111214066 0014370 0 ustar 00 <?php namespace Spatie\Browsershot\Exceptions; use Exception; class UnsuccessfulResponse extends Exception { public function __construct($url, $code) { parent::__construct("The given url `{$url}` responds with code {$code}"); } } src/Exceptions/ElementNotFound.php 0000644 00000000377 15111214066 0013237 0 ustar 00 <?php namespace Spatie\Browsershot\Exceptions; use Exception; class ElementNotFound extends Exception { public function __construct($selector) { parent::__construct("The given selector `{$selector} did not match any elements"); } } src/Helpers.php 0000644 00000000556 15111214066 0007451 0 ustar 00 <?php namespace Spatie\Browsershot; class Helpers { public static function stringStartsWith($haystack, $needle): bool { $length = strlen($needle); return substr($haystack, 0, $length) === $needle; } public static function stringContains($haystack, $needle): bool { return strpos($haystack, $needle) !== false; } } src/Browsershot.php 0000644 00000065104 15111214066 0010370 0 ustar 00 <?php namespace Spatie\Browsershot; use Spatie\Browsershot\Exceptions\CouldNotTakeBrowsershot; use Spatie\Browsershot\Exceptions\ElementNotFound; use Spatie\Browsershot\Exceptions\FileDoesNotExistException; use Spatie\Browsershot\Exceptions\FileUrlNotAllowed; use Spatie\Browsershot\Exceptions\HtmlIsNotAllowedToContainFile; use Spatie\Browsershot\Exceptions\UnsuccessfulResponse; use Spatie\Image\Image; use Spatie\Image\Manipulations; use Spatie\TemporaryDirectory\TemporaryDirectory; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; /** @mixin \Spatie\Image\Manipulations */ class Browsershot { protected $nodeBinary = null; protected $npmBinary = null; protected $nodeModulePath = null; protected $includePath = '$PATH:/usr/local/bin:/opt/homebrew/bin'; protected $binPath = null; protected $html = ''; protected $noSandbox = false; protected $proxyServer = ''; protected $showBackground = false; protected $showScreenshotBackground = true; protected $scale = null; protected $screenshotType = 'png'; protected $screenshotQuality = null; protected $temporaryHtmlDirectory; protected $timeout = 60; protected $transparentBackground = false; protected $url = ''; protected $postParams = []; protected $additionalOptions = []; protected $temporaryOptionsDirectory; protected $tempPath = ''; protected $writeOptionsToFile = false; protected $chromiumArguments = []; /** @var \Spatie\Image\Manipulations */ protected $imageManipulations; public const POLLING_REQUEST_ANIMATION_FRAME = 'raf'; public const POLLING_MUTATION = 'mutation'; public static function url(string $url): static { return (new static())->setUrl($url); } public static function html(string $html): static { return (new static())->setHtml($html); } public static function htmlFromFilePath(string $filePath): static { return (new static())->setHtmlFromFilePath($filePath); } public function __construct(string $url = '', bool $deviceEmulate = false) { $this->url = $url; $this->imageManipulations = new Manipulations(); if (! $deviceEmulate) { $this->windowSize(800, 600); } } public function setNodeBinary(string $nodeBinary) { $this->nodeBinary = $nodeBinary; return $this; } public function setNpmBinary(string $npmBinary) { $this->npmBinary = $npmBinary; return $this; } public function setIncludePath(string $includePath) { $this->includePath = $includePath; return $this; } public function setBinPath(string $binPath) { $this->binPath = $binPath; return $this; } public function setNodeModulePath(string $nodeModulePath) { $this->nodeModulePath = $nodeModulePath; return $this; } public function setChromePath(string $executablePath) { $this->setOption('executablePath', $executablePath); return $this; } public function setCustomTempPath(string $tempPath) { $this->tempPath = $tempPath; return $this; } public function post(array $postParams = []) { $this->postParams = $postParams; return $this; } public function useCookies(array $cookies, string $domain = null) { if (! count($cookies)) { return $this; } if (is_null($domain)) { $domain = parse_url($this->url)['host']; } $cookies = array_map(function ($value, $name) use ($domain) { return compact('name', 'value', 'domain'); }, $cookies, array_keys($cookies)); if (isset($this->additionalOptions['cookies'])) { $cookies = array_merge($this->additionalOptions['cookies'], $cookies); } $this->setOption('cookies', $cookies); return $this; } public function setExtraHttpHeaders(array $extraHTTPHeaders) { $this->setOption('extraHTTPHeaders', $extraHTTPHeaders); return $this; } public function setExtraNavigationHttpHeaders(array $extraNavigationHTTPHeaders) { $this->setOption('extraNavigationHTTPHeaders', $extraNavigationHTTPHeaders); return $this; } public function authenticate(string $username, string $password) { $this->setOption('authentication', compact('username', 'password')); return $this; } public function click(string $selector, string $button = 'left', int $clickCount = 1, int $delay = 0) { $clicks = $this->additionalOptions['clicks'] ?? []; $clicks[] = compact('selector', 'button', 'clickCount', 'delay'); $this->setOption('clicks', $clicks); return $this; } public function selectOption(string $selector, string $value = '') { $dropdownSelects = $this->additionalOptions['selects'] ?? []; $dropdownSelects[] = compact('selector', 'value'); $this->setOption('selects', $dropdownSelects); return $this; } public function type(string $selector, string $text = '', int $delay = 0) { $types = $this->additionalOptions['types'] ?? []; $types[] = compact('selector', 'text', 'delay'); $this->setOption('types', $types); return $this; } /** * @deprecated This option is no longer supported by modern versions of Puppeteer. */ public function setNetworkIdleTimeout(int $networkIdleTimeout) { $this->setOption('networkIdleTimeout'); return $this; } public function waitUntilNetworkIdle(bool $strict = true) { $this->setOption('waitUntil', $strict ? 'networkidle0' : 'networkidle2'); return $this; } public function waitForFunction(string $function, $polling = self::POLLING_REQUEST_ANIMATION_FRAME, int $timeout = 0) { $this->setOption('functionPolling', $polling); $this->setOption('functionTimeout', $timeout); return $this->setOption('function', $function); } public function waitForSelector(string $selector, array $options = []) { $this->setOption('waitForSelector', $selector); if (! empty($options)) { $this->setOption('waitForSelectorOptions', $options); } return $this; } public function setUrl(string $url) { if (Helpers::stringStartsWith(strtolower($url), 'file://')) { throw FileUrlNotAllowed::make(); } $this->url = $url; $this->html = ''; return $this; } public function setHtmlFromFilePath(string $filePath): self { if (false === file_exists($filePath)) { throw new FileDoesNotExistException($filePath); } $this->url = 'file://'.$filePath; $this->html = ''; return $this; } public function setProxyServer(string $proxyServer) { $this->proxyServer = $proxyServer; return $this; } public function setHtml(string $html) { if (Helpers::stringContains(strtolower($html), 'file://')) { throw HtmlIsNotAllowedToContainFile::make(); } $this->html = $html; $this->url = ''; $this->hideBrowserHeaderAndFooter(); return $this; } public function clip(int $x, int $y, int $width, int $height) { return $this->setOption('clip', compact('x', 'y', 'width', 'height')); } public function preventUnsuccessfulResponse(bool $preventUnsuccessfulResponse = true) { return $this->setOption('preventUnsuccessfulResponse', $preventUnsuccessfulResponse); } public function select($selector, $index = 0) { $this->selectorIndex($index); return $this->setOption('selector', $selector); } public function selectorIndex(int $index) { return $this->setOption('selectorIndex', $index); } public function showBrowserHeaderAndFooter() { return $this->setOption('displayHeaderFooter', true); } public function hideBrowserHeaderAndFooter() { return $this->setOption('displayHeaderFooter', false); } public function hideHeader() { return $this->headerHtml('<p></p>'); } public function hideFooter() { return $this->footerHtml('<p></p>'); } public function headerHtml(string $html) { return $this->setOption('headerTemplate', $html); } public function footerHtml(string $html) { return $this->setOption('footerTemplate', $html); } public function deviceScaleFactor(int $deviceScaleFactor) { // Google Chrome currently supports values of 1, 2, and 3. return $this->setOption('viewport.deviceScaleFactor', max(1, min(3, $deviceScaleFactor))); } public function fullPage() { return $this->setOption('fullPage', true); } public function showBackground() { $this->showBackground = true; $this->showScreenshotBackground = true; return $this; } public function hideBackground() { $this->showBackground = false; $this->showScreenshotBackground = false; return $this; } public function transparentBackground() { $this->transparentBackground = true; return $this; } public function setScreenshotType(string $type, int $quality = null) { $this->screenshotType = $type; if (! is_null($quality)) { $this->screenshotQuality = $quality; } return $this; } public function ignoreHttpsErrors() { return $this->setOption('ignoreHttpsErrors', true); } public function mobile(bool $mobile = true) { return $this->setOption('viewport.isMobile', $mobile); } public function touch(bool $touch = true) { return $this->setOption('viewport.hasTouch', $touch); } public function landscape(bool $landscape = true) { return $this->setOption('landscape', $landscape); } public function margins(float $top, float $right, float $bottom, float $left, string $unit = 'mm') { return $this->setOption('margin', [ 'top' => $top.$unit, 'right' => $right.$unit, 'bottom' => $bottom.$unit, 'left' => $left.$unit, ]); } public function noSandbox() { $this->noSandbox = true; return $this; } public function dismissDialogs() { return $this->setOption('dismissDialogs', true); } public function disableJavascript() { return $this->setOption('disableJavascript', true); } public function disableImages() { return $this->setOption('disableImages', true); } public function blockUrls($array) { return $this->setOption('blockUrls', $array); } public function blockDomains($array) { return $this->setOption('blockDomains', $array); } public function pages(string $pages) { return $this->setOption('pageRanges', $pages); } public function paperSize(float $width, float $height, string $unit = 'mm') { return $this ->setOption('width', $width.$unit) ->setOption('height', $height.$unit); } // paper format public function format(string $format) { return $this->setOption('format', $format); } public function scale(float $scale) { $this->scale = $scale; return $this; } public function timeout(int $timeout) { $this->timeout = $timeout; $this->setOption('timeout', $timeout * 1000); return $this; } public function userAgent(string $userAgent) { $this->setOption('userAgent', $userAgent); return $this; } public function device(string $device) { $this->setOption('device', $device); return $this; } public function emulateMedia(?string $media) { $this->setOption('emulateMedia', $media); return $this; } public function windowSize(int $width, int $height) { return $this ->setOption('viewport.width', $width) ->setOption('viewport.height', $height); } public function setDelay(int $delayInMilliseconds) { return $this->setOption('delay', $delayInMilliseconds); } public function delay(int $delayInMilliseconds) { return $this->setDelay($delayInMilliseconds); } public function setUserDataDir(string $absolutePath) { return $this->addChromiumArguments(['user-data-dir' => $absolutePath]); } public function userDataDir(string $absolutePath) { return $this->setUserDataDir($absolutePath); } public function writeOptionsToFile() { $this->writeOptionsToFile = true; return $this; } public function setOption($key, $value) { $this->arraySet($this->additionalOptions, $key, $value); return $this; } public function addChromiumArguments(array $arguments) { foreach ($arguments as $argument => $value) { if (is_numeric($argument)) { $this->chromiumArguments[] = "--$value"; } else { $this->chromiumArguments[] = "--$argument=$value"; } } return $this; } public function __call($name, $arguments) { $this->imageManipulations->$name(...$arguments); return $this; } public function save(string $targetPath) { $extension = strtolower(pathinfo($targetPath, PATHINFO_EXTENSION)); if ($extension === '') { throw CouldNotTakeBrowsershot::outputFileDidNotHaveAnExtension($targetPath); } if ($extension === 'pdf') { return $this->savePdf($targetPath); } $command = $this->createScreenshotCommand($targetPath); $output = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); if (! file_exists($targetPath)) { throw CouldNotTakeBrowsershot::chromeOutputEmpty($targetPath, $output, $command); } if (! $this->imageManipulations->isEmpty()) { $this->applyManipulations($targetPath); } } public function bodyHtml(): string { $command = $this->createBodyHtmlCommand(); $html = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return $html; } public function base64Screenshot(): string { $command = $this->createScreenshotCommand(); $encodedImage = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return $encodedImage; } public function screenshot(): string { if ($this->imageManipulations->isEmpty()) { $command = $this->createScreenshotCommand(); $encodedImage = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return base64_decode($encodedImage); } $temporaryDirectory = (new TemporaryDirectory($this->tempPath))->create(); $this->save($temporaryDirectory->path('screenshot.png')); $screenshot = file_get_contents($temporaryDirectory->path('screenshot.png')); $temporaryDirectory->delete(); return $screenshot; } public function pdf(): string { $command = $this->createPdfCommand(); $encodedPdf = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return base64_decode($encodedPdf); } public function savePdf(string $targetPath) { $command = $this->createPdfCommand($targetPath); $output = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); if (! file_exists($targetPath)) { throw CouldNotTakeBrowsershot::chromeOutputEmpty($targetPath, $output); } } public function base64pdf(): string { $command = $this->createPdfCommand(); $encodedPdf = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return $encodedPdf; } public function evaluate(string $pageFunction): string { $command = $this->createEvaluateCommand($pageFunction); $evaluation = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return $evaluation; } public function triggeredRequests(): array { $command = $this->createTriggeredRequestsListCommand(); $requests = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return json_decode($requests, true); } public function redirectHistory(): array { $command = $this->createRedirectHistoryCommand(); return json_decode($this->callBrowser($command), true); } /** * @return array{type: string, message: string, location:array} */ public function consoleMessages(): array { $command = $this->createConsoleMessagesCommand(); $messages = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return json_decode($messages, true); } public function failedRequests(): array { $command = $this->createFailedRequestsCommand(); $requests = $this->callBrowser($command); $this->cleanupTemporaryHtmlFile(); return json_decode($requests, true); } public function applyManipulations(string $imagePath) { Image::load($imagePath) ->manipulate($this->imageManipulations) ->save(); } public function createBodyHtmlCommand(): array { $url = $this->getFinalContentsUrl(); return $this->createCommand($url, 'content'); } public function createScreenshotCommand($targetPath = null): array { $url = $this->getFinalContentsUrl(); $options = [ 'type' => $this->screenshotType, ]; if ($targetPath) { $options['path'] = $targetPath; } if ($this->screenshotQuality) { $options['quality'] = $this->screenshotQuality; } $command = $this->createCommand($url, 'screenshot', $options); if (! $this->showScreenshotBackground) { $command['options']['omitBackground'] = true; } return $command; } public function createPdfCommand($targetPath = null): array { $url = $this->getFinalContentsUrl(); $options = []; if ($targetPath) { $options['path'] = $targetPath; } $command = $this->createCommand($url, 'pdf', $options); if ($this->showBackground) { $command['options']['printBackground'] = true; } if ($this->transparentBackground) { $command['options']['omitBackground'] = true; } if ($this->scale) { $command['options']['scale'] = $this->scale; } return $command; } public function createEvaluateCommand(string $pageFunction): array { $url = $this->getFinalContentsUrl(); $options = [ 'pageFunction' => $pageFunction, ]; return $this->createCommand($url, 'evaluate', $options); } public function createTriggeredRequestsListCommand(): array { $url = $this->html ? $this->createTemporaryHtmlFile() : $this->url; return $this->createCommand($url, 'requestsList'); } public function createRedirectHistoryCommand(): array { $url = $this->html ? $this->createTemporaryHtmlFile() : $this->url; return $this->createCommand($url, 'redirectHistory'); } public function createConsoleMessagesCommand(): array { $url = $this->html ? $this->createTemporaryHtmlFile() : $this->url; return $this->createCommand($url, 'consoleMessages'); } public function createFailedRequestsCommand(): array { $url = $this->html ? $this->createTemporaryHtmlFile() : $this->url; return $this->createCommand($url, 'failedRequests'); } public function setRemoteInstance(string $ip = '127.0.0.1', int $port = 9222): self { // assuring that ip and port does actually contains a value if ($ip && $port) { $this->setOption('remoteInstanceUrl', 'http://'.$ip.':'.$port); } return $this; } public function setWSEndpoint(string $endpoint): self { if (! is_null($endpoint)) { $this->setOption('browserWSEndpoint', $endpoint); } return $this; } public function usePipe(): self { $this->setOption('pipe', true); return $this; } public function setEnvironmentOptions(array $options = []): self { return $this->setOption('env', $options); } public function setContentUrl(string $contentUrl): self { return $this->html ? $this->setOption('contentUrl', $contentUrl) : $this; } protected function getOptionArgs(): array { $args = $this->chromiumArguments; if ($this->noSandbox) { $args[] = '--no-sandbox'; } if ($this->proxyServer) { $args[] = '--proxy-server='.$this->proxyServer; } return $args; } protected function createCommand(string $url, string $action, array $options = []): array { $command = compact('url', 'action', 'options'); $command['options']['args'] = $this->getOptionArgs(); if (! empty($this->postParams)) { $command['postParams'] = $this->postParams; } if (! empty($this->additionalOptions)) { $command['options'] = array_merge_recursive($command['options'], $this->additionalOptions); } return $command; } protected function createTemporaryHtmlFile(): string { $this->temporaryHtmlDirectory = (new TemporaryDirectory($this->tempPath))->create(); file_put_contents($temporaryHtmlFile = $this->temporaryHtmlDirectory->path('index.html'), $this->html); return "file://{$temporaryHtmlFile}"; } protected function cleanupTemporaryHtmlFile() { if ($this->temporaryHtmlDirectory) { $this->temporaryHtmlDirectory->delete(); } } protected function createTemporaryOptionsFile(string $command): string { $this->temporaryOptionsDirectory = (new TemporaryDirectory($this->tempPath))->create(); file_put_contents($temporaryOptionsFile = $this->temporaryOptionsDirectory->path('command.js'), $command); return "file://{$temporaryOptionsFile}"; } protected function cleanupTemporaryOptionsFile() { if ($this->temporaryOptionsDirectory) { $this->temporaryOptionsDirectory->delete(); } } protected function callBrowser(array $command): string { $fullCommand = $this->getFullCommand($command); $process = $this->isWindows() ? new Process($fullCommand) : Process::fromShellCommandline($fullCommand); $process->setTimeout($this->timeout); $process->run(); if ($process->isSuccessful()) { return rtrim($process->getOutput()); } $this->cleanupTemporaryOptionsFile(); $process->clearOutput(); $exitCode = $process->getExitCode(); if ($exitCode === 3) { throw new UnsuccessfulResponse($this->url, $process->getErrorOutput()); } if ($exitCode === 2) { throw new ElementNotFound($this->additionalOptions['selector']); } throw new ProcessFailedException($process); } protected function getFullCommand(array $command) { $nodeBinary = $this->nodeBinary ?: 'node'; $binPath = $this->binPath ?: __DIR__.'/../bin/browser.cjs'; $optionsCommand = $this->getOptionsCommand(json_encode($command)); if ($this->isWindows()) { // on Windows we will let Symfony/process handle the command escaping // by passing an array to the process instance return [ $nodeBinary, $binPath, $optionsCommand, ]; } $setIncludePathCommand = "PATH={$this->includePath}"; $setNodePathCommand = $this->getNodePathCommand($nodeBinary); return $setIncludePathCommand.' ' .$setNodePathCommand.' ' .$nodeBinary.' ' .escapeshellarg($binPath).' ' .$optionsCommand; } protected function getNodePathCommand(string $nodeBinary): string { if ($this->nodeModulePath) { return "NODE_PATH='{$this->nodeModulePath}'"; } if ($this->npmBinary) { return "NODE_PATH=`{$nodeBinary} {$this->npmBinary} root -g`"; } return 'NODE_PATH=`npm root -g`'; } protected function getOptionsCommand(string $command): string { if ($this->writeOptionsToFile) { $temporaryOptionsFile = $this->createTemporaryOptionsFile($command); $command = "-f {$temporaryOptionsFile}"; } if ($this->isWindows()) { return $command; } return escapeshellarg($command); } protected function arraySet(array &$array, string $key, $value): array { if (is_null($key)) { return $array = $value; } $keys = explode('.', $key); while (count($keys) > 1) { $key = array_shift($keys); // If the key doesn't exist at this depth, we will just create an empty array // to hold the next value, allowing us to create the arrays to hold final // values at the correct depth. Then we'll keep digging into the array. if (! isset($array[$key]) || ! is_array($array[$key])) { $array[$key] = []; } $array = &$array[$key]; } $array[array_shift($keys)] = $value; return $array; } public function initialPageNumber(int $initialPage = 1) { return $this ->setOption('initialPageNumber', ($initialPage - 1)) ->pages($initialPage.'-'); } private function isWindows() { return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; } private function getFinalContentsUrl(): string { $url = $this->html ? $this->createTemporaryHtmlFile() : $this->url; return $url; } public function newHeadless(): self { return $this->setOption('newHeadless', true); } } README.md 0000644 00000007767 15111214066 0006041 0 ustar 00 <p align="center"><img src="/art/socialcard.png" alt="Social Card of Spatie's Browsershot"></p> # Convert a webpage to an image or pdf using headless Chrome [](https://github.com/spatie/browsershot/releases) [](LICENSE.md) [](https://github.com/spatie/browsershot/actions) [](https://packagist.org/packages/spatie/browsershot) The package can convert a webpage to an image or pdf. The conversion is done behind the scenes by [Puppeteer](https://github.com/GoogleChrome/puppeteer) which controls a headless version of Google Chrome. Here's a quick example: ```php use Spatie\Browsershot\Browsershot; // an image will be saved Browsershot::url('https://example.com')->save($pathToImage); ``` It will save a pdf if the path passed to the `save` method has a `pdf` extension. ```php // a pdf will be saved Browsershot::url('https://example.com')->save('example.pdf'); ``` You can also use an arbitrary html input, simply replace the `url` method with `html`: ```php Browsershot::html('<h1>Hello world!!</h1>')->save('example.pdf'); ``` If your HTML input is already in a file locally use the : ```php Browsershot::htmlFromFilePath('/local/path/to/file.html')->save('example.pdf'); ``` Browsershot also can get the body of an html page after JavaScript has been executed: ```php Browsershot::url('https://example.com')->bodyHtml(); // returns the html of the body ``` If you wish to retrieve an array list with all of the requests that the page triggered you can do so: ```php $requests = Browsershot::url('https://example.com') ->triggeredRequests(); foreach ($requests as $request) { $url = $request['url']; //https://example.com/ } ``` To use Chrome's new [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome) pass the `newHeadless` method: ```php Browsershot::url('https://example.com')->newHeadless()->save($pathToImage); ``` ## Support us Learn how to create a package like this one, by watching our premium video course: [](https://laravelpackage.training) We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). ## Documentation All documentation is available [on our documentation site](https://spatie.be/docs/browsershot). ## Contributing Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. ## Security If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. ## Alternatives If you're not able to install Node and Puppeteer, take a look at [v2 of browsershot](https://github.com/spatie/browsershot/tree/2.4.1), which uses Chrome headless CLI to take a screenshot. `v2` is not maintained anymore, but should work pretty well. If using headless Chrome does not work for you take a look at at `v1` of this package which uses the abandoned `PhantomJS` binary. ## Credits - [Freek Van der Herten](https://github.com/freekmurze) - [All Contributors](../../contributors) And a special thanks to [Caneco](https://twitter.com/caneco) for the logo ✨ ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. bin/browser.cjs 0000644 00000033261 15111214066 0007502 0 ustar 00 const fs = require('fs'); const URL = require('url').URL; const URLParse = require('url').parse; const [, , ...args] = process.argv; /** * There are two ways for Browsershot to communicate with puppeteer: * - By giving a options JSON dump as an argument * - Or by providing a temporary file with the options JSON dump, * the path to this file is then given as an argument with the flag -f */ const request = args[0].startsWith('-f ') ? JSON.parse(fs.readFileSync(new URL(args[0].substring(3)))) : JSON.parse(args[0]); const requestsList = []; const redirectHistory = []; const consoleMessages = []; const failedRequests = []; const getOutput = async (page, request) => { let output; if (request.action == 'requestsList') { output = JSON.stringify(requestsList); return output; } if (request.action == 'redirectHistory') { output = JSON.stringify(redirectHistory); return output; } if (request.action == 'consoleMessages') { output = JSON.stringify(consoleMessages); return output; } if (request.action == 'failedRequests') { output = JSON.stringify(failedRequests); return output; } if (request.action == 'evaluate') { output = await page.evaluate(request.options.pageFunction); return output; } output = await page[request.action](request.options); return output.toString('base64'); }; const callChrome = async pup => { let browser; let page; let output; let remoteInstance; const puppet = (pup || require('puppeteer')); try { if (request.options.remoteInstanceUrl || request.options.browserWSEndpoint ) { // default options let options = { ignoreHTTPSErrors: request.options.ignoreHttpsErrors }; // choose only one method to connect to the browser instance if ( request.options.remoteInstanceUrl ) { options.browserURL = request.options.remoteInstanceUrl; } else if ( request.options.browserWSEndpoint ) { options.browserWSEndpoint = request.options.browserWSEndpoint; } try { browser = await puppet.connect( options ); remoteInstance = true; } catch (exception) { /** does nothing. fallbacks to launching a chromium instance */} } if (!browser) { browser = await puppet.launch({ headless: request.options.newHeadless ? 'new' : true, ignoreHTTPSErrors: request.options.ignoreHttpsErrors, executablePath: request.options.executablePath, args: request.options.args || [], pipe: request.options.pipe || false, env: { ...(request.options.env || {}), ...process.env }, }); } page = await browser.newPage(); if (request.options && request.options.disableJavascript) { await page.setJavaScriptEnabled(false); } await page.setRequestInterception(true); const contentUrl = request.options.contentUrl; const parsedContentUrl = contentUrl ? contentUrl.replace(/\/$/, "") : undefined; let pageContent; if (contentUrl) { pageContent = fs.readFileSync(request.url.replace('file://', '')); request.url = contentUrl; } page.on('console', message => consoleMessages.push({ type: message.type(), message: message.text(), location: message.location() })); page.on('response', function (response) { if (response.request().isNavigationRequest() && response.request().frame().parentFrame() === null) { redirectHistory.push({ url: response.request().url(), status: response.status(), reason: response.statusText(), headers: response.headers() }) } if (response.status() >= 200 && response.status() <= 399) { return; } failedRequests.push({ status: response.status(), url: response.url(), }); }) page.on('request', interceptedRequest => { var headers = interceptedRequest.headers(); requestsList.push({ url: interceptedRequest.url(), }); if (request.options && request.options.disableImages) { if (interceptedRequest.resourceType() === 'image') { interceptedRequest.abort(); return; } } if (request.options && request.options.blockDomains) { const hostname = URLParse(interceptedRequest.url()).hostname; if (request.options.blockDomains.includes(hostname)) { interceptedRequest.abort(); return; } } if (request.options && request.options.blockUrls) { for (const element of request.options.blockUrls) { if (interceptedRequest.url().indexOf(element) >= 0) { interceptedRequest.abort(); return; } } } if (request.options && request.options.extraNavigationHTTPHeaders) { // Do nothing in case of non-navigation requests. if (interceptedRequest.isNavigationRequest()) { headers = Object.assign({}, headers, request.options.extraNavigationHTTPHeaders); } } if (pageContent) { const interceptedUrl = interceptedRequest.url().replace(/\/$/, ""); // if content url matches the intercepted request url, will return the content fetched from the local file system if (interceptedUrl === parsedContentUrl) { interceptedRequest.respond({ headers, body: pageContent, }); return; } } if (request.postParams) { const postParamsArray = request.postParams; const queryString = Object.keys(postParamsArray) .map(key => `${key}=${postParamsArray[key]}`) .join('&'); interceptedRequest.continue({ method: "POST", postData: queryString, headers: { ...interceptedRequest.headers(), "Content-Type": "application/x-www-form-urlencoded" } }); return; } interceptedRequest.continue({ headers }); }); if (request.options && request.options.dismissDialogs) { page.on('dialog', async dialog => { await dialog.dismiss(); }); } if (request.options && request.options.userAgent) { await page.setUserAgent(request.options.userAgent); } if (request.options && request.options.device) { const devices = puppet.devices; const device = devices[request.options.device]; await page.emulate(device); } if (request.options && request.options.emulateMedia) { await page.emulateMediaType(request.options.emulateMedia); } if (request.options && request.options.viewport) { await page.setViewport(request.options.viewport); } if (request.options && request.options.extraHTTPHeaders) { await page.setExtraHTTPHeaders(request.options.extraHTTPHeaders); } if (request.options && request.options.authentication) { await page.authenticate(request.options.authentication); } if (request.options && request.options.cookies) { await page.setCookie(...request.options.cookies); } if (request.options && request.options.timeout) { await page.setDefaultNavigationTimeout(request.options.timeout); } const requestOptions = {}; if (request.options && request.options.networkIdleTimeout) { requestOptions.waitUntil = 'networkidle'; requestOptions.networkIdleTimeout = request.options.networkIdleTimeout; } else if (request.options && request.options.waitUntil) { requestOptions.waitUntil = request.options.waitUntil; } const response = await page.goto(request.url, requestOptions); if (request.options.preventUnsuccessfulResponse) { const status = response.status() if (status >= 400 && status < 600) { throw {type: "UnsuccessfulResponse", status}; } } if (request.options && request.options.disableImages) { await page.evaluate(() => { let images = document.getElementsByTagName('img'); while (images.length > 0) { images[0].parentNode.removeChild(images[0]); } }); } if (request.options && request.options.types) { for (let i = 0, len = request.options.types.length; i < len; i++) { let typeOptions = request.options.types[i]; await page.type(typeOptions.selector, typeOptions.text, { 'delay': typeOptions.delay, }); } } if (request.options && request.options.selects) { for (let i = 0, len = request.options.selects.length; i < len; i++) { let selectOptions = request.options.selects[i]; await page.select(selectOptions.selector, selectOptions.value); } } if (request.options && request.options.clicks) { for (let i = 0, len = request.options.clicks.length; i < len; i++) { let clickOptions = request.options.clicks[i]; await page.click(clickOptions.selector, { 'button': clickOptions.button, 'clickCount': clickOptions.clickCount, 'delay': clickOptions.delay, }); } } if (request.options && request.options.addStyleTag) { await page.addStyleTag(JSON.parse(request.options.addStyleTag)); } if (request.options && request.options.addScriptTag) { await page.addScriptTag(JSON.parse(request.options.addScriptTag)); } if (request.options.delay) { await page.waitForTimeout(request.options.delay); } if (request.options.initialPageNumber) { await page.evaluate((initialPageNumber) => { window.pageStart = initialPageNumber; const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = '.empty-page { page-break-after: always; visibility: hidden; }'; document.getElementsByTagName('head')[0].appendChild(style); const emptyPages = Array.from({length: window.pageStart}).map(() => { const emptyPage = document.createElement('div'); emptyPage.className = "empty-page"; emptyPage.textContent = "empty"; return emptyPage; }); document.body.prepend(...emptyPages); }, request.options.initialPageNumber); } if (request.options.selector) { var element; const index = request.options.selectorIndex || 0; if(index){ element = await page.$$(request.options.selector); if(!element.length || typeof element[index] === 'undefined'){ element = null; }else{ element = element[index]; } }else{ element = await page.$(request.options.selector); } if (element === null) { throw {type: 'ElementNotFound'}; } request.options.clip = await element.boundingBox(); } if (request.options.function) { let functionOptions = { polling: request.options.functionPolling, timeout: request.options.functionTimeout || request.options.timeout }; await page.waitForFunction(request.options.function, functionOptions); } if (request.options.waitForSelector) { await page.waitForSelector(request.options.waitForSelector, request.options.waitForSelectorOptions ?? undefined); } output = await getOutput(page, request); if (!request.options.path) { console.log(output); } if (remoteInstance && page) { await page.close(); } await remoteInstance ? browser.disconnect() : browser.close(); } catch (exception) { if (browser) { if (remoteInstance && page) { await page.close(); } await remoteInstance ? browser.disconnect() : browser.close(); } if (exception.type === 'UnsuccessfulResponse') { console.error(exception.status) process.exit(3); } console.error(exception); if (exception.type === 'ElementNotFound') { process.exit(2); } process.exit(1); } }; if (require.main === module) { callChrome(); } exports.callChrome = callChrome; composer.json 0000644 00000002432 15111214066 0007264 0 ustar 00 { "name": "spatie/browsershot", "description": "Convert a webpage to an image or pdf using headless Chrome", "homepage": "https://github.com/spatie/browsershot", "keywords": [ "convert", "webpage", "image", "pdf", "screenshot", "chrome", "headless", "puppeteer" ], "license": "MIT", "authors": [ { "name": "Freek Van der Herten", "email": "freek@spatie.be", "homepage": "https://github.com/freekmurze", "role": "Developer" } ], "require": { "php": "^8.0", "spatie/image": "^1.5.3|^2.0", "spatie/temporary-directory": "^1.1|^2.0", "symfony/process": "^4.2|^5.0|^6.0", "ext-json": "*" }, "require-dev": { "spatie/phpunit-snapshot-assertions": "^4.2.3", "pestphp/pest": "^1.20" }, "autoload": { "psr-4": { "Spatie\\Browsershot\\": "src" } }, "autoload-dev": { "psr-4": { "Spatie\\Browsershot\\Test\\": "tests" } }, "scripts": { "test": "vendor/bin/pest" }, "config": { "sort-packages": true, "allow-plugins": { "pestphp/pest-plugin": true } } } LICENSE.md 0000644 00000002102 15111214066 0006140 0 ustar 00 The MIT License (MIT) Copyright (c) Spatie bvba <info@spatie.be> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.