├── .gitignore ├── Resources ├── views │ └── Example │ │ ├── usingAutomaticFormatGuessing.html.twig │ │ ├── pdfStylesheet.xml.twig │ │ ├── usingFacadeDirectly.pdf.twig │ │ ├── usingAutomaticFormatGuessing.pdf.twig │ │ ├── index.html.twig │ │ └── markdown.pdf.twig └── config │ ├── routing.yml │ └── pdf.xml ├── PsPdfBundle.php ├── Tests ├── phpunit.xml ├── Annotation │ └── PdfTest.php ├── PHPPdf │ └── Util │ │ └── BundleBasedStringFilterTest.php ├── Templating │ └── ImageLocatorTest.php ├── DependencyInjection │ └── PsPdfExtensionTest.php └── EventListener │ └── PdfListenerTest.php ├── Templating ├── ImageLocatorInterface.php └── ImageLocator.php ├── EventListener ├── PdfRequestListener.php └── PdfListener.php ├── composer.json ├── CHANGELOG ├── Reflection └── Factory.php ├── Annotation └── Pdf.php ├── Twig └── Extensions │ └── Extension │ └── PdfExtension.php ├── LICENSE ├── .travis.yml ├── PHPPdf └── Util │ └── BundleBasedStringFilter.php ├── DependencyInjection ├── Configuration.php └── PsPdfExtension.php ├── Controller └── ExampleController.php └── README.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | vendor/ 4 | composer.lock -------------------------------------------------------------------------------- /Resources/views/Example/usingAutomaticFormatGuessing.html.twig: -------------------------------------------------------------------------------- 1 | Hello {{ name }}! -------------------------------------------------------------------------------- /Resources/views/Example/pdfStylesheet.xml.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Resources/views/Example/usingFacadeDirectly.pdf.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello! 6 | 7 | -------------------------------------------------------------------------------- /Resources/views/Example/usingAutomaticFormatGuessing.pdf.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello {{ name }}! 6 | 7 | -------------------------------------------------------------------------------- /PsPdfBundle.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle; 10 | 11 | use Symfony\Component\HttpKernel\Bundle\Bundle; 12 | 13 | /** 14 | * This bundle provides integration with PHPPdf library 15 | * 16 | * @author Piotr Śliwa 17 | */ 18 | class PsPdfBundle extends Bundle 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /Tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./ 6 | 7 | 8 | 9 | 10 | ./ 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Templating/ImageLocatorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ImageLocatorInterface 11 | { 12 | /** 13 | * Converts image logical name in "BundleName:image-name.extension" format to absolute file path. 14 | * 15 | * @return string file path 16 | * 17 | * @throws /InvalidArgumentException If bundle does not exist. 18 | */ 19 | public function getImagePath($logicalImageName); 20 | } 21 | -------------------------------------------------------------------------------- /EventListener/PdfRequestListener.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\EventListener; 10 | 11 | use Symfony\Component\HttpKernel\Event\GetResponseEvent; 12 | 13 | /** 14 | * Register a new 'pdf' format associated to mime type pdf. 15 | * 16 | */ 17 | class PdfRequestListener 18 | { 19 | public function onKernelRequest(GetResponseEvent $event) 20 | { 21 | $request = $event->getRequest(); 22 | $request->setFormat('pdf', 'application/pdf'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Resources/views/Example/index.html.twig: -------------------------------------------------------------------------------- 1 |

PdfBundle examples

2 | 3 | -------------------------------------------------------------------------------- /Resources/config/routing.yml: -------------------------------------------------------------------------------- 1 | _pdf_index: 2 | pattern: / 3 | defaults: { _controller: PsPdfBundle:Example:index } 4 | 5 | _pdf_using_facade_directly: 6 | pattern: /manually 7 | defaults: { _controller: PsPdfBundle:Example:usingFacadeDirectly } 8 | 9 | _pdf_using_automatic_format_detection: 10 | pattern: /auto/{name}.{_format} 11 | defaults: { _controller: PsPdfBundle:Example:usingAutomaticFormatGuessing, _format: html } 12 | requirements: 13 | _format: html|pdf 14 | 15 | _pdf_examples: 16 | pattern: /examples 17 | defaults: { _controller: PsPdfBundle:Example:examples } 18 | 19 | _pdf_markdown: 20 | pattern: /markdown 21 | defaults: { _controller: PsPdfBundle:Example:markdown, _format: pdf } 22 | requirements: 23 | _format: pdf -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "psliwa/pdf-bundle", 3 | "description": "This bundle integrates Symfony2 with PHPPdf library.", 4 | "keywords": ["PDF", "PHPPdf"], 5 | "type": "symfony-bundle", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Piotr Śliwa", 10 | "homepage": "http://psliwa.org", 11 | "email": "me@psliwa.org" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.0", 16 | "symfony/symfony": "~2.3|~3.0", 17 | "psliwa/php-pdf": "^1.1.5", 18 | "sensio/framework-extra-bundle": ">=2.0 <4.0.0" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": ">=4,<6.0.0" 22 | }, 23 | "autoload": { 24 | "psr-0": { "Ps\\PdfBundle": "" } 25 | }, 26 | "target-dir": "Ps/PdfBundle" 27 | } 28 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 2012-09-23 2 | * update composer.json file 3 | 4 | 2012-01-30 5 | * support for bundle based path placeholder in fonts and document xml files 6 | 7 | 2012-01-07 8 | * update supported PHPPdf version to 1.1.1 tag 9 | 10 | 2012-01-01 11 | * update supported PHPPdf version to 1.1.x branch 12 | 13 | 2011-12-29 14 | * fix support for PHPPdf 1.1.0-DEV version 15 | * rename config option ps_pdf.enhancements_file to ps_pdf.complex_attributes_file 16 | * new config options: ps_pdf.nodes_file and ps_pdf.colors_file 17 | 18 | 2011-11-11 19 | * new @Pdf annotation option: enableCache (boolean) 20 | * fix DI configuration 21 | 22 | 2011-11-11: 23 | * new branch: zf1-in-backend - version compatible with "zf1-in-backend" branch of PHPPdf library and PHPPdf 1.0.0 24 | * master branch is now compatible with PHPPdf 1.0.1 and newer, supported version of Zend Framework components is 2.0.0-beta1 and newer. 25 | -------------------------------------------------------------------------------- /Reflection/Factory.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\Reflection; 10 | 11 | /** 12 | * Simple factory method for reflection objects created in order to testing. 13 | * 14 | * @author Piotr Śliwa 15 | */ 16 | class Factory 17 | { 18 | public function createMethod($objectOrClass, $methodName) 19 | { 20 | $class = is_object($objectOrClass) ? get_class($objectOrClass) : (string) $objectOrClass; 21 | 22 | $class = $this->getUserClass($class); 23 | 24 | return new \ReflectionMethod($class, $methodName); 25 | } 26 | 27 | private function getUserClass($class) 28 | { 29 | if(class_exists('CG\Core\ClassUtils', true)) 30 | { 31 | return \CG\Core\ClassUtils::getUserClass($class); 32 | } 33 | 34 | return $class; 35 | } 36 | } -------------------------------------------------------------------------------- /Annotation/Pdf.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\Annotation; 10 | 11 | use Doctrine\Common\Annotations\Annotation; 12 | 13 | /** 14 | * Pdf annotation 15 | * 16 | * @Annotation 17 | * @author Piotr Śliwa 18 | */ 19 | class Pdf 20 | { 21 | public $stylesheet; 22 | public $documentParserType = 'xml'; 23 | public $headers = array(); 24 | public $enableCache = false; 25 | 26 | public function __construct(array $values) 27 | { 28 | $currentValues = get_object_vars($this); 29 | 30 | foreach($values as $key => $value) 31 | { 32 | if(array_key_exists($key, $currentValues)) 33 | { 34 | $this->$key = $value; 35 | } 36 | else 37 | { 38 | throw new \InvalidArgumentException(sprintf('Argument "%s" for @Pdf() annotation is unsupported.', $key)); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Twig/Extensions/Extension/PdfExtension.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\Twig\Extensions\Extension; 10 | 11 | use Ps\PdfBundle\Templating\ImageLocatorInterface; 12 | 13 | /** 14 | * Twig extension 15 | * 16 | * @author Piotr Śliwa 17 | */ 18 | class PdfExtension extends \Twig_Extension 19 | { 20 | private $imageLocator; 21 | 22 | public function __construct(ImageLocatorInterface $imageLocator) 23 | { 24 | $this->imageLocator = $imageLocator; 25 | } 26 | 27 | public function getFunctions() 28 | { 29 | return array( 30 | new \Twig_SimpleFunction('pdf_image', array($this, 'getImagePath')), 31 | ); 32 | } 33 | 34 | public function getName() 35 | { 36 | return 'ps_pdf'; 37 | } 38 | 39 | public function getImagePath($logicalImageName) 40 | { 41 | return $this->imageLocator->getImagePath($logicalImageName); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Piotr Śliwa 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - 7.1 9 | 10 | 11 | before_install: 12 | - composer self-update 13 | - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo "memory_limit = -1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi; 14 | - composer require --no-update symfony/framework-bundle "$SYMFONY_VERSION" 15 | 16 | install: 17 | - composer install --prefer-source 18 | 19 | script: vendor/bin/phpunit --configuration Tests/phpunit.xml 20 | 21 | env: 22 | - SYMFONY_VERSION=2.3.* 23 | - SYMFONY_VERSION=2.7.* 24 | - SYMFONY_VERSION=2.8.* 25 | 26 | matrix: 27 | include: 28 | - php: 5.5 29 | env: SYMFONY_VERSION=3.0.* 30 | - php: 5.6 31 | env: SYMFONY_VERSION=3.0.* 32 | - php: 7.0 33 | env: SYMFONY_VERSION=3.0.* 34 | - php: 7.1 35 | env: SYMFONY_VERSION=3.0.* 36 | 37 | - php: 7.0 38 | env: SYMFONY_VERSION=3.1.* 39 | - php: 7.1 40 | env: SYMFONY_VERSION=3.1.* 41 | 42 | - php: 7.0 43 | env: SYMFONY_VERSION=3.2.* 44 | - php: 7.1 45 | env: SYMFONY_VERSION=3.2.* 46 | 47 | - php: 7.1 48 | env: SYMFONY_VERSION=3.3.* 49 | 50 | sudo: false 51 | 52 | cache: 53 | directories: 54 | - $HOME/.composer/cache 55 | 56 | -------------------------------------------------------------------------------- /PHPPdf/Util/BundleBasedStringFilter.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\PHPPdf\Util; 10 | 11 | use Symfony\Component\HttpKernel\KernelInterface; 12 | use PHPPdf\Util\StringFilter; 13 | 14 | /** 15 | * Class that provides support for bundle based path translations. 16 | * 17 | * Example: 18 | * %SomeBundle:someFile.xml% will be replaced by "path/to/SomeBundle/Resources/someFile.xml" 19 | * 20 | * @author Piotr Śliwa 21 | */ 22 | class BundleBasedStringFilter implements StringFilter 23 | { 24 | private $kernel; 25 | 26 | public function __construct(KernelInterface $kernel = null) 27 | { 28 | $this->kernel = $kernel; 29 | } 30 | 31 | public function filter($value) 32 | { 33 | if(!$this->kernel) 34 | { 35 | return $value; 36 | } 37 | 38 | if(preg_match_all('/\%(.+Bundle):(.+)\%/U', $value, $matches)) 39 | { 40 | $searches = array(); 41 | $replacements = array(); 42 | foreach($matches[1] as $index => $bundleName) 43 | { 44 | $bundle = $this->kernel->getBundle($bundleName); 45 | $path = $bundle->getPath(); 46 | 47 | $searches[] = $matches[0][$index]; 48 | $replacements[] = $path.'/Resources/'.$matches[2][$index]; 49 | } 50 | 51 | $value = str_replace($searches, $replacements, $value); 52 | } 53 | 54 | return $value; 55 | } 56 | } -------------------------------------------------------------------------------- /Templating/ImageLocator.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\Templating; 10 | 11 | use Symfony\Component\HttpKernel\Kernel; 12 | 13 | /** 14 | * Image locator 15 | * 16 | * @author Piotr Sliwa 17 | */ 18 | class ImageLocator implements ImageLocatorInterface 19 | { 20 | private $kernel; 21 | 22 | public function __construct(Kernel $kernel) 23 | { 24 | $this->kernel = $kernel; 25 | } 26 | 27 | /** 28 | * Converts image logical name in "BundleName:image-name.extension" format to absolute file path. 29 | * 30 | * @return string file path 31 | * 32 | * @throws /InvalidArgumentException If bundle does not exist. 33 | */ 34 | public function getImagePath($logicalImageName) 35 | { 36 | $pos = strpos($logicalImageName, ':'); 37 | 38 | // add support for ::$imagePath syntax as in twig 39 | // @see http://symfony.com/doc/current/book/page_creation.html#optional-step-3-create-the-template 40 | if($pos === false || $pos === 0) 41 | { 42 | return $this->kernel->getRootDir() . '/Resources/public/images/' . ltrim($logicalImageName, ':'); 43 | } 44 | 45 | $bundleName = substr($logicalImageName, 0, $pos); 46 | $imageName = substr($logicalImageName, $pos + 1); 47 | 48 | $bundle = $this->kernel->getBundle($bundleName); 49 | $bundlePath = $bundle->getPath(); 50 | 51 | return $bundlePath.'/Resources/public/images/'.$imageName; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/Annotation/PdfTest.php: -------------------------------------------------------------------------------- 1 | getMethodAnnotation($method, 'Ps\PdfBundle\Annotation\Pdf'); 19 | 20 | $this->assertNotNull($pdf); 21 | } 22 | 23 | /** 24 | * @test 25 | * @dataProvider createAnnotationProvider 26 | */ 27 | public function createAnnotation(array $args, $expectedException) 28 | { 29 | try 30 | { 31 | $defaultArgs = array('stylesheet' => null, 'documentParserType' => 'xml', 'headers' => array(), 'enableCache' => false); 32 | 33 | $annotation = new Pdf($args); 34 | 35 | if($expectedException) 36 | { 37 | $this->fail('exception expected'); 38 | } 39 | 40 | $expectedVars = $args + $defaultArgs; 41 | 42 | $this->assertEquals($expectedVars, get_object_vars($annotation)); 43 | } 44 | catch(\InvalidArgumentException $e) 45 | { 46 | if(!$expectedException) 47 | { 48 | $this->fail('unexpected exception'); 49 | } 50 | } 51 | } 52 | 53 | public function createAnnotationProvider() 54 | { 55 | return array( 56 | array(array(), false), 57 | array(array('stylesheet' => 'stylesheet', 'documentParserType' => 'markdown'), false), 58 | array(array('enableCache' => true, 'headers' => array('key' => 'value')), false), 59 | array(array('unexistedArg' => 'value'), true), 60 | ); 61 | } 62 | } -------------------------------------------------------------------------------- /Tests/PHPPdf/Util/BundleBasedStringFilterTest.php: -------------------------------------------------------------------------------- 1 | kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); 15 | $this->filter = new BundleBasedStringFilter($this->kernel); 16 | } 17 | 18 | /** 19 | * @test 20 | * @dataProvider replaceBundleVariablesProvider 21 | */ 22 | public function replaceBundleVariables($string, $expectedString, array $expectedBundles) 23 | { 24 | foreach($expectedBundles as $at => $bundle) 25 | { 26 | list($bundleName, $bundlePath) = $bundle; 27 | 28 | $bundleMock = $this->getMockBuilder('\Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock(); 29 | $bundleMock->expects($this->once()) 30 | ->method('getPath') 31 | ->will($this->returnValue($bundlePath)); 32 | 33 | $this->kernel->expects($this->at($at)) 34 | ->method('getBundle') 35 | ->with($bundleName) 36 | ->will($this->returnValue($bundleMock)); 37 | } 38 | 39 | $actualString = $this->filter->filter($string); 40 | 41 | $this->assertEquals($expectedString, $actualString); 42 | } 43 | 44 | public function replaceBundleVariablesProvider() 45 | { 46 | return array( 47 | array('some text', 'some text', array()), 48 | array('text text %SomeBundle:file.xml% text text', 'text text path/Resources/file.xml text text', array( 49 | array('SomeBundle', 'path'), 50 | )), 51 | array('text text %SomeBundle:file1.xml% text %SomeBundle:file2.xml% text', 'text text path/Resources/file1.xml text path/Resources/file2.xml text', array( 52 | array('SomeBundle', 'path'), 53 | array('SomeBundle', 'path'), 54 | )), 55 | ); 56 | } 57 | } -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\DependencyInjection; 10 | 11 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 12 | use Symfony\Component\Config\Definition\ConfigurationInterface; 13 | 14 | /** 15 | * Configuration definition class 16 | * 17 | * @author Piotr Śliwa 18 | */ 19 | class Configuration implements ConfigurationInterface 20 | { 21 | public function getConfigTreeBuilder() 22 | { 23 | $treeBuilder = new TreeBuilder(); 24 | $rootNode = $treeBuilder->root('ps_pdf'); 25 | 26 | $rootNode->children() 27 | ->arrayNode('cache') 28 | ->children() 29 | ->variableNode('options') 30 | ->defaultValue(array()) 31 | ->end() 32 | ->scalarNode('type') 33 | ->defaultValue('File') 34 | ->end() 35 | ->end() 36 | ->end() 37 | ->scalarNode('use_cache_in_stylesheet') 38 | ->defaultValue(true) 39 | ->end() 40 | ->scalarNode('nodes_file') 41 | ->defaultNull() 42 | ->end() 43 | ->scalarNode('fonts_file') 44 | ->defaultNull() 45 | ->end() 46 | ->scalarNode('complex_attributes_file') 47 | ->defaultNull() 48 | ->end() 49 | ->scalarNode('colors_file') 50 | ->defaultNull() 51 | ->end() 52 | ->scalarNode('markdown_stylesheet_filepath') 53 | ->defaultNull() 54 | ->end() 55 | ->scalarNode('markdown_document_template_filepath') 56 | ->defaultNull() 57 | ->end() 58 | ->scalarNode('document_parser_type') 59 | ->defaultValue('xml') 60 | ->end() 61 | ->end(); 62 | 63 | return $treeBuilder; 64 | } 65 | } -------------------------------------------------------------------------------- /Controller/ExampleController.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\Controller; 10 | 11 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 12 | use Symfony\Component\HttpFoundation\Response; 13 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 14 | use Ps\PdfBundle\Annotation\Pdf; 15 | 16 | /** 17 | * Controller with examples 18 | * 19 | * @author Piotr Śliwa 20 | */ 21 | class ExampleController extends Controller 22 | { 23 | public function indexAction() 24 | { 25 | return $this->render('PsPdfBundle:Example:index.html.twig'); 26 | } 27 | 28 | public function usingFacadeDirectlyAction() 29 | { 30 | $facade = $this->get('ps_pdf.facade'); 31 | $response = new Response(); 32 | $this->render('PsPdfBundle:Example:usingFacadeDirectly.pdf.twig', array(), $response); 33 | 34 | $xml = $response->getContent(); 35 | 36 | $content = $facade->render($xml); 37 | 38 | return new Response($content, 200, array('content-type' => 'application/pdf')); 39 | } 40 | 41 | /** 42 | * Possible custom headers and external stylesheet file 43 | * 44 | * @Pdf( 45 | * headers={"Expires"="Sat, 1 Jan 2000 12:00:00 GMT"}, 46 | * stylesheet="PsPdfBundle:Example:pdfStylesheet.xml.twig", 47 | * enableCache=true 48 | * ) 49 | */ 50 | public function usingAutomaticFormatGuessingAction($name) 51 | { 52 | $format = $this->get('request')->get('_format'); 53 | 54 | return $this->render(sprintf('PsPdfBundle:Example:usingAutomaticFormatGuessing.%s.twig', $format), array( 55 | 'name' => $name, 56 | )); 57 | } 58 | 59 | /** 60 | * Standard examples of PHPPdf library 61 | */ 62 | public function examplesAction() 63 | { 64 | $kernelRootDir = $this->container->getParameter('kernel.root_dir'); 65 | 66 | $propablyPhpPdfExamplesFilePaths = array($kernelRootDir.'/../vendor/PHPPdf/examples/index.php', $kernelRootDir.'/../vendor/psliwa/php-pdf/examples/index.php'); 67 | 68 | foreach($propablyPhpPdfExamplesFilePaths as $propablyPhpPdfExamplesFilePath) 69 | { 70 | if(file_exists($propablyPhpPdfExamplesFilePath)) 71 | { 72 | require $propablyPhpPdfExamplesFilePath; 73 | exit(); 74 | } 75 | } 76 | 77 | throw new NotFoundHttpException('File with PHPPdf examples not found.'); 78 | } 79 | 80 | /** 81 | * @Pdf( 82 | * headers={"Expires"="Sat, 1 Jan 2000 12:00:00 GMT"}, 83 | * documentParserType="markdown" 84 | * ) 85 | */ 86 | public function markdownAction() 87 | { 88 | $format = $this->get('request')->get('_format'); 89 | 90 | return $this->render(sprintf('PsPdfBundle:Example:markdown.%s.twig', $format)); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Tests/Templating/ImageLocatorTest.php: -------------------------------------------------------------------------------- 1 | kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Kernel') 15 | ->setMethods(array('getBundle', 'registerBundles', 'registerContainerConfiguration', 'getRootDir')) 16 | ->disableOriginalConstructor() 17 | ->getMock(); 18 | 19 | $this->locator = new ImageLocator($this->kernel); 20 | } 21 | 22 | /** 23 | * @test 24 | * @dataProvider dataProvider 25 | */ 26 | public function getImagePathSuccessfullyWhenBundleExists($bundleName, $imageName) 27 | { 28 | $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') 29 | ->setMethods(array('getPath')) 30 | ->disableOriginalConstructor() 31 | ->getMock(); 32 | 33 | $bundlePath = 'some/bundle/path'; 34 | 35 | $imageLogicalName = sprintf('%s:%s', $bundleName, $imageName); 36 | $expectedImagePath = $bundlePath.'/Resources/public/images/'.$imageName; 37 | 38 | $this->kernel->expects($this->once()) 39 | ->method('getBundle') 40 | ->with($bundleName) 41 | ->will($this->returnValue($bundle)); 42 | 43 | $bundle->expects($this->once()) 44 | ->method('getPath') 45 | ->will($this->returnValue($bundlePath)); 46 | 47 | $this->assertEquals($expectedImagePath, $this->locator->getImagePath($imageLogicalName)); 48 | } 49 | 50 | public function dataProvider() 51 | { 52 | return array( 53 | array('SomeBundle', 'some-image.jpg'), 54 | array('SomeBundle', 'dir/some:image.jpg'), 55 | ); 56 | } 57 | 58 | /** 59 | * @test 60 | * @expectedException InvalidArgumentException 61 | */ 62 | public function throwExceptionIfBundleDoesNotExist() 63 | { 64 | $this->kernel->expects($this->once()) 65 | ->method('getBundle') 66 | ->will($this->throwException(new \InvalidArgumentException())); 67 | 68 | $this->locator->getImagePath('unexistedBundle:someImage.jpg'); 69 | } 70 | 71 | /** 72 | * @test 73 | */ 74 | public function getImagePathFromGlobalResourcesWhenBundleNameIsEmpty() 75 | { 76 | $rootDir = 'some/root/dir'; 77 | $imageName = 'some/image/name.jpg'; 78 | $prefixes = array('', ':', '::'); 79 | 80 | $this->kernel->expects($this->exactly(count($prefixes))) 81 | ->method('getRootDir') 82 | ->will($this->returnValue($rootDir)); 83 | 84 | $this->kernel->expects($this->never()) 85 | ->method('getBundle'); 86 | 87 | $expectedPath = $rootDir.'/Resources/public/images/'.$imageName; 88 | 89 | foreach($prefixes as $prefix) 90 | { 91 | $this->assertEquals($expectedPath, $this->locator->getImagePath($prefix.$imageName)); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /DependencyInjection/PsPdfExtension.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\DependencyInjection; 10 | 11 | use Symfony\Component\Config\Definition\Processor; 12 | use Symfony\Component\Config\FileLocator; 13 | use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; 14 | use Symfony\Component\DependencyInjection\Reference; 15 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | 18 | 19 | /** 20 | * Extenstion class 21 | * 22 | * @author Piotr Śliwa 23 | */ 24 | class PsPdfExtension extends Extension 25 | { 26 | public function load(array $config, ContainerBuilder $container) 27 | { 28 | $config = $this->getConfig($config); 29 | 30 | $this->loadDefaults($container); 31 | 32 | $this->setConfigIntoContainer($container, $config); 33 | } 34 | 35 | private function getConfig(array $config) 36 | { 37 | $configurationProcessor = new Processor(); 38 | $configuration = new Configuration(); 39 | 40 | return $configurationProcessor->processConfiguration($configuration, $config); 41 | } 42 | 43 | private function loadDefaults(ContainerBuilder $container) 44 | { 45 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 46 | 47 | $loader->load('pdf.xml'); 48 | 49 | // TODO: Go back to xml configuration when bumping the requirement to Symfony >=2.6 50 | $facadeDefinition = $container->getDefinition('ps_pdf.facade'); 51 | $facadeBuilderDefinition = $container->getDefinition('ps_pdf.facade_builder'); 52 | 53 | if(method_exists('Symfony\\Component\\DependencyInjection\\Definition', 'setFactory')) 54 | { 55 | $facadeDefinition->setFactory(array(new Reference('ps_pdf.facade_builder'), 'build')); 56 | $facadeBuilderDefinition->setFactory(array('PHPPdf\\Core\\FacadeBuilder', 'create')); 57 | } 58 | else 59 | { 60 | $facadeDefinition->setFactoryService('ps_pdf.facade_builder'); 61 | $facadeDefinition->setFactoryMethod('build'); 62 | 63 | $facadeBuilderDefinition->setFactoryClass('PHPPdf\\Core\\FacadeBuilder'); 64 | $facadeBuilderDefinition->setFactoryMethod('create'); 65 | } 66 | } 67 | 68 | private function setConfigIntoContainer(ContainerBuilder $container, array $config) 69 | { 70 | $this->setGenericConfig($container, $config, 'ps_pdf.%s', array('nodes_file', 'fonts_file', 'complex_attributes_file', 'colors_file', 'use_cache_in_stylesheet', 'markdown_stylesheet_filepath', 'markdown_document_template_filepath', 'document_parser_type')); 71 | 72 | if(isset($config['cache'])) 73 | { 74 | $this->setGenericConfig($container, $config['cache'], 'ps_pdf.cache.%s', array('type', 'options')); 75 | } 76 | } 77 | 78 | private function setGenericConfig(ContainerBuilder $container, array $config, $format, array $options) 79 | { 80 | foreach($options as $name) 81 | { 82 | if(null !== $config[$name]) 83 | { 84 | $container->setParameter(sprintf($format, $name), $config[$name]); 85 | } 86 | } 87 | } 88 | 89 | public function getNamespace() 90 | { 91 | return 'http://ohey.pl/phppdf/schema/dic/'.$this->getAlias(); 92 | } 93 | 94 | public function getXsdValidationBasePath() 95 | { 96 | return false; 97 | } 98 | 99 | public function getAlias() 100 | { 101 | return 'ps_pdf'; 102 | } 103 | } -------------------------------------------------------------------------------- /Tests/DependencyInjection/PsPdfExtensionTest.php: -------------------------------------------------------------------------------- 1 | extension = new PsPdfExtension(); 15 | } 16 | 17 | /** 18 | * @test 19 | */ 20 | public function insertFacadeObjectIntoContainer() 21 | { 22 | $container = new ContainerBuilder(); 23 | $container->setParameter('kernel.cache_dir', __DIR__.'/'); 24 | 25 | $this->extension->load(array(), $container); 26 | 27 | $this->assertTrue($container->has('ps_pdf.facade')); 28 | $facade = $container->get('ps_pdf.facade'); 29 | 30 | $this->assertInstanceOf('PHPPdf\Core\Facade', $facade); 31 | } 32 | 33 | /** 34 | * @test 35 | */ 36 | public function insertFacadeBuilderObjectIntoContainer() 37 | { 38 | $container = new ContainerBuilder(); 39 | $container->setParameter('kernel.cache_dir', __DIR__.'/'); 40 | 41 | $this->extension->load(array(), $container); 42 | 43 | $this->assertTrue($container->has('ps_pdf.facade_builder')); 44 | $builder = $container->get('ps_pdf.facade_builder'); 45 | 46 | $this->assertInstanceOf('PHPPdf\Core\FacadeBuilder', $builder); 47 | } 48 | 49 | /** 50 | * @test 51 | */ 52 | public function insertCacheObjectIntoContainer() 53 | { 54 | $container = new ContainerBuilder(); 55 | $container->setParameter('kernel.cache_dir', __DIR__.'/'); 56 | 57 | $this->extension->load(array(), $container); 58 | 59 | $this->assertTrue($container->has('ps_pdf.cache')); 60 | $cache = $container->get('ps_pdf.cache'); 61 | 62 | $this->assertInstanceOf('PHPPdf\Cache\Cache', $cache); 63 | } 64 | 65 | /** 66 | * @test 67 | */ 68 | public function setContainerParametersIfPassed() 69 | { 70 | $container = new ContainerBuilder(); 71 | $container->setParameter('kernel.cache_dir', __DIR__.'/'); 72 | $config = array( 73 | array( 74 | 'nodes_file' => 'nodes file', 75 | 'fonts_file' => 'some file', 76 | 'complex_attributes_file' => 'some another file', 77 | 'colors_file' => 'colors file', 78 | 'cache' => array( 79 | 'type' => 'some type', 80 | 'options' => array( 81 | 'custom_option' => 'value', 82 | ), 83 | ), 84 | 'use_cache_in_stylesheet' => true, 85 | 'markdown_stylesheet_filepath' => 'path1', 86 | 'markdown_document_template_filepath' => 'path2', 87 | 'document_parser_type' => 'markdown', 88 | ), 89 | ); 90 | 91 | $this->extension->load($config, $container); 92 | 93 | $this->assertEquals($config[0]['nodes_file'], $container->getParameter('ps_pdf.nodes_file')); 94 | $this->assertEquals($config[0]['colors_file'], $container->getParameter('ps_pdf.colors_file')); 95 | $this->assertEquals($config[0]['fonts_file'], $container->getParameter('ps_pdf.fonts_file')); 96 | $this->assertEquals($config[0]['complex_attributes_file'], $container->getParameter('ps_pdf.complex_attributes_file')); 97 | $this->assertEquals($config[0]['cache']['type'], $container->getParameter('ps_pdf.cache.type')); 98 | $this->assertEquals($config[0]['cache']['options'], $container->getParameter('ps_pdf.cache.options')); 99 | $this->assertEquals($config[0]['use_cache_in_stylesheet'], $container->getParameter('ps_pdf.use_cache_in_stylesheet')); 100 | $this->assertEquals($config[0]['markdown_stylesheet_filepath'], $container->getParameter('ps_pdf.markdown_stylesheet_filepath')); 101 | $this->assertEquals($config[0]['markdown_document_template_filepath'], $container->getParameter('ps_pdf.markdown_document_template_filepath')); 102 | $this->assertEquals($config[0]['document_parser_type'], $container->getParameter('ps_pdf.document_parser_type')); 103 | } 104 | } -------------------------------------------------------------------------------- /Resources/config/pdf.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | null 8 | null 9 | null 10 | null 11 | File 12 | 13 | %kernel.cache_dir% 14 | 15 | true 16 | null 17 | null 18 | xml 19 | Ps\PdfBundle\PHPPdf\Util\BundleBasedStringFilter 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | %ps_pdf.nodes_file% 29 | %ps_pdf.complex_attributes_file% 30 | %ps_pdf.fonts_file% 31 | %ps_pdf.colors_file% 32 | 33 | 34 | 35 | 36 | 37 | 38 | %ps_pdf.cache.type% 39 | %ps_pdf.cache.options% 40 | 41 | 42 | %ps_pdf.use_cache_in_stylesheet% 43 | 44 | 45 | %ps_pdf.markdown_stylesheet_filepath% 46 | 47 | 48 | %ps_pdf.markdown_document_template_filepath% 49 | 50 | 51 | %ps_pdf.document_parser_type% 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | %ps_pdf.cache.type% 86 | %ps_pdf.cache.options% 87 | 88 | 89 | -------------------------------------------------------------------------------- /EventListener/PdfListener.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * License information is in LICENSE file 7 | */ 8 | 9 | namespace Ps\PdfBundle\EventListener; 10 | 11 | use PHPPdf\Cache\Cache; 12 | use Ps\PdfBundle\Annotation\Pdf as PdfAnnotation; 13 | use Symfony\Component\HttpFoundation\Request; 14 | use PHPPdf\Core\Facade; 15 | use Symfony\Component\HttpKernel\Exception\HttpException; 16 | use PHPPdf\Core\FacadeBuilder; 17 | use Symfony\Component\Templating\EngineInterface; 18 | use Symfony\Component\HttpKernel\Event\GetResponseEvent; 19 | use Symfony\Component\HttpKernel\Event\FilterResponseEvent; 20 | use Symfony\Component\HttpFoundation\Response; 21 | use Symfony\Component\HttpKernel\Event\FilterControllerEvent; 22 | use Ps\PdfBundle\Reflection\Factory; 23 | use Doctrine\Common\Annotations\Reader; 24 | 25 | /** 26 | * This listener will replace reponse content by pdf document's content if Pdf annotations is found. 27 | * Also adds proper headers to response object. 28 | * 29 | * @author Piotr Śliwa 30 | */ 31 | class PdfListener 32 | { 33 | private $pdfFacadeBuilder; 34 | private $annotationReader; 35 | private $reflectionFactory; 36 | private $templatingEngine; 37 | private $cache; 38 | 39 | public function __construct(FacadeBuilder $pdfFacadeBuilder, Reader $annotationReader, Factory $reflectionFactory, EngineInterface $templatingEngine, Cache $cache) 40 | { 41 | $this->pdfFacadeBuilder = $pdfFacadeBuilder; 42 | $this->annotationReader = $annotationReader; 43 | $this->reflectionFactory = $reflectionFactory; 44 | $this->templatingEngine = $templatingEngine; 45 | $this->cache = $cache; 46 | } 47 | 48 | public function onKernelController(FilterControllerEvent $event) 49 | { 50 | $request = $event->getRequest(); 51 | $format = $request->get('_format'); 52 | 53 | if($format != 'pdf' || !is_array($controller = $event->getController()) || !$controller) 54 | { 55 | return; 56 | } 57 | 58 | $method = $this->reflectionFactory->createMethod($controller[0], $controller[1]); 59 | 60 | $annotation = $this->annotationReader->getMethodAnnotation($method, 'Ps\PdfBundle\Annotation\Pdf'); 61 | 62 | if($annotation) 63 | { 64 | $request->attributes->set('_pdf', $annotation); 65 | } 66 | } 67 | 68 | public function onKernelResponse(FilterResponseEvent $event) 69 | { 70 | $request = $event->getRequest(); 71 | 72 | if(!($annotation = $request->attributes->get('_pdf'))) 73 | { 74 | return; 75 | } 76 | 77 | $response = $event->getResponse(); 78 | 79 | if($response->getStatusCode() > 299) 80 | { 81 | return; 82 | } 83 | 84 | $stylesheetContent = null; 85 | if($stylesheet = $annotation->stylesheet) 86 | { 87 | $stylesheetContent = $this->templatingEngine->render($stylesheet); 88 | } 89 | 90 | $content = $this->getPdfContent($annotation, $response, $request, $stylesheetContent); 91 | 92 | $headers = (array) $annotation->headers; 93 | $headers['content-length'] = strlen($content); 94 | foreach($headers as $key => $value) 95 | { 96 | $response->headers->set($key, $value); 97 | } 98 | 99 | $response->setContent($content); 100 | } 101 | 102 | private function getPdfContent(PdfAnnotation $pdfAnnotation, Response $response, Request $request, $stylesheetContent) 103 | { 104 | try 105 | { 106 | $responseContent = $response->getContent(); 107 | 108 | $pdfContent = null; 109 | 110 | if($pdfAnnotation->enableCache) 111 | { 112 | $cacheKey = md5($responseContent.$stylesheetContent); 113 | 114 | if($this->cache->test($cacheKey)) 115 | { 116 | $pdfContent = $this->cache->load($cacheKey); 117 | } 118 | } 119 | 120 | if($pdfContent === null) 121 | { 122 | $pdfFacade = $this->pdfFacadeBuilder->setDocumentParserType($pdfAnnotation->documentParserType) 123 | ->build(); 124 | 125 | $pdfContent = $pdfFacade->render($responseContent, $stylesheetContent); 126 | 127 | if($pdfAnnotation->enableCache) 128 | { 129 | $this->cache->save($pdfContent, $cacheKey); 130 | } 131 | } 132 | 133 | return $pdfContent; 134 | } 135 | catch(\Exception $e) 136 | { 137 | $request->setRequestFormat('html'); 138 | $response->headers->set('content-type', 'text/html'); 139 | throw $e; 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | PsPdfBundle 2 | =========== 3 | 4 | [![Build Status](https://secure.travis-ci.org/psliwa/PdfBundle.png?branch=master)](http://travis-ci.org/psliwa/PdfBundle) 5 | 6 | This bundle integrates Symfony2 with [PHPPdf][1] library. Thanks to this bundle you can easily generate PDF or image (png, jpg) files. 7 | 8 | Documentation of [PHPPdf][1] you can find on github (README file). 9 | 10 | Installation 11 | ------------ 12 | 13 | 1. Use composer. PsPdfBundle requires "minimum-stability" equals to dev. Run this command: 14 | 15 | composer require psliwa/pdf-bundle 16 | 17 | 2. Register bundle in AppKernel: 18 | 19 | //app/AppKernel.php 20 | public function registerBundles() 21 | { 22 | return array( 23 | // .. 24 | new Ps\PdfBundle\PsPdfBundle(), 25 | // .. 26 | ); 27 | } 28 | 29 | Configuration 30 | ------------- 31 | 32 | All options are optional. 33 | 34 | # app/config/config.yml 35 | ps_pdf: 36 | nodes_file: ~ 37 | fonts_file: ~ 38 | complex_attributes_file: ~ 39 | colors_file: ~ 40 | use_cache_in_stylesheet: ~ 41 | cache: 42 | type: ~ 43 | options: ~ 44 | markdown_stylesheet_filepath: ~ 45 | markdown_document_template_filepath: ~ 46 | document_parser_type: ~ 47 | 48 | * nodes_file - path to file with nodes/tags definitions, internal nodes.xml file from PHPPdf library is used by default 49 | * fonts_file - path to file with fonts definitions, internal fonts.xml file from PHPPdf library is used by default 50 | * complex_attributes_file - path to file with complex attributes definitions, internal complex-attributes.xml file from PHPPdf library is used by default 51 | * colors_file - path to file with default palette of colors, internal colors.xml file from PHPPdf library is used by default 52 | * cache.type - type of cache, supported are all backend cache from Zend_Cache component (for instance File, Apc, Memcached, Sqlite etc.). File engine is used by default. 53 | * cache.options - specyfic options for cache engine (for instance "cache_dir" for File engine). cache_dir by default is as same as kernel.cache_dir. 54 | * use_cache_in_stylesheet - stylesheet maching rules will be cache, if this option is set. In complex stylesheet cache significantly improves performance. Default is true, but **in dev environment cache should be off**. 55 | * markdown_stylesheet_filepath - filepath of stylesheet for markdown parser 56 | * markdown_document_template_filepath - xml document template form output of markdown parser 57 | * document_parser_type - default parser type: xml or markdown 58 | 59 | Images in source document 60 | ------------------------- 61 | 62 | If you want to display image, you must provide absolute path to image file via "src" attribute of image tag. Asset Twig function dosn't work, because it converts image path to relative path according to web directory. To make using of images easier, bundle provides Twig function, that converts image logical name to real, absolute path. 63 | 64 | Example: 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Bundle based paths in fonts and document xml file 74 | ------------------------------------------------- 75 | 76 | If you want to use custom fonts, you should create your own fonts.xml config file (default fonts filepath is PHPPdf\Resources\config\fonts.xml). To make easier defining fonts paths, bundle based paths are supported. Example: 77 | 78 | 79 | 80 | 81 | "%SomeBundle:file.ttf%" will be replaced by "path/to/SomeBundle/Resources/file.ttf" 82 | 83 | Example 84 | ------- 85 | // In controller 86 | //... 87 | use Ps\PdfBundle\Annotation\Pdf; 88 | //... 89 | 90 | /** 91 | * @Pdf() 92 | */ 93 | public function helloAction($name) 94 | { 95 | $format = $this->get('request')->get('_format'); 96 | 97 | return $this->render(sprintf('SomeBundle:SomeController:helloAction.%s.twig', $format), array( 98 | 'name' => $name, 99 | )); 100 | } 101 | 102 | // in helloAction.html.twig 103 | Hello {{ name }}! 104 | 105 | // in helloAction.pdf.twig 106 | 107 | 108 | Hello {{ name }}! 109 | 110 | 111 | 112 | Bundle automatically detects pdf format (via _format) request and create pdf document from response. 113 | 114 | Pdf annotation has four optional properties: 115 | 116 | * headers - associative array of specyfic headers 117 | * stylesheet - pdf stylesheet template file in standard Symfony2 notation ("Bundle:Controller:file.format.engine") 118 | * documentParserType - type of parser: xml or markdown 119 | * enableCache - pdf output should by cached? True or false, default: false. Hash (md5) from template and stylesheet content is a cache key, only PHPPdf invocation is cached, controller is always called. 120 | 121 | [1]: https://github.com/psliwa/PHPPdf 122 | [2]: https://github.com/avalanche123/Imagine 123 | -------------------------------------------------------------------------------- /Tests/EventListener/PdfListenerTest.php: -------------------------------------------------------------------------------- 1 | pdfFacadeBuilder = $this->getMockBuilder('PHPPdf\Core\FacadeBuilder') 31 | ->disableOriginalConstructor() 32 | ->setMethods(array('build', 'setDocumentParserType')) 33 | ->getMock(); 34 | 35 | $this->pdfFacade = $this->getMockBuilder('PHPPdf\Core\Facade') 36 | ->disableOriginalConstructor() 37 | ->setMethods(array('render')) 38 | ->getMock(); 39 | 40 | $this->templatingEngine = $this->getMockBuilder('Symfony\Component\Templating\EngineInterface') 41 | ->setMethods(array('render', 'supports', 'exists')) 42 | ->getMock(); 43 | 44 | $this->reflactionFactory = $this->getMockBuilder('Ps\PdfBundle\Reflection\Factory') 45 | ->setMethods(array('createMethod')) 46 | ->getMock(); 47 | $this->annotationReader = $this->getMockBuilder('Doctrine\Common\Annotations\Reader') 48 | ->setMethods(array('getMethodAnnotations', 'getMethodAnnotation', 'getClassAnnotations', 'getClassAnnotation', 'getPropertyAnnotations', 'getPropertyAnnotation')) 49 | ->getMock(); 50 | 51 | $this->cache = $this->getMockBuilder('PHPPdf\Cache\Cache')->getMock(); 52 | 53 | $this->listener = new PdfListener($this->pdfFacadeBuilder, $this->annotationReader, $this->reflactionFactory, $this->templatingEngine, $this->cache); 54 | 55 | $this->request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request') 56 | ->setMethods(array('get')) 57 | ->getMock(); 58 | $this->requestAttributes = $this->getMockBuilder('stdClass') 59 | ->setMethods(array('set', 'get')) 60 | ->getMock(); 61 | 62 | $this->request->attributes = $this->requestAttributes; 63 | 64 | $this->controllerEvent = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\FilterControllerEvent') 65 | ->setMethods(array('setController', 'getController', 'getRequest')) 66 | ->disableOriginalConstructor() 67 | ->getMock(); 68 | 69 | $this->controllerEvent->expects($this->any()) 70 | ->method('getRequest') 71 | ->will($this->returnValue($this->request)); 72 | } 73 | 74 | /** 75 | * @test 76 | * @dataProvider annotationProvider 77 | */ 78 | public function setAnnotationObjectToRequestIfRequestFormatIsPdfAndAnnotationExists($annotation, $format, $shouldControllerBeenSet) 79 | { 80 | $objectStub = new FileLocator(); 81 | $controllerStub = array($objectStub, 'locate'); 82 | $methodStub = new \ReflectionMethod($controllerStub[0], $controllerStub[1]); 83 | 84 | $this->request->expects($this->once()) 85 | ->method('get') 86 | ->with('_format') 87 | ->will($this->returnValue($format)); 88 | 89 | $this->controllerEvent->expects($this->any()) 90 | ->method('getController') 91 | ->will($this->returnValue($controllerStub)); 92 | 93 | if($format == 'pdf') 94 | { 95 | $this->reflactionFactory->expects($this->once()) 96 | ->method('createMethod') 97 | ->with($controllerStub[0], $controllerStub[1]) 98 | ->will($this->returnValue($methodStub)); 99 | 100 | $this->annotationReader->expects($this->once()) 101 | ->method('getMethodAnnotation') 102 | ->with($methodStub, 'Ps\PdfBundle\Annotation\Pdf') 103 | ->will($this->returnValue($annotation)); 104 | } 105 | else 106 | { 107 | $this->reflactionFactory->expects($this->never()) 108 | ->method('createMethod'); 109 | 110 | $this->annotationReader->expects($this->never()) 111 | ->method('getMethodAnnotation'); 112 | } 113 | 114 | if($shouldControllerBeenSet) 115 | { 116 | $this->requestAttributes->expects($this->once()) 117 | ->method('set') 118 | ->with('_pdf', $annotation); 119 | } 120 | else 121 | { 122 | $this->requestAttributes->expects($this->never()) 123 | ->method('set'); 124 | } 125 | 126 | $this->listener->onKernelController($this->controllerEvent); 127 | } 128 | 129 | public function annotationProvider() 130 | { 131 | $annotation = new Pdf(array()); 132 | 133 | return array( 134 | array($annotation, 'pdf', true), 135 | array(null, 'pdf', false), 136 | array($annotation, 'html', false), 137 | ); 138 | } 139 | 140 | /** 141 | * @test 142 | */ 143 | public function donotInvokePdfRenderingOnViewEventWhenResponseStatusIsError() 144 | { 145 | $annotation = new Pdf(array()); 146 | $this->requestAttributes->expects($this->once()) 147 | ->method('get') 148 | ->with('_pdf') 149 | ->will($this->returnValue($annotation)); 150 | 151 | $responseStub = new Response(); 152 | $responseStub->setStatusCode(300); 153 | $event = new FilterResponseEventStub($this->request, $responseStub); 154 | 155 | $this->pdfFacadeBuilder->expects($this->never()) 156 | ->method('build'); 157 | 158 | $this->listener->onKernelResponse($event); 159 | } 160 | 161 | /** 162 | * @test 163 | * @dataProvider booleanPairProvider 164 | */ 165 | public function invokePdfRenderingOnViewEvent($enableCache, $freshCache) 166 | { 167 | $annotation = new Pdf(array('enableCache' => $enableCache)); 168 | $this->requestAttributes->expects($this->once()) 169 | ->method('get') 170 | ->with('_pdf') 171 | ->will($this->returnValue($annotation)); 172 | 173 | $contentStub = 'stub'; 174 | $responseContent = 'controller result stub'; 175 | $responseStub = new Response($responseContent); 176 | 177 | if($enableCache) 178 | { 179 | $cacheKey = md5($responseContent); 180 | $this->cache->expects($this->once()) 181 | ->method('test') 182 | ->with($cacheKey) 183 | ->will($this->returnValue($freshCache)); 184 | 185 | if($freshCache) 186 | { 187 | $this->cache->expects($this->once()) 188 | ->method('load') 189 | ->with($cacheKey) 190 | ->will($this->returnValue($contentStub)); 191 | } 192 | else 193 | { 194 | $this->cache->expects($this->never()) 195 | ->method('load'); 196 | 197 | $this->expectPdfFacadeBuilding($annotation); 198 | 199 | $this->pdfFacade->expects($this->once()) 200 | ->method('render') 201 | ->with($responseContent) 202 | ->will($this->returnValue($contentStub)); 203 | 204 | $this->cache->expects($this->once()) 205 | ->method('save') 206 | ->with($contentStub, $cacheKey); 207 | } 208 | } 209 | else 210 | { 211 | foreach(array('test', 'load', 'save') as $method) 212 | { 213 | $this->cache->expects($this->never()) 214 | ->method($method); 215 | } 216 | 217 | $this->expectPdfFacadeBuilding($annotation); 218 | 219 | $this->pdfFacade->expects($this->once()) 220 | ->method('render') 221 | ->with($responseContent) 222 | ->will($this->returnValue($contentStub)); 223 | } 224 | 225 | $event = new FilterResponseEventStub($this->request, $responseStub); 226 | 227 | $this->listener->onKernelResponse($event); 228 | 229 | $response = $event->getResponse(); 230 | 231 | $this->assertEquals($contentStub, $response->getContent()); 232 | } 233 | 234 | public function booleanPairProvider() 235 | { 236 | return array( 237 | array(false, false), 238 | array(true, true), 239 | array(true, false), 240 | ); 241 | } 242 | 243 | private function expectPdfFacadeBuilding(Pdf $annotation) 244 | { 245 | $this->pdfFacadeBuilder->expects($this->once()) 246 | ->method('setDocumentParserType') 247 | ->with($annotation->documentParserType) 248 | ->will($this->returnValue($this->pdfFacadeBuilder)); 249 | $this->pdfFacadeBuilder->expects($this->once()) 250 | ->method('build') 251 | ->will($this->returnValue($this->pdfFacade)); 252 | } 253 | 254 | /** 255 | * @test 256 | */ 257 | public function setResponseContentTypeAndRequestFormatOnException() 258 | { 259 | $annotation = new Pdf(array('enableCache' => false)); 260 | $this->requestAttributes->expects($this->once()) 261 | ->method('get') 262 | ->with('_pdf') 263 | ->will($this->returnValue($annotation)); 264 | 265 | $this->expectPdfFacadeBuilding($annotation); 266 | 267 | $exception = new ParseException(); 268 | 269 | $this->pdfFacade->expects($this->once()) 270 | ->method('render') 271 | ->will($this->throwException($exception)); 272 | 273 | $responseStub = new Response(); 274 | $event = new FilterResponseEventStub($this->request, $responseStub); 275 | 276 | try 277 | { 278 | $this->listener->onKernelResponse($event); 279 | $this->fail('exception expected'); 280 | } 281 | catch(ParseException $e) 282 | { 283 | $this->assertEquals('text/html', $responseStub->headers->get('content-type')); 284 | $this->assertEquals('html', $this->request->getRequestFormat('pdf')); 285 | } 286 | } 287 | 288 | /** 289 | * @test 290 | */ 291 | public function useStylesheetFromAnnotation() 292 | { 293 | $stylesheetPath = 'some path'; 294 | 295 | $annotation = new Pdf(array('stylesheet' => $stylesheetPath, 'enableCache' => false)); 296 | $this->requestAttributes->expects($this->once()) 297 | ->method('get') 298 | ->with('_pdf') 299 | ->will($this->returnValue($annotation)); 300 | 301 | $stylesheetContent = 'stylesheet content'; 302 | 303 | $this->templatingEngine->expects($this->once()) 304 | ->method('render') 305 | ->with($stylesheetPath) 306 | ->will($this->returnValue($stylesheetContent)); 307 | 308 | $this->pdfFacadeBuilder->expects($this->once()) 309 | ->method('setDocumentParserType') 310 | ->with($annotation->documentParserType) 311 | ->will($this->returnValue($this->pdfFacadeBuilder)); 312 | $this->pdfFacadeBuilder->expects($this->once()) 313 | ->method('build') 314 | ->will($this->returnValue($this->pdfFacade)); 315 | 316 | $this->pdfFacade->expects($this->once()) 317 | ->method('render') 318 | ->with($this->anything(), $stylesheetContent); 319 | 320 | $event = new FilterResponseEventStub($this->request, new Response()); 321 | $this->listener->onKernelResponse($event); 322 | } 323 | 324 | /** 325 | * @test 326 | */ 327 | public function breakInvocationIfControllerIsEmpty() 328 | { 329 | $this->request->expects($this->once()) 330 | ->method('get') 331 | ->with('_format') 332 | ->will($this->returnValue('pdf')); 333 | 334 | $this->controllerEvent->expects($this->once()) 335 | ->method('getController') 336 | ->will($this->returnValue(array())); 337 | 338 | $this->reflactionFactory->expects($this->never()) 339 | ->method('createMethod'); 340 | 341 | $this->listener->onKernelController($this->controllerEvent); 342 | } 343 | } 344 | 345 | class FilterResponseEventStub extends FilterResponseEvent 346 | { 347 | private $request; 348 | private $response; 349 | 350 | public function __construct($request, $response) 351 | { 352 | $this->request = $request; 353 | $this->response = $response; 354 | } 355 | 356 | public function getResponse() 357 | { 358 | return $this->response; 359 | } 360 | 361 | public function setResponse(Response $response) 362 | { 363 | $this->response = $response; 364 | } 365 | 366 | public function getRequest() 367 | { 368 | return $this->request; 369 | } 370 | } -------------------------------------------------------------------------------- /Resources/views/Example/markdown.pdf.twig: -------------------------------------------------------------------------------- 1 | PHP Markdown 2 | ============ 3 | 4 | Version 1.0.1m - Sat 21 Jun 2008 5 | 6 | by Michel Fortin 7 | 8 | 9 | based on work by John Gruber 10 | 11 | 12 | 13 | Introduction 14 | ------------ 15 | 16 | Markdown is a text-to-HTML conversion tool for web writers. Markdown 17 | allows you to write using an easy-to-read, easy-to-write plain text 18 | format, then convert it to structurally valid XHTML (or HTML). 19 | 20 | "Markdown" is two things: a plain text markup syntax, and a software 21 | tool, written in Perl, that converts the plain text markup to HTML. 22 | PHP Markdown is a port to PHP of the original Markdown program by 23 | John Gruber. 24 | 25 | PHP Markdown can work as a plug-in for WordPress and bBlog, as a 26 | modifier for the Smarty templating engine, or as a remplacement for 27 | textile formatting in any software that support textile. 28 | 29 | Full documentation of Markdown's syntax is available on John's 30 | Markdown page: 31 | 32 | 33 | Installation and Requirement 34 | ---------------------------- 35 | 36 | PHP Markdown requires PHP version 4.0.5 or later. 37 | 38 | 39 | ### WordPress ### 40 | 41 | PHP Markdown works with [WordPress][wp], version 1.2 or later. 42 | 43 | [wp]: http://wordpress.org/ 44 | 45 | 1. To use PHP Markdown with WordPress, place the "makrdown.php" file 46 | in the "plugins" folder. This folder is located inside 47 | "wp-content" at the root of your site: 48 | 49 | (site home)/wp-content/plugins/ 50 | 51 | 2. Activate the plugin with the administrative interface of 52 | WordPress. In the "Plugins" section you will now find Markdown. 53 | To activate the plugin, click on the "Activate" button on the 54 | same line than Markdown. Your entries will now be formatted by 55 | PHP Markdown. 56 | 57 | 3. To post Markdown content, you'll first have to disable the 58 | "visual" editor in the User section of WordPress. 59 | 60 | You can configure PHP Markdown to not apply to the comments on your 61 | WordPress weblog. See the "Configuration" section below. 62 | 63 | It is not possible at this time to apply a different set of 64 | filters to different entries. All your entries will be formated by 65 | PHP Markdown. This is a limitation of WordPress. If your old entries 66 | are written in HTML (as opposed to another formatting syntax, like 67 | Textile), they'll probably stay fine after installing Markdown. 68 | 69 | 70 | ### bBlog ### 71 | 72 | PHP Markdown also works with [bBlog][bb]. 73 | 74 | [bb]: http://www.bblog.com/ 75 | 76 | To use PHP Markdown with bBlog, rename "markdown.php" to 77 | "modifier.markdown.php" and place the file in the "bBlog_plugins" 78 | folder. This folder is located inside the "bblog" directory of 79 | your site, like this: 80 | 81 | (site home)/bblog/bBlog_plugins/modifier.markdown.php 82 | 83 | Select "Markdown" as the "Entry Modifier" when you post a new 84 | entry. This setting will only apply to the entry you are editing. 85 | 86 | 87 | ### Replacing Textile in TextPattern ### 88 | 89 | [TextPattern][tp] use [Textile][tx] to format your text. You can 90 | replace Textile by Markdown in TextPattern without having to change 91 | any code by using the *Texitle Compatibility Mode*. This may work 92 | with other software that expect Textile too. 93 | 94 | [tx]: http://www.textism.com/tools/textile/ 95 | [tp]: http://www.textpattern.com/ 96 | 97 | 1. Rename the "markdown.php" file to "classTextile.php". This will 98 | make PHP Markdown behave as if it was the actual Textile parser. 99 | 100 | 2. Replace the "classTextile.php" file TextPattern installed in your 101 | web directory. It can be found in the "lib" directory: 102 | 103 | (site home)/textpattern/lib/ 104 | 105 | Contrary to Textile, Markdown does not convert quotes to curly ones 106 | and does not convert multiple hyphens (`--` and `---`) into en- and 107 | em-dashes. If you use PHP Markdown in Textile Compatibility Mode, you 108 | can solve this problem by installing the "smartypants.php" file from 109 | [PHP SmartyPants][psp] beside the "classTextile.php" file. The Textile 110 | Compatibility Mode function will use SmartyPants automatically without 111 | further modification. 112 | 113 | [psp]: http://michelf.com/projects/php-smartypants/ 114 | 115 | 116 | ### Updating Markdown in Other Programs ### 117 | 118 | Many web applications now ship with PHP Markdown, or have plugins to 119 | perform the conversion to HTML. You can update PHP Markdown in many of 120 | these programs by swapping the old "markdown.php" file for the new one. 121 | 122 | Here is a short non-exhaustive list of some programs and where they 123 | hide the "markdown.php" file. 124 | 125 | | Program | Path to Markdown 126 | | ------- | ---------------- 127 | | [Pivot][] | `(site home)/pivot/includes/markdown/markdown.php` 128 | 129 | If you're unsure if you can do this with your application, ask the 130 | developer, or wait for the developer to update his application or 131 | plugin with the new version of PHP Markdown. 132 | 133 | [Pivot]: http://pivotlog.net/ 134 | 135 | 136 | ### In Your Own Programs ### 137 | 138 | You can use PHP Markdown easily in your current PHP program. Simply 139 | include the file and then call the Markdown function on the text you 140 | want to convert: 141 | 142 | include_once "markdown.php"; 143 | $my_html = Markdown($my_text); 144 | 145 | If you wish to use PHP Markdown with another text filter function 146 | built to parse HTML, you should filter the text *after* the Markdown 147 | function call. This is an example with [PHP SmartyPants][psp]: 148 | 149 | $my_html = SmartyPants(Markdown($my_text)); 150 | 151 | 152 | ### With Smarty ### 153 | 154 | If your program use the [Smarty][sm] template engine, PHP Markdown 155 | can now be used as a modifier for your templates. Rename "markdown.php" 156 | to "modifier.markdown.php" and put it in your smarty plugins folder. 157 | 158 | [sm]: http://smarty.php.net/ 159 | 160 | If you are using MovableType 3.1 or later, the Smarty plugin folder is 161 | located at `(MT CGI root)/php/extlib/smarty/plugins`. This will allow 162 | Markdown to work on dynamic pages. 163 | 164 | 165 | Configuration 166 | ------------- 167 | 168 | By default, PHP Markdown produces XHTML output for tags with empty 169 | elements. E.g.: 170 | 171 |
172 | 173 | Markdown can be configured to produce HTML-style tags; e.g.: 174 | 175 |
176 | 177 | To do this, you must edit the "MARKDOWN_EMPTY_ELEMENT_SUFFIX" 178 | definition below the "Global default settings" header at the start of 179 | the "markdown.php" file. 180 | 181 | 182 | ### WordPress-Specific Settings ### 183 | 184 | By default, the Markdown plugin applies to both posts and comments on 185 | your WordPress weblog. To deactivate one or the other, edit the 186 | `MARKDOWN_WP_POSTS` or `MARKDOWN_WP_COMMENTS` definitions under the 187 | "WordPress settings" header at the start of the "markdown.php" file. 188 | 189 | 190 | Bugs 191 | ---- 192 | 193 | To file bug reports please send email to: 194 | 195 | 196 | Please include with your report: (1) the example input; (2) the output you 197 | expected; (3) the output PHP Markdown actually produced. 198 | 199 | 200 | Version History 201 | --------------- 202 | 203 | 1.0.1n (10 Oct 2009): 204 | 205 | * Enabled reference-style shortcut links. Now you can write reference-style 206 | links with less brakets: 207 | 208 | This is [my website]. 209 | 210 | [my website]: http://example.com/ 211 | 212 | This was added in the 1.0.2 betas, but commented out in the 1.0.1 branch, 213 | waiting for the feature to be officialized. [But half of the other Markdown 214 | implementations are supporting this syntax][half], so it makes sense for 215 | compatibility's sake to allow it in PHP Markdown too. 216 | 217 | [half]: http://babelmark.bobtfish.net/?markdown=This+is+%5Bmy+website%5D.%0D%0A%09%09%0D%0A%5Bmy+website%5D%3A+http%3A%2F%2Fexample.com%2F%0D%0A&src=1&dest=2 218 | 219 | * Now accepting many valid email addresses in autolinks that were 220 | previously rejected, such as: 221 | 222 | 223 | 224 | <"abc@def"@example.com> 225 | <"Fred Bloggs"@example.com> 226 | 227 | 228 | * Now accepting spaces in URLs for inline and reference-style links. Such 229 | URLs need to be surrounded by angle brakets. For instance: 230 | 231 | [link text]( "optional title") 232 | 233 | [link text][ref] 234 | [ref]: "optional title" 235 | 236 | There is still a quirk which may prevent this from working correctly with 237 | relative URLs in inline-style links however. 238 | 239 | * Fix for adjacent list of different kind where the second list could 240 | end as a sublist of the first when not separated by an empty line. 241 | 242 | * Fixed a bug where inline-style links wouldn't be recognized when the link 243 | definition contains a line break between the url and the title. 244 | 245 | * Fixed a bug where tags where the name contains an underscore aren't parsed 246 | correctly. 247 | 248 | * Fixed some corner-cases mixing underscore-ephasis and asterisk-emphasis. 249 | 250 | 251 | 1.0.1m (21 Jun 2008): 252 | 253 | * Lists can now have empty items. 254 | 255 | * Rewrote the emphasis and strong emphasis parser to fix some issues 256 | with odly placed and overlong markers. 257 | 258 | 259 | 1.0.1l (11 May 2008): 260 | 261 | * Now removing the UTF-8 BOM at the start of a document, if present. 262 | 263 | * Now accepting capitalized URI schemes (such as HTTP:) in automatic 264 | links, such as ``. 265 | 266 | * Fixed a problem where `` was seen as a horizontal 267 | rule instead of an automatic link. 268 | 269 | * Fixed an issue where some characters in Markdown-generated HTML 270 | attributes weren't properly escaped with entities. 271 | 272 | * Fix for code blocks as first element of a list item. Previously, 273 | this didn't create any code block for item 2: 274 | 275 | * Item 1 (regular paragraph) 276 | 277 | * Item 2 (code block) 278 | 279 | * A code block starting on the second line of a document wasn't seen 280 | as a code block. This has been fixed. 281 | 282 | * Added programatically-settable parser properties `predef_urls` and 283 | `predef_titles` for predefined URLs and titles for reference-style 284 | links. To use this, your PHP code must call the parser this way: 285 | 286 | $parser = new Markdwon_Parser; 287 | $parser->predef_urls = array('linkref' => 'http://example.com'); 288 | $html = $parser->transform($text); 289 | 290 | You can then use the URL as a normal link reference: 291 | 292 | [my link][linkref] 293 | [my link][linkRef] 294 | 295 | Reference names in the parser properties *must* be lowercase. 296 | Reference names in the Markdown source may have any case. 297 | 298 | * Added `setup` and `teardown` methods which can be used by subclassers 299 | as hook points to arrange the state of some parser variables before and 300 | after parsing. 301 | 302 | 303 | 1.0.1k (26 Sep 2007): 304 | 305 | * Fixed a problem introduced in 1.0.1i where three or more identical 306 | uppercase letters, as well as a few other symbols, would trigger 307 | a horizontal line. 308 | 309 | 310 | 1.0.1j (4 Sep 2007): 311 | 312 | * Fixed a problem introduced in 1.0.1i where the closing `code` and 313 | `pre` tags at the end of a code block were appearing in the wrong 314 | order. 315 | 316 | * Overriding configuration settings by defining constants from an 317 | external before markdown.php is included is now possible without 318 | producing a PHP warning. 319 | 320 | 321 | 1.0.1i (31 Aug 2007): 322 | 323 | * Fixed a problem where an escaped backslash before a code span 324 | would prevent the code span from being created. This should now 325 | work as expected: 326 | 327 | Litteral backslash: \\`code span` 328 | 329 | * Overall speed improvements, especially with long documents. 330 | 331 | 332 | 1.0.1h (3 Aug 2007): 333 | 334 | * Added two properties (`no_markup` and `no_entities`) to the parser 335 | allowing HTML tags and entities to be disabled. 336 | 337 | * Fix for a problem introduced in 1.0.1g where posting comments in 338 | WordPress would trigger PHP warnings and cause some markup to be 339 | incorrectly filtered by the kses filter in WordPress. 340 | 341 | 342 | 1.0.1g (3 Jul 2007): 343 | 344 | * Fix for PHP 5 compiled without the mbstring module. Previous fix to 345 | calculate the length of UTF-8 strings in `detab` when `mb_strlen` is 346 | not available was only working with PHP 4. 347 | 348 | * Fixed a problem with WordPress 2.x where full-content posts in RSS feeds 349 | were not processed correctly by Markdown. 350 | 351 | * Now supports URLs containing literal parentheses for inline links 352 | and images, such as: 353 | 354 | [WIMP](http://en.wikipedia.org/wiki/WIMP_(computing)) 355 | 356 | Such parentheses may be arbitrarily nested, but must be 357 | balanced. Unbalenced parentheses are allowed however when the URL 358 | when escaped or when the URL is enclosed in angle brakets `<>`. 359 | 360 | * Fixed a performance problem where the regular expression for strong 361 | emphasis introduced in version 1.0.1d could sometime be long to process, 362 | give slightly wrong results, and in some circumstances could remove 363 | entirely the content for a whole paragraph. 364 | 365 | * Some change in version 1.0.1d made possible the incorrect nesting of 366 | anchors within each other. This is now fixed. 367 | 368 | * Fixed a rare issue where certain MD5 hashes in the content could 369 | be changed to their corresponding text. For instance, this: 370 | 371 | The MD5 value for "+" is "26b17225b626fb9238849fd60eabdf60". 372 | 373 | was incorrectly changed to this in previous versions of PHP Markdown: 374 | 375 |

The MD5 value for "+" is "+".

376 | 377 | * Now convert escaped characters to their numeric character 378 | references equivalent. 379 | 380 | This fix an integration issue with SmartyPants and backslash escapes. 381 | Since Markdown and SmartyPants have some escapable characters in common, 382 | it was sometime necessary to escape them twice. Previously, two 383 | backslashes were sometime required to prevent Markdown from "eating" the 384 | backslash before SmartyPants sees it: 385 | 386 | Here are two hyphens: \\-- 387 | 388 | Now, only one backslash will do: 389 | 390 | Here are two hyphens: \-- 391 | 392 | 393 | 1.0.1f (7 Feb 2007): 394 | 395 | * Fixed an issue with WordPress where manually-entered excerpts, but 396 | not the auto-generated ones, would contain nested paragraphs. 397 | 398 | * Fixed an issue introduced in 1.0.1d where headers and blockquotes 399 | preceded too closely by a paragraph (not separated by a blank line) 400 | where incorrectly put inside the paragraph. 401 | 402 | * Fixed an issue introduced in 1.0.1d in the tokenizeHTML method where 403 | two consecutive code spans would be merged into one when together they 404 | form a valid tag in a multiline paragraph. 405 | 406 | * Fixed an long-prevailing issue where blank lines in code blocks would 407 | be doubled when the code block is in a list item. 408 | 409 | This was due to the list processing functions relying on artificially 410 | doubled blank lines to correctly determine when list items should 411 | contain block-level content. The list item processing model was thus 412 | changed to avoid the need for double blank lines. 413 | 414 | * Fixed an issue with `<% asp-style %>` instructions used as inline 415 | content where the opening `<` was encoded as `<`. 416 | 417 | * Fixed a parse error occuring when PHP is configured to accept 418 | ASP-style delimiters as boundaries for PHP scripts. 419 | 420 | * Fixed a bug introduced in 1.0.1d where underscores in automatic links 421 | got swapped with emphasis tags. 422 | 423 | 424 | 1.0.1e (28 Dec 2006) 425 | 426 | * Added support for internationalized domain names for email addresses in 427 | automatic link. Improved the speed at which email addresses are converted 428 | to entities. Thanks to Milian Wolff for his optimisations. 429 | 430 | * Made deterministic the conversion to entities of email addresses in 431 | automatic links. This means that a given email address will always be 432 | encoded the same way. 433 | 434 | * PHP Markdown will now use its own function to calculate the length of an 435 | UTF-8 string in `detab` when `mb_strlen` is not available instead of 436 | giving a fatal error. 437 | 438 | 439 | 1.0.1d (1 Dec 2006) 440 | 441 | * Fixed a bug where inline images always had an empty title attribute. The 442 | title attribute is now present only when explicitly defined. 443 | 444 | * Link references definitions can now have an empty title, previously if the 445 | title was defined but left empty the link definition was ignored. This can 446 | be useful if you want an empty title attribute in images to hide the 447 | tooltip in Internet Explorer. 448 | 449 | * Made `detab` aware of UTF-8 characters. UTF-8 multi-byte sequences are now 450 | correctly mapped to one character instead of the number of bytes. 451 | 452 | * Fixed a small bug with WordPress where WordPress' default filter `wpautop` 453 | was not properly deactivated on comment text, resulting in hard line breaks 454 | where Markdown do not prescribes them. 455 | 456 | * Added a `TextileRestrited` method to the textile compatibility mode. There 457 | is no restriction however, as Markdown does not have a restricted mode at 458 | this point. This should make PHP Markdown work again in the latest 459 | versions of TextPattern. 460 | 461 | * Converted PHP Markdown to a object-oriented design. 462 | 463 | * Changed span and block gamut methods so that they loop over a 464 | customizable list of methods. This makes subclassing the parser a more 465 | interesting option for creating syntax extensions. 466 | 467 | * Also added a "document" gamut loop which can be used to hook document-level 468 | methods (like for striping link definitions). 469 | 470 | * Changed all methods which were inserting HTML code so that they now return 471 | a hashed representation of the code. New methods `hashSpan` and `hashBlock` 472 | are used to hash respectivly span- and block-level generated content. This 473 | has a couple of significant effects: 474 | 475 | 1. It prevents invalid nesting of Markdown-generated elements which 476 | could occur occuring with constructs like `*something [link*][1]`. 477 | 2. It prevents problems occuring with deeply nested lists on which 478 | paragraphs were ill-formed. 479 | 3. It removes the need to call `hashHTMLBlocks` twice during the the 480 | block gamut. 481 | 482 | Hashes are turned back to HTML prior output. 483 | 484 | * Made the block-level HTML parser smarter using a specially-crafted regular 485 | expression capable of handling nested tags. 486 | 487 | * Solved backtick issues in tag attributes by rewriting the HTML tokenizer to 488 | be aware of code spans. All these lines should work correctly now: 489 | 490 | bar 491 | bar 492 | `` 493 | 494 | * Changed the parsing of HTML comments to match simply from `` 495 | instead using of the more complicated SGML-style rule with paired `--`. 496 | This is how most browsers parse comments and how XML defines them too. 497 | 498 | * `
` has been added to the list of block-level elements and is now 499 | treated as an HTML block instead of being wrapped within paragraph tags. 500 | 501 | * Now only trim trailing newlines from code blocks, instead of trimming 502 | all trailing whitespace characters. 503 | 504 | * Fixed bug where this: 505 | 506 | [text](http://m.com "title" ) 507 | 508 | wasn't working as expected, because the parser wasn't allowing for spaces 509 | before the closing paren. 510 | 511 | * Filthy hack to support markdown='1' in div tags. 512 | 513 | * _DoAutoLinks() now supports the 'dict://' URL scheme. 514 | 515 | * PHP- and ASP-style processor instructions are now protected as 516 | raw HTML blocks. 517 | 518 | 519 | <% ... %> 520 | 521 | * Fix for escaped backticks still triggering code spans: 522 | 523 | There are two raw backticks here: \` and here: \`, not a code span 524 | 525 | 526 | 1.0.1c (9 Dec 2005) 527 | 528 | * Fixed a problem occurring with PHP 5.1.1 due to a small 529 | change to strings variable replacement behaviour in 530 | this version. 531 | 532 | 533 | 1.0.1b (6 Jun 2005) 534 | 535 | * Fixed a bug where an inline image followed by a reference link would 536 | give a completely wrong result. 537 | 538 | * Fix for escaped backticks still triggering code spans: 539 | 540 | There are two raw backticks here: \` and here: \`, not a code span 541 | 542 | * Fix for an ordered list following an unordered list, and the 543 | reverse. There is now a loop in _DoList that does the two 544 | separately. 545 | 546 | * Fix for nested sub-lists in list-paragraph mode. Previously we got 547 | a spurious extra level of `

` tags for something like this: 548 | 549 | * this 550 | 551 | * sub 552 | 553 | that 554 | 555 | * Fixed some incorrect behaviour with emphasis. This will now work 556 | as it should: 557 | 558 | *test **thing*** 559 | **test *thing*** 560 | ***thing* test** 561 | ***thing** test* 562 | 563 | Name: __________ 564 | Address: _______ 565 | 566 | * Correct a small bug in `_TokenizeHTML` where a Doctype declaration 567 | was not seen as HTML. 568 | 569 | * Major rewrite of the WordPress integration code that should 570 | correct many problems by preventing default WordPress filters from 571 | tampering with Markdown-formatted text. More details here: 572 | 573 | 574 | 575 | 1.0.1a (15 Apr 2005) 576 | 577 | * Fixed an issue where PHP warnings were trigged when converting 578 | text with list items running on PHP 4.0.6. This was comming from 579 | the `rtrim` function which did not support the second argument 580 | prior version 4.1. Replaced by a regular expression. 581 | 582 | * Markdown now filter correctly post excerpts and comment 583 | excerpts in WordPress. 584 | 585 | * Automatic links and some code sample were "corrected" by 586 | the balenceTag filter in WordPress meant to ensure HTML 587 | is well formed. This new version of PHP Markdown postpone this 588 | filter so that it runs after Markdown. 589 | 590 | * Blockquote syntax and some code sample were stripped by 591 | a new WordPress 1.5 filter meant to remove unwanted HTML 592 | in comments. This new version of PHP Markdown postpone this 593 | filter so that it runs after Markdown. 594 | 595 | 596 | 1.0.1 (16 Dec 2004): 597 | 598 | * Changed the syntax rules for code blocks and spans. Previously, 599 | backslash escapes for special Markdown characters were processed 600 | everywhere other than within inline HTML tags. Now, the contents of 601 | code blocks and spans are no longer processed for backslash escapes. 602 | This means that code blocks and spans are now treated literally, 603 | with no special rules to worry about regarding backslashes. 604 | 605 | **IMPORTANT**: This breaks the syntax from all previous versions of 606 | Markdown. Code blocks and spans involving backslash characters will 607 | now generate different output than before. 608 | 609 | Implementation-wise, this change was made by moving the call to 610 | `_EscapeSpecialChars()` from the top-level `Markdown()` function to 611 | within `_RunSpanGamut()`. 612 | 613 | * Significants performance improvement in `_DoHeader`, `_Detab` 614 | and `_TokenizeHTML`. 615 | 616 | * Added `>`, `+`, and `-` to the list of backslash-escapable 617 | characters. These should have been done when these characters 618 | were added as unordered list item markers. 619 | 620 | * Inline links using `<` and `>` URL delimiters weren't working: 621 | 622 | like [this]() 623 | 624 | Fixed by moving `_DoAutoLinks()` after `_DoAnchors()` in 625 | `_RunSpanGamut()`. 626 | 627 | * Fixed bug where auto-links were being processed within code spans: 628 | 629 | like this: `` 630 | 631 | Fixed by moving `_DoAutoLinks()` from `_RunBlockGamut()` to 632 | `_RunSpanGamut()`. 633 | 634 | * Sort-of fixed a bug where lines in the middle of hard-wrapped 635 | paragraphs, which lines look like the start of a list item, 636 | would accidentally trigger the creation of a list. E.g. a 637 | paragraph that looked like this: 638 | 639 | I recommend upgrading to version 640 | 8. Oops, now this line is treated 641 | as a sub-list. 642 | 643 | This is fixed for top-level lists, but it can still happen for 644 | sub-lists. E.g., the following list item will not be parsed 645 | properly: 646 | 647 | * I recommend upgrading to version 648 | 8. Oops, now this line is treated 649 | as a sub-list. 650 | 651 | Given Markdown's list-creation rules, I'm not sure this can 652 | be fixed. 653 | 654 | * Fix for horizontal rules preceded by 2 or 3 spaces or followed by 655 | trailing spaces and tabs. 656 | 657 | * Standalone HTML comments are now handled; previously, they'd get 658 | wrapped in a spurious `

` tag. 659 | 660 | * `_HashHTMLBlocks()` now tolerates trailing spaces and tabs following 661 | HTML comments and `


` tags. 662 | 663 | * Changed special case pattern for hashing `
` tags in 664 | `_HashHTMLBlocks()` so that they must occur within three spaces 665 | of left margin. (With 4 spaces or a tab, they should be 666 | code blocks, but weren't before this fix.) 667 | 668 | * Auto-linked email address can now optionally contain 669 | a 'mailto:' protocol. I.e. these are equivalent: 670 | 671 | 672 | 673 | 674 | * Fixed annoying bug where nested lists would wind up with 675 | spurious (and invalid) `

` tags. 676 | 677 | * Changed `_StripLinkDefinitions()` so that link definitions must 678 | occur within three spaces of the left margin. Thus if you indent 679 | a link definition by four spaces or a tab, it will now be a code 680 | block. 681 | 682 | * You can now write empty links: 683 | 684 | [like this]() 685 | 686 | and they'll be turned into anchor tags with empty href attributes. 687 | This should have worked before, but didn't. 688 | 689 | * `***this***` and `___this___` are now turned into 690 | 691 | this 692 | 693 | Instead of 694 | 695 | this 696 | 697 | which isn't valid. 698 | 699 | * Fixed problem for links defined with urls that include parens, e.g.: 700 | 701 | [1]: http://sources.wikipedia.org/wiki/Middle_East_Policy_(Chomsky) 702 | 703 | "Chomsky" was being erroneously treated as the URL's title. 704 | 705 | * Double quotes in the title of an inline link used to give strange 706 | results (incorrectly made entities). Fixed. 707 | 708 | * Tabs are now correctly changed into spaces. Previously, only 709 | the first tab was converted. In code blocks, the second one was too, 710 | but was not always correctly aligned. 711 | 712 | * Fixed a bug where a tab character inserted after a quote on the same 713 | line could add a slash before the quotes. 714 | 715 | This is "before" [tab] and "after" a tab. 716 | 717 | Previously gave this result: 718 | 719 |

This is \"before\" [tab] and "after" a tab.

720 | 721 | * Removed a call to `htmlentities`. This fixes a bug where multibyte 722 | characters present in the title of a link reference could lead to 723 | invalid utf-8 characters. 724 | 725 | * Changed a regular expression in `_TokenizeHTML` that could lead to 726 | a segmentation fault with PHP 4.3.8 on Linux. 727 | 728 | * Fixed some notices that could show up if PHP error reporting 729 | E_NOTICE flag was set. 730 | 731 | 732 | Copyright and License 733 | --------------------- 734 | 735 | PHP Markdown 736 | Copyright (c) 2004-2009 Michel Fortin 737 | 738 | All rights reserved. 739 | 740 | Based on Markdown 741 | Copyright (c) 2003-2006 John Gruber 742 | 743 | All rights reserved. 744 | 745 | Redistribution and use in source and binary forms, with or without 746 | modification, are permitted provided that the following conditions are 747 | met: 748 | 749 | * Redistributions of source code must retain the above copyright notice, 750 | this list of conditions and the following disclaimer. 751 | 752 | * Redistributions in binary form must reproduce the above copyright 753 | notice, this list of conditions and the following disclaimer in the 754 | documentation and/or other materials provided with the distribution. 755 | 756 | * Neither the name "Markdown" nor the names of its contributors may 757 | be used to endorse or promote products derived from this software 758 | without specific prior written permission. 759 | 760 | This software is provided by the copyright holders and contributors "as 761 | is" and any express or implied warranties, including, but not limited 762 | to, the implied warranties of merchantability and fitness for a 763 | particular purpose are disclaimed. In no event shall the copyright owner 764 | or contributors be liable for any direct, indirect, incidental, special, 765 | exemplary, or consequential damages (including, but not limited to, 766 | procurement of substitute goods or services; loss of use, data, or 767 | profits; or business interruption) however caused and on any theory of 768 | liability, whether in contract, strict liability, or tort (including 769 | negligence or otherwise) arising in any way out of the use of this 770 | software, even if advised of the possibility of such damage. 771 | --------------------------------------------------------------------------------