/**
  * @dataProvider getConfigFileName
  */
 public function testEasybookConfiguration($configFileName)
 {
     $this->app = $this->getApplication($configFileName);
     $config = $this->app['configurator']->loadBookFileConfiguration(null);
     $easybookConfig = isset($config['easybook']) ? $config['easybook']['parameters'] : array();
     $expectedConfiguration = Toolkit::array_deep_merge_and_replace($this->getEasybookDefaultParameters(), $easybookConfig);
     $this->assertEasybookConfiguration($expectedConfiguration);
 }
Esempio n. 2
0
 public function testUuidMethodGeneratesRandomIds()
 {
     $uuids = array();
     while (count($uuids) < 1000) {
         $uuids[] = Toolkit::uuid();
     }
     $this->assertEquals(count($uuids), count(array_unique($uuids)));
 }
Esempio n. 3
0
 public function testBookPublish()
 {
     $console = new ConsoleApplication($this->app);
     // find the test books
     $books = $this->app['finder']->directories()->name('book*')->depth(0)->in(__DIR__ . '/fixtures');
     foreach ($books as $book) {
         $this->markTestSkipped('Temporarily marked as skipeed until we update these tests to be less fragile with whitespaces.');
         $slug = $book->getFileName();
         if ('book5' == $slug && (version_compare(phpversion(), '5.4.0', '<') || !extension_loaded('intl'))) {
             $this->markTestSkipped('This test requires PHP 5.4.0+ with the intl extension enabled (the book contains a lot of non-latin characters that need the native PHP transliterator)');
         }
         // mirror test book contents in temp dir
         $this->filesystem->mirror(__DIR__ . '/fixtures/' . $slug . '/input', $this->tmpDir . '/' . $slug);
         // look for and publish all the book editions
         $bookConfig = Yaml::parse($this->tmpDir . '/' . $slug . '/config.yml');
         $editionNames = array_keys($bookConfig['book']['editions']);
         foreach ($editionNames as $editionName) {
             // publish each book edition
             $input = new ArrayInput(array('command' => 'publish', 'slug' => $slug, 'edition' => $editionName, '--dir' => $this->tmpDir));
             $console->find('publish')->run($input, new NullOutput());
             // assert that generated files are exactly the same as expected
             $generatedFiles = $this->app['finder']->files()->notName('.gitignore')->in($this->tmpDir . '/' . $slug . '/Output/' . $editionName);
             foreach ($generatedFiles as $file) {
                 if ('epub' == $file->getExtension()) {
                     // unzip both files to compare its contents
                     $workDir = $this->tmpDir . '/' . $slug . '/unzip/' . $editionName;
                     $generated = $workDir . '/generated';
                     $expected = $workDir . '/expected';
                     Toolkit::unzip($file->getRealPath(), $generated);
                     Toolkit::unzip(__DIR__ . '/fixtures/' . $slug . '/expected/' . $editionName . '/' . $file->getRelativePathname(), $expected);
                     // assert that generated files are exactly the same as expected
                     $genFiles = $this->app['finder']->files()->notName('.gitignore')->in($generated);
                     foreach ($genFiles as $genFile) {
                         $this->assertFileEquals($expected . '/' . $genFile->getRelativePathname(), $genFile->getPathname(), sprintf("ERROR on {$book}:\n '%s' file (into ZIP file '%s') not properly generated", $genFile->getRelativePathname(), $file->getPathName()));
                     }
                     // assert that all required files are generated
                     $this->checkForMissingFiles($expected, $generated);
                 } else {
                     $this->assertFileEquals(__DIR__ . '/fixtures/' . $slug . '/expected/' . $editionName . '/' . $file->getRelativePathname(), $file->getPathname(), sprintf("'%s' file not properly generated", $file->getPathname()));
                 }
             }
             // assert that all required files are generated
             $this->checkForMissingFiles(__DIR__ . '/fixtures/' . $slug . '/expected/' . $editionName, $this->tmpDir . '/' . $slug . '/Output/' . $editionName);
             // assert than book publication took less than 5 seconds
             $this->assertLessThan(5, $this->app['app.timer.finish'] - $this->app['app.timer.start'], sprintf("Publication of '%s' edition for '%s' book took more than 5 seconds", $editionName, $slug));
             // reset app state before the next publishing
             $this->app = new Application();
             $console = new ConsoleApplication($this->app);
         }
     }
 }
Esempio n. 4
0
 public function register(Container $app)
 {
     $app['twig.options'] = array('autoescape' => false, 'charset' => $app['app.charset'], 'debug' => $app['app.debug'], 'strict_variables' => $app['app.debug']);
     $app['twig.loader'] = function () use($app) {
         $theme = ucfirst($app->edition('theme'));
         $format = Toolkit::camelize($app->edition('format'), true);
         $loader = new \Twig_Loader_Filesystem($app['app.dir.themes']);
         // Base theme (common styles per edition type)
         // <easybook>/app/Resources/Themes/Base/<edition-type>/Templates/<template-name>.twig
         $baseThemeDir = sprintf('%s/Base/%s/Templates', $app['app.dir.themes'], $format);
         $loader->addPath($baseThemeDir);
         $loader->addPath($baseThemeDir, 'theme');
         $loader->addPath($baseThemeDir, 'theme_base');
         // Book theme (configured per edition in 'config.yml')
         // <easybook>/app/Resources/Themes/<theme>/<edition-type>/Templates/<template-name>.twig
         $bookThemeDir = sprintf('%s/%s/%s/Templates', $app['app.dir.themes'], $theme, $format);
         $loader->prependPath($bookThemeDir);
         $loader->prependPath($bookThemeDir, 'theme');
         $userTemplatePaths = array($app['publishing.dir.templates'], sprintf('%s/%s', $app['publishing.dir.templates'], strtolower($format)), sprintf('%s/%s', $app['publishing.dir.templates'], $app['publishing.edition']));
         foreach ($userTemplatePaths as $path) {
             if (file_exists($path)) {
                 $loader->prependPath($path);
             }
         }
         $defaultContentPaths = array(sprintf('%s/Base/%s/Contents', $app['app.dir.themes'], $format), sprintf('%s/%s/%s/Contents', $app['app.dir.themes'], $theme, $format));
         foreach ($defaultContentPaths as $path) {
             if (file_exists($path)) {
                 $loader->prependPath($path, 'content');
             }
         }
         return $loader;
     };
     $app['twig'] = function () use($app) {
         $twig = new \Twig_Environment($app['twig.loader'], $app['twig.options']);
         $twig->addExtension(new TwigCssExtension());
         $twig->addGlobal('app', $app);
         if (null !== ($bookConfig = $app['publishing.book.config'])) {
             $twig->addGlobal('book', $bookConfig['book']);
             $publishingEdition = $app['publishing.edition'];
             $editions = $app->book('editions');
             $twig->addGlobal('edition', $editions[$publishingEdition]);
         }
         return $twig;
     };
 }
Esempio n. 5
0
 public function build()
 {
     echo sprintf("\nBuilding easybook %s package\n%s\n", $this->version, str_repeat('=', 80));
     // add book script
     $this->addFile(new \SplFileInfo($this->rootDir . '/book'));
     // add autoloaders
     $this->addFile(new \SplFileInfo($this->rootDir . '/vendor/autoload.php'));
     $this->addFile(new \SplFileInfo($this->rootDir . '/vendor/composer/ClassLoader.php'));
     $this->addFile(new \SplFileInfo($this->rootDir . '/vendor/composer/autoload_classmap.php'));
     $this->addFile(new \SplFileInfo($this->rootDir . '/vendor/composer/autoload_namespaces.php'));
     // add resources
     $finder = new Finder();
     $finder->files()->ignoreVCS(true)->notName('.DS_Store')->in($this->rootDir . '/app/Resources');
     foreach ($finder as $file) {
         $this->addFile($file);
     }
     // add sample books
     $finder = new Finder();
     $finder->files()->ignoreVCS(true)->notName('.DS_Store')->exclude('Output')->exclude('Resources')->in(array($this->rootDir . '/doc/easybook-doc-en', $this->rootDir . '/doc/easybook-doc-es'));
     foreach ($finder as $file) {
         $this->addFile($file);
     }
     // add core classes
     $finder = new Finder();
     $finder->files()->ignoreVCS(true)->name('*.php')->notName('.DS_Store')->exclude('Tests')->notName('Builder.php')->in($this->rootDir . '/src');
     foreach ($finder as $file) {
         $this->addFile($file);
     }
     // add vendors
     $finder = new Finder();
     $finder->files()->ignoreVCS(true)->notName('.DS_Store')->notName('README*')->notName('CHANGELOG')->notName('AUTHORS')->notName('create_pear_package.php')->notName('composer.json')->notName('installed.json')->notName('package.xml.tpl')->notName('phpunit.xml.dist')->exclude(array('docs', 'tests', 'twig/bin', 'twig/doc', 'twig/ext', 'twig/test'))->in($this->rootDir . '/vendor');
     foreach ($finder as $file) {
         $this->addFile($file);
     }
     // add license and Readme
     $this->addFile(new \SplFileInfo($this->rootDir . '/LICENSE.md'));
     $this->addFile(new \SplFileInfo($this->rootDir . '/README.md'));
     // compress all files into a single ZIP file
     Toolkit::zip($this->packageDir, './' . $this->zipFile);
     // delete temp directory
     $this->filesystem->remove($this->packageDir);
     echo sprintf("\n %d files added\n\n %s (%.2f MB) package built successfully\n\n", $this->fileCount, $this->zipFile, filesize($this->zipFile) / (1024 * 1024));
 }
Esempio n. 6
0
 public function build($zipFile = null)
 {
     $this->zipFile = $zipFile ?: sprintf('%s/easybook-%s.zip', $this->rootDir, $this->version);
     if (file_exists($this->zipFile)) {
         unlink($this->zipFile);
     }
     // add package files
     $this->addBookScript();
     $this->addAutoloaders();
     $this->addResources();
     $this->addSampleBooks();
     $this->addCommandHelp();
     $this->addCoreClasses();
     $this->addVendors();
     $this->addLicenseAndReadme();
     // compress all files into a single ZIP file
     Toolkit::zip($this->packageDir, $this->zipFile);
     // delete temp directory
     $this->filesystem->remove($this->packageDir);
     echo sprintf("\n %d files added\n\n %s (%.2f MB) package built successfully\n\n", $this->fileCount, $this->zipFile, filesize($this->zipFile) / (1024 * 1024));
 }
Esempio n. 7
0
 public function assembleBook()
 {
     // set the edition id needed for ebook generation
     $this->app->edition('id', $this->app['publishing.id']);
     // variables needed to hold the list of images and fonts of the book
     $bookImages = array();
     $bookFonts = array();
     // prepare the temp directory used to build the book
     $bookTempDir = $this->app['app.dir.cache'] . '/' . $this->app['publishing.book.slug'] . '-' . $this->app['publishing.edition'];
     $this->app->get('filesystem')->mkdir(array($bookTempDir, $bookTempDir . '/book', $bookTempDir . '/book/META-INF', $bookTempDir . '/book/OEBPS', $bookTempDir . '/book/OEBPS/css', $bookTempDir . '/book/OEBPS/images', $bookTempDir . '/book/OEBPS/fonts'));
     // generate easybook CSS file
     if ($this->app->edition('include_styles')) {
         $this->app->renderThemeTemplate('style.css.twig', array('resources_dir' => '..'), $bookTempDir . '/book/OEBPS/css/easybook.css');
         // copy book fonts and prepare font data for ebook manifest
         $this->app->get('filesystem')->copy($this->app['app.dir.resources'] . '/Fonts/Inconsolata/Inconsolata.ttf', $bookTempDir . '/book/OEBPS/fonts/Inconsolata.ttf');
         $bookFonts[] = array('id' => 'font-1', 'filePath' => 'fonts/Inconsolata.ttf', 'mediaType' => 'application/octet-stream');
     }
     // generate custom CSS file
     $customCss = $this->app->getCustomTemplate('style.css');
     if (file_exists($customCss)) {
         $this->app->get('filesystem')->copy($customCss, $bookTempDir . '/book/OEBPS/css/styles.css', true);
     }
     // each book element will generate an HTML page
     // use automatic slugs (chapter-1, chapter-2, ...) instead of
     // semantic slugs (lorem-ipsum, dolor-sit-amet, ...)
     $this->app->set('publishing.slugs', array());
     $items = array();
     foreach ($this->app['publishing.items'] as $item) {
         $pageName = array_key_exists('number', $item['config']) ? $item['config']['element'] . ' ' . $item['config']['number'] : $item['config']['element'];
         $slug = $this->app->get('slugger')->slugify(trim($pageName));
         $item['slug'] = $slug;
         // TODO: document this new item property
         $item['fileName'] = $slug . '.html';
         $items[] = $item;
     }
     // update `publishing items` with the new slug value
     $this->app->set('publishing.items', $items);
     // generate one HTML page for every book item
     $items = array();
     foreach ($this->app['publishing.items'] as $item) {
         $this->app->renderThemeTemplate('chunk.twig', array('item' => $item, 'has_custom_css' => file_exists($customCss)), $bookTempDir . '/book/OEBPS/' . $item['fileName']);
     }
     // copy book images and prepare image data for ebook manifest
     if (file_exists($imagesDir = $this->app['publishing.dir.contents'] . '/images')) {
         $images = $this->app->get('finder')->files()->in($imagesDir);
         $i = 1;
         foreach ($images as $image) {
             $this->app->get('filesystem')->copy($image->getPathName(), $bookTempDir . '/book/OEBPS/images/' . $image->getFileName());
             $bookImages[] = array('id' => 'figure-' . $i++, 'filePath' => 'images/' . $image->getFileName(), 'mediaType' => 'image/' . pathinfo($image->getFilename(), PATHINFO_EXTENSION));
         }
     }
     // look for cover images
     $cover = null;
     if (null != ($image = $this->app->getCustomCoverImage())) {
         list($width, $height, $type) = getimagesize($image);
         $cover = array('height' => $height, 'width' => $width, 'filePath' => 'images/' . basename($image), 'mediaType' => image_type_to_mime_type($type));
         // copy the cover image
         $this->app->get('filesystem')->copy($image, $bookTempDir . '/book/OEBPS/images/' . basename($image));
     }
     // generate book cover
     $this->app->renderThemeTemplate('cover.twig', array('cover' => $cover), $bookTempDir . '/book/OEBPS/titlepage.html');
     // generate OPF file
     $this->app->renderThemeTemplate('content.opf.twig', array('cover' => $cover, 'has_custom_css' => file_exists($customCss), 'fonts' => $bookFonts, 'images' => $bookImages), $bookTempDir . '/book/OEBPS/content.opf');
     // generate NCX file
     $this->app->renderThemeTemplate('toc.ncx.twig', array(), $bookTempDir . '/book/OEBPS/toc.ncx');
     // generate container.xml and mimetype files
     $this->app->renderThemeTemplate('container.xml.twig', array(), $bookTempDir . '/book/META-INF/container.xml');
     $this->app->renderThemeTemplate('mimetype.twig', array(), $bookTempDir . '/book/mimetype');
     // compress book contents as ZIP file and rename to .epub
     // TODO: the name of the book file (book.epub) must be configurable
     Toolkit::zip($bookTempDir . '/book', $bookTempDir . '/book.zip');
     $this->app->get('filesystem')->copy($bookTempDir . '/book.zip', $this->app['publishing.dir.output'] . '/book.epub', true);
     // remove temp directory used to build the book
     $this->app->get('filesystem')->remove($bookTempDir);
 }
Esempio n. 8
0
    public function __construct()
    {
        parent::__construct();
        $app = $this;
        // -- global generic parameters ---------------------------------------
        $this['app.debug'] = false;
        $this['app.charset'] = 'UTF-8';
        $this['app.name'] = 'easybook';
        $this['app.signature'] = <<<SIGNATURE
                     |              |
 ,---.,---.,---.,   .|---.,---.,---.|__/
 |---',---|`---.|   ||   ||   ||   ||  \\
 `---'`---^`---'`---|`---'`---'`---'`   `
                `---'
SIGNATURE;
        // -- global directories location -------------------------------------
        $this['app.dir.base'] = realpath(__DIR__ . '/../../../');
        $this['app.dir.cache'] = $this['app.dir.base'] . '/app/Cache';
        $this['app.dir.doc'] = $this['app.dir.base'] . '/doc';
        $this['app.dir.resources'] = $this['app.dir.base'] . '/app/Resources';
        $this['app.dir.plugins'] = $this['app.dir.base'] . '/src/Easybook/Plugins';
        $this['app.dir.translations'] = $this['app.dir.resources'] . '/Translations';
        $this['app.dir.skeletons'] = $this['app.dir.resources'] . '/Skeletons';
        $this['app.dir.themes'] = $this['app.dir.resources'] . '/Themes';
        // -- console ---------------------------------------------------------
        $this['console.input'] = null;
        $this['console.output'] = null;
        $this['console.dialog'] = null;
        // -- timer -----------------------------------------------------------
        $this['app.timer.start'] = 0.0;
        $this['app.timer.finish'] = 0.0;
        // -- publishing process variables ------------------------------------
        // holds the app theme dir for the current edition
        $this['publishing.dir.app_theme'] = '';
        $this['publishing.dir.book'] = '';
        $this['publishing.dir.contents'] = '';
        $this['publishing.dir.resources'] = '';
        $this['publishing.dir.plugins'] = '';
        $this['publishing.dir.templates'] = '';
        $this['publishing.dir.output'] = '';
        $this['publishing.edition'] = '';
        $this['publishing.items'] = array();
        // the specific item currently being parsed/modified/decorated/...
        $this['publishing.active_item'] = array();
        $this['publishing.active_item.toc'] = array();
        $this['publishing.book.config'] = array('book' => array());
        $this['publishing.book.slug'] = '';
        $this['publishing.book.items'] = array();
        // the real TOC used to generate the book (needed for html_chunked editions)
        $this['publishing.book.toc'] = array();
        // holds all the internal links (used in html_chunked and epub editions)
        $this['publishing.links'] = array();
        $this['publishing.list.images'] = array();
        $this['publishing.list.tables'] = array();
        $this['publishing.edition.id'] = function ($app) {
            if (null !== ($isbn = $app->edition('isbn'))) {
                return array('scheme' => 'isbn', 'value' => $isbn);
            }
            // for ISBN-less books, generate a unique RFC 4211 UUID v4 ID
            return array('scheme' => 'URN', 'value' => Toolkit::uuid());
        };
        // maintained for backwards compatibility
        $this['publishing.id'] = function () {
            trigger_error('The "publishing.id" option is deprecated since version 5.0 and will be removed in the future. Use "publishing.edition.id" instead.', E_USER_DEPRECATED);
        };
        // -- event dispatcher ------------------------------------------------
        $this['dispatcher'] = function () {
            return new EventDispatcher();
        };
        // -- finder ----------------------------------------------------------
        $this['finder'] = $this->factory(function () {
            return new Finder();
        });
        // -- filesystem ------------------------------------------------------
        $this['filesystem'] = $this->factory(function () {
            return new Filesystem();
        });
        // -- configurator ----------------------------------------------------
        $this['configurator'] = function ($app) {
            return new BookConfigurator($app);
        };
        // -- validator -------------------------------------------------------
        $this['validator'] = function ($app) {
            return new Validator($app);
        };
        $this->register(new PublisherServiceProvider());
        $this->register(new ParserServiceProvider());
        $this->register(new TwigServiceProvider());
        $this->register(new PrinceXMLServiceProvider());
        $this->register(new KindleGenServiceProvider());
        $this->register(new SluggerServiceProvider());
        $this->register(new CodeHighlighterServiceProvider());
        // -- labels ---------------------------------------------------------
        $this['labels'] = function () use($app) {
            $labels = Yaml::parse($app['app.dir.translations'] . '/labels.' . $app->book('language') . '.yml');
            // books can define their own labels files
            if (null !== ($customLabelsFile = $app->getCustomLabelsFile())) {
                $customLabels = Yaml::parse($customLabelsFile);
                return Toolkit::array_deep_merge_and_replace($labels, $customLabels);
            }
            return $labels;
        };
        // -- titles ----------------------------------------------------------
        $this['titles'] = function () use($app) {
            $titles = Yaml::parse($app['app.dir.translations'] . '/titles.' . $app->book('language') . '.yml');
            // books can define their own titles files
            if (null !== ($customTitlesFile = $app->getCustomTitlesFile())) {
                $customTitles = Yaml::parse($customTitlesFile);
                return Toolkit::array_deep_merge_and_replace($titles, $customTitles);
            }
            return $titles;
        };
    }
Esempio n. 9
0
 public function loadEditionConfig()
 {
     $book = $this->get('book');
     $edition = $this->get('publishing.edition');
     $userConfig = $book['editions'][$edition];
     $defaultConfig = $this['app.edition.defaults'];
     // if edition extends another edition, merge their configurations
     if (null != ($parent = $this->edition('extends'))) {
         if (!array_key_exists($parent, $book['editions'])) {
             throw new \UnexpectedValueException(sprintf(" ERROR: '%s' edition extends nonexistent '%s' edition" . "\n\n" . "Check in '%s' file \n" . "that the value of 'extends' option in '%s' edition is a valid \n" . "edition of the book", $edition, $parent, realpath($this['publishing.dir.book'] . '/config.yml'), $edition));
         }
         $parentConfig = $book['editions'][$parent];
         $userConfig = Toolkit::array_deep_merge($parentConfig, $userConfig);
     }
     $config = array_merge($defaultConfig, $userConfig);
     $book['editions'][$edition] = $config;
     $this->set('book', $book);
 }
Esempio n. 10
0
 /**
  * It generates the compressed file required for the ePub book.
  * To compress the contents, it uses the 'Zip' PHP extension.
  *
  * @param string $directory Book contents directory
  * @param string $zip_file  The path of the generated ZIP file
  */
 private function zipBookContentsWithPhpExtension($directory, $zip_file)
 {
     Toolkit::zip($directory, $zip_file);
 }
Esempio n. 11
0
 private function zipBookContents($directory, $zip_file)
 {
     if (extension_loaded('zip')) {
         return Toolkit::zip($directory, $zip_file);
     }
     // After several hours trying to create ZIP files with lots of PHP
     // tools and libraries (Archive_Zip, Pclzip, zetacomponents/archive, ...)
     // I can't produce a proper ZIP file for ebook readers.
     // Therefore, if ZIP extension isn't enabled, the ePub ZIP file is
     // generated by executing 'zip' command
     // check if 'zip' command exists
     $process = new Process('zip');
     $process->run();
     if (!$process->isSuccessful()) {
         throw new \RuntimeException("[ERROR] You must enable the ZIP extension in PHP \n" . " or your system should be able to execute 'zip' console command.");
     }
     // To generate the ePub file, you must execute the following commands:
     //   $ cd /path/to/ebook/contents
     //   $ zip -X0 book.zip mimetype
     //   $ zip -rX9 book.zip * -x mimetype
     $command = sprintf('cd %s && zip -X0 %s mimetype && zip -rX9 %s * -x mimetype', $directory, $zip_file, $zip_file);
     $process = new Process($command);
     $process->run();
     if (!$process->isSuccessful()) {
         throw new \RuntimeException("[ERROR] 'zip' command execution wasn't successful.\n\n" . "Executed command:\n" . " {$command}\n\n" . "Result:\n" . $process->getErrorOutput());
     }
 }