Created
May 12, 2013 18:33
-
-
Save fprochazka/5564471 to your computer and use it in GitHub Desktop.
WkhtmlToPDF abstraction that can run either localy or remotely, or even can be used as a hosted service.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * This file is part of the Kdyby (http://www.kdyby.org) | |
| * | |
| * Copyright (c) 2008, 2012 Filip Procházka (filip@prochazka.su) | |
| * | |
| * For the full copyright and license information, please view the file license.txt that was distributed with this source code. | |
| */ | |
| namespace Pdf\DI; | |
| use Kdyby; | |
| use Kdyby\Curl\Request; | |
| use Kdyby\Curl\CurlException; | |
| use Nette; | |
| use Nette\Http\Url; | |
| use Nette\Diagnostics\Debugger; | |
| use Pdf\InvalidStateException; | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| * | |
| * @property \Nette\Http\Url $codeUrl | |
| * @property \Nette\Http\Url $pingUrl | |
| */ | |
| class Configuration extends Nette\Object | |
| { | |
| /** | |
| * @var string | |
| */ | |
| public $provider; | |
| /** | |
| * @var string | |
| */ | |
| public $apiKey; | |
| /** | |
| * @var string | |
| */ | |
| public $tempDir; | |
| /** | |
| * @return \Nette\Http\Url | |
| */ | |
| public function getCodeUrl() | |
| { | |
| return new Url($this->provider . '/code'); | |
| } | |
| /** | |
| * @return \Nette\Http\Url | |
| */ | |
| protected function getPingUrl() | |
| { | |
| return new Url($this->provider . '/ping'); | |
| } | |
| /** | |
| * @return bool | |
| */ | |
| public function testConnection() | |
| { | |
| try { | |
| $request = new Request($this->getPingUrl()); | |
| $request->headers['X-ApiKey'] = $this->apiKey; | |
| $response = $request->get(); | |
| if ($response->getResponse() !== 'pong') { | |
| throw new InvalidStateException($response->getResponse(), $response->headers['Status-Code']); | |
| } | |
| return TRUE; | |
| } catch (CurlException $e) { | |
| Debugger::log($e, 'curl'); | |
| return FALSE; | |
| } catch (InvalidStateException $e) { | |
| Debugger::log($e, 'Pdf'); | |
| return FALSE; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Pdf; | |
| use Kdyby; | |
| use Nette; | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| */ | |
| interface Exception | |
| { | |
| } | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| */ | |
| class IOException extends \RuntimeException implements Exception | |
| { | |
| } | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| */ | |
| class InvalidArgumentException extends \InvalidArgumentException implements Exception | |
| { | |
| } | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| */ | |
| class InvalidStateException extends \RuntimeException implements Exception | |
| { | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Pdf; | |
| use Kdyby; | |
| use Nette; | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| */ | |
| interface IGenerator | |
| { | |
| /** | |
| * @param string $html | |
| * @return Nette\Application\IResponse | |
| */ | |
| function render($html); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * This file is part of the Kdyby (http://www.kdyby.org) | |
| * | |
| * Copyright (c) 2008, 2012 Filip Procházka (filip@prochazka.su) | |
| * | |
| * For the full copyright and license information, please view the file license.txt that was distributed with this source code. | |
| */ | |
| namespace Pdf\DI; | |
| use Kdyby; | |
| use Nette; | |
| use Nette\Utils\Validators; | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| */ | |
| class PdfExtension extends Nette\Config\CompilerExtension | |
| { | |
| /** | |
| * @var array | |
| */ | |
| public $defaults = array( | |
| 'provider' => 'http://pdf.kdyby.org', | |
| 'apiKey' => NULL, | |
| ); | |
| public function loadConfiguration() | |
| { | |
| $builder = $this->getContainerBuilder(); | |
| $config = $this->getConfig($this->defaults); | |
| $builder->addDefinition($this->prefix('config')) | |
| ->setClass('Pdf\DI\Configuration') | |
| ->addSetup('$tempDir', array($builder->expand('%tempDir%/pdf'))) | |
| ->addSetup('$provider', array($config['provider'])) | |
| ->addSetup('$apiKey', array($config['apiKey'])); | |
| $generator = $builder->addDefinition($this->prefix('generator')) | |
| ->setClass('Pdf\IGenerator'); | |
| if (!empty($config['apiKey'])) { | |
| $generator->setFactory('Pdf\RemoteGenerator'); | |
| } else { | |
| $generator->setFactory('Pdf\WkhtmltopdfGenerator'); | |
| } | |
| } | |
| public function beforeCompile() | |
| { | |
| $config = $this->getConfig($this->defaults); | |
| if (!empty($config['apiKey'])) { | |
| $configuration = new Configuration(); | |
| $configuration->apiKey = $config['apiKey']; | |
| $configuration->provider = $config['provider']; | |
| if ($configuration->testConnection() === FALSE) { | |
| trigger_error("The Pdf generator is badly configured or components are missing. Please check log.", E_USER_WARNING); | |
| } | |
| } | |
| } | |
| /** | |
| * @param \Nette\Config\Configurator $configurator | |
| */ | |
| public static function register(Nette\Config\Configurator $configurator) | |
| { | |
| $configurator->onCompile[] = function ($config, Nette\Config\Compiler $compiler) { | |
| $compiler->addExtension('pdfGenerator', new PdfExtension()); | |
| }; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * This file is part of the Kdyby (http://www.kdyby.org) | |
| * | |
| * Copyright (c) 2008, 2012 Filip Procházka (filip@prochazka.su) | |
| * | |
| * For the full copyright and license information, please view the file license.txt that was distributed with this source code. | |
| */ | |
| namespace Pdf; | |
| use Kdyby; | |
| use Nette; | |
| use Kdyby\Curl\CurlSender; | |
| use Kdyby\Curl\Request; | |
| use Nette\Application\Responses\TextResponse; | |
| use Nette\Diagnostics\Debugger; | |
| use Nette\Utils\Json; | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| */ | |
| class RemoteGenerator extends Nette\Object implements IGenerator | |
| { | |
| /** | |
| * @var \Kdyby\QrEncode\DI\Configuration | |
| */ | |
| private $config; | |
| /** | |
| * @var \Kdyby\Curl\CurlSender | |
| */ | |
| private $curlSender; | |
| /** | |
| * @param \Kdyby\QrEncode\DI\Configuration $config | |
| * @param \Kdyby\Curl\CurlSender $curlSender | |
| */ | |
| public function __construct(DI\Configuration $config, CurlSender $curlSender = NULL) | |
| { | |
| $this->config = $config; | |
| $this->curlSender = $curlSender ?: new CurlSender(); | |
| } | |
| /** | |
| * @param string $html | |
| * @return \Nette\Application\IResponse|\Nette\Application\Responses\TextResponse | |
| * @throws InvalidStateException | |
| */ | |
| public function render($html) | |
| { | |
| $request = new Request($this->config->getCodeUrl()); | |
| $request->headers['X-ApiKey'] = $this->config->apiKey; | |
| $request->setTimeout(45); | |
| $request->setSender($this->curlSender); | |
| try { | |
| $response = $request->post(array('body' => (string) $html)); | |
| if ($response->headers['Content-Type'] === 'application/pdf') { | |
| return new TextResponse($response->getResponse()); | |
| } | |
| $json = Json::decode($response->getResponse(), Json::FORCE_ARRAY); | |
| throw new InvalidStateException($json['error']); | |
| } catch (Kdyby\Curl\Exception $e) { | |
| throw new InvalidStateException($e->getMessage(), $e->getCode(), $e); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Pdf; | |
| use DOMDocument; | |
| use Kdyby; | |
| use Nette\Utils\Json; | |
| use Nette\Utils\Strings; | |
| use Nette; | |
| use Wkhtmltopdf\Document; | |
| /** | |
| * @author Filip Procházka <filip@prochazka.su> | |
| */ | |
| class WkhtmltopdfGenerator extends Nette\Object implements IGenerator | |
| { | |
| /** | |
| * @var string | |
| */ | |
| private $tempDir; | |
| /** | |
| * @param string $tempDir | |
| */ | |
| public function __construct(DI\Configuration $config) | |
| { | |
| if (!is_dir($config->tempDir)) { | |
| umask(0); | |
| mkdir($config->tempDir, 0777); | |
| } | |
| $this->tempDir = $config->tempDir; | |
| } | |
| /** | |
| * @param string $html | |
| * @return \Wkhtmltopdf\Document|\Nette\Application\IResponse | |
| */ | |
| public function render($html) | |
| { | |
| $pdf = new Document($this->tempDir); | |
| $pdf->addHtml($html = (string) $html); | |
| $this->loadOptionsFromBody($pdf, $html); | |
| return $pdf; | |
| } | |
| /** | |
| * @param \Wkhtmltopdf\Document|\Wkhtmltopdf\PageMeta $pdf | |
| * @param string $html | |
| * @throws \Nette\MemberAccessException | |
| */ | |
| private function loadOptionsFromBody($pdf, $html) | |
| { | |
| $dom = new DomDocument('1.0', 'UTF-8'); | |
| $dom->resolveExternals = FALSE; | |
| $dom->validateOnParse = FALSE; | |
| $dom->preserveWhiteSpace = FALSE; | |
| $dom->strictErrorChecking = FALSE; | |
| $dom->recover = TRUE; | |
| @$dom->loadHTML($html); | |
| $propertyName = function ($name) use ($pdf) { | |
| foreach (array_keys(get_object_vars($pdf)) as $propertyName) { | |
| if (strcasecmp($name, $propertyName) == 0) { | |
| return $propertyName; | |
| } | |
| } | |
| throw new Nette\MemberAccessException("Cannot write to an undeclared property " . get_class($pdf) . "::\$$name."); | |
| }; | |
| foreach ($dom->getElementsByTagName('body') as $body) { | |
| /** @var \DOMElement $body */ | |
| foreach ($body->attributes as $attrName => $value) { | |
| /** @var \DOMAttr $value */ | |
| if (stripos($attrName, 'data-pdf-') !== 0) { | |
| continue; | |
| } | |
| $property = $propertyName(substr($attrName, 9)); | |
| if (in_array(substr($value->value, 0, 1), array('[', '{'))) { | |
| $pdf->$property = Json::decode($value->value); | |
| } elseif ($value->value == 'false') { | |
| $pdf->$property = FALSE; | |
| } elseif ($value->value == 'true') { | |
| $pdf->$property = TRUE; | |
| } else { | |
| $pdf->$property = $value->value; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment