* * Copyright 2008-2011 Crowd Favorite, Ltd. All rights reserved. * Released under the GPL license * http://www.opensource.org/licenses/gpl-license.php */ // add_filter('Anno_PDF_Download_debug', '__return_true'); class Anno_PDF_Download { static $instance; private function __construct() { $this->ver = defined('ANNO_VER') ? ANNO_VER : '1.0'; } public static function i() { if (!isset(self::$instance)) { self::$instance = new Anno_PDF_Download; } return self::$instance; } public function setup_filterable_props() { $this->debug = apply_filters(__CLASS__.'_debug', false); $this->memory_limit = apply_filters(__CLASS__.'_memory_limit', '100M'); // only gets set when downloading the PDF $this->template_path = apply_filters(__CLASS__.'_template_path', 'templates/default.php'); } public function add_actions() { // Allow plugins to change various properties of our plugin add_action('init', array($this, 'setup_filterable_props')); // Run late to make sure all custom post types are registered add_action('init', array($this, 'setup_permalinks'), 20); // Handle various requests add_action('wp', array($this, 'request_handler')); } protected function set_pdfmaker_configs() { // Define various DOMPDF Settings (typically defined in dompdf_config.custom.inc.php) //define("DOMPDF_TEMP_DIR", "/tmp"); //define("DOMPDF_CHROOT", DOMPDF_DIR); define("DOMPDF_UNICODE_ENABLED", true); //define("TTF2AFM", "C:/Program Files (x86)/GnuWin32/bin/ttf2pt1.exe"); //define("DOMPDF_PDF_BACKEND", "PDFLib"); define("DOMPDF_DEFAULT_MEDIA_TYPE", "print"); define("DOMPDF_DEFAULT_PAPER_SIZE", "letter"); define("DOMPDF_DEFAULT_FONT", "helvetica"); //define("DOMPDF_DPI", 72); define("DOMPDF_ENABLE_PHP", true); define("DOMPDF_ENABLE_REMOTE", true); //define("DOMPDF_ENABLE_CSS_FLOAT", true); //define("DOMPDF_ENABLE_JAVASCRIPT", false); //define("DEBUGPNG", true); //define("DEBUGKEEPTEMP", true); //define("DEBUGCSS", true); //define("DEBUG_LAYOUT", true); //define("DEBUG_LAYOUT_LINES", false); //define("DEBUG_LAYOUT_BLOCKS", false); //define("DEBUG_LAYOUT_INLINE", false); //define("DOMPDF_FONT_HEIGHT_RATIO", 1.0); //define("DEBUG_LAYOUT_PADDINGBOX", false); //define("DOMPDF_LOG_OUTPUT_FILE", DOMPDF_FONT_DIR."log.htm"); } /** * Let WP know how to handle "pretty" requests for PDFs * * @return void */ public function setup_permalinks() { // Returns an array of post types named article $article_post_types = get_post_types(array('name' => 'article'), 'object'); // Ensure we have what we're looking for if (!is_array($article_post_types) || empty($article_post_types)) { return; } // Get the first (and hopefully only) one $article_post_type = array_shift($article_post_types); global $wp; // Allows us to use get_query_var('pdf') later on $wp->add_query_var('pdf'); // Tells WP that a request like /articles/my-article-name/pdf/ is valid add_rewrite_rule($article_post_type->rewrite['slug'].'/([^/]+)/pdf/?$', 'index.php?articles=$matches[1]&pdf=true', 'top'); } /** * Takes action when various query_vars are present * (currently just 'PDF' query var) * * @return void */ public function request_handler() { if (get_query_var('pdf')) { // Increase our memory limit for this request ini_set('memory_limit', $this->memory_limit); // Sanitize our article ID $id = get_the_ID(); // If we don't have an Article, get out if (empty($id)) { wp_die(__('No article found.', 'anno')); } // If we're not debugging, turn off errors if (!$this->debug) { $display_errors = ini_get('display_errors'); ini_set('display_errors', 0); } // Load whatever class will make the PDF if (!$this->load_pdfmaker()) { $this->log('Couldn\'t load the $pdfmaker'); $this->generic_die(); } // Get our HTML locally if (!$this->get_html($id)) { $this->log('Could not find HTML post_meta for post ID: '.$id); $this->generic_die(); } // If we force it to the screen @TODO remove once styling's OK in PDF if (isset($_GET['screen'])) { die($this->html); } // Get the PDF title (currently just the Article title) if (!$this->generate_pdf_title($id)) { $this->log('Couldn\'t generate the PDF title for post ID: '.$id); $this->generic_die(); } // Generate the PDF try { $this->generate_pdf(); } catch (Exception $e) { $this->log('Error Creating PDF: '.$e->getMessage()); $this->generic_die(); } // Set our error display back to what it was. if (!$this->debug) { ini_set('display_errors', $display_errors); } } } /** * Loads the Class that will generate the PDF. This is one place where we * should be able to change the PDF maker class * * @return bool */ private function load_pdfmaker() { $this->set_pdfmaker_configs(); // Requre the DOMPDF class require_once 'lib/dompdf/dompdf_config.inc.php'; // Store our class to a property $this->pdfmaker = new DOMPDF; return is_a($this->pdfmaker, 'DOMPDF'); } /** * Gets the HTML associated with a post, spec'd by $id * * @param int $id - Post ID * @return bool - Whether there was HTML or not */ private function get_html($id) { // Setup our global $post stuff global $post; $post = get_post($id); setup_postdata($post); // Output the template add_filter('anno_author_html', array($this, 'author_html'), 10, 2); ob_start(); remove_filter('the_content','wpautop'); include $this->template_path; $this->html = ob_get_clean(); remove_filter('anno_author_html', array($this, 'author_html'), 10, 2); // Reset our global $post wp_reset_postdata(); // Replace the HTML5 tags with HTML4 $this->html = $this->htmlcleanup($this->html); return !empty($this->html); } /** * Cleans up tags for better domPDF rendering * * @param string $html * @return string - replaced HTML */ private function htmlcleanup($html) { $html = $this->html4ify($html); $html = $this->fixtags($html); return $html; } /** * Replaces tags with others using regex for improved rendering in domPDF * * @param string $html * @return string - replaced HTML */ private function fixtags($html) { // Pattern => replacemant $replacements = array( '/()/i' => '${1}

', // Addresses http://code.google.com/p/dompdf/issues/detail?id=238 '/(<\/td>)/i' => '

${1}', ); foreach ($replacements as $pattern => $replacement) { $html = preg_replace($pattern, $replacement, $html); } return $html; } /** * Replaces the HTML5 elements defined in the $replacements array * with an HTML4 element. * * @param string $html * @return string - replaced HTML */ private function html4ify($html) { $replacements = array( ' ' ' ' ' ' ' ' ' ' ' ' ' '
' '
' ' ' ' ' ' ' ' ' '' => '', ); return str_replace(array_keys($replacements), array_values($replacements), $html); } /** * Creates the PDF for download. This is another place that will need * modification to change which underlying class that creates the PDF. * * @return void */ private function generate_pdf() { $this->pdfmaker->load_html($this->html); $this->pdfmaker->render(); $this->pdfmaker->stream($this->pdf_title, array( 'compress' => 1, 'Attachment' => 1, )); } /** * Gets the post IDs * * @param string $id * @return void */ private function generate_pdf_title($id) { $this->pdf_title = get_the_title($id); if (empty($this->pdf_title)) { return false; } $this->pdf_title.= '.pdf'; return true; } /** * Generates the PDF download URL for a post * * @param int $id * @return string */ public function get_download_url($id = null) { // Default to the global $post if (is_null($id)) { global $post; if (empty($post)) { $this->log('There is no global $post in scope.'); return false; } $id = $post->ID; } // Build our download link $permalink = get_permalink($id); /* Handle pretty and ugly permalinks. Need at least this stripos function, b/c initial drafts don't get a pretty permalink till published, so even if the setting is pretty permalinks the draft's permalink is going to be ugly. */ if (stripos($permalink, 'post_type=article') || get_option('permalink_structure') == '') { // Ugly permalinks $link = add_query_arg(array('pdf' => 'true'), $permalink); } else { // Pretty permalinks $link = trailingslashit($permalink).'pdf/'; } return $link; } /** * Provide a nice die message for failed PDF downloads * * @return void */ private function generic_die() { wp_die(__('There was an issue creating the PDF for your article, please try again later', 'anno')); } /** * Conditionally logs messages to the error log * * @param string $msg * @return void */ private function log($msg) { if ($this->debug) { error_log($msg); } } /** * Filter author html to be appropriate for PDF output * * @param string $html Default HTML * @param array $authors Authors data array **/ public function author_html($html, $authors) { $out = ''; $author_markup_arr = array(); $institutions = array(); foreach ($authors as $author_data) { // Use a user's website if there isn't a user object with associated id (imported user snapshots) // Also check to see if this is a string ID or int val id, knol_id vs wp_id if ($author_data['id'] == intval($author_data['id'])) { $posts_url = get_author_posts_url($author_data['id']); $posts_url = $posts_url == home_url('/author/') ? $author_data['link'] : $posts_url; } else { $posts_url = ''; } $prefix_markup = empty($author_data['prefix']) ? '' : esc_html($author_data['prefix'].' '); $suffix_markup = empty($author_data['suffix']) ? '' : esc_html(' '.$author_data['suffix']); if ($author_data['first_name'] && $author_data['last_name']) { $fn = empty($posts_url) ? '' : ''; $fn .= $prefix_markup.esc_html($author_data['first_name']).' '.esc_html($author_data['last_name']).$suffix_markup; $fn .= $posts_url ? '' : ''; } else { $fn = $posts_url ? '' : ''; $fn .= $prefix_markup.esc_html($author_data['display_name']).$suffix_markup; $fn .= $posts_url ? '' : ''; } if (!empty($author_data['institution'])) { $new = true; // Loop instead of foreach for case comparison foreach ($institutions as $key => $institution) { if (strcasecmp($author_data['institution'], $institution) === 0) { $new = false; break; } } if ($new) { $institutions[] = $author_data['institution']; $fn .= ''.count($institutions).''; } else { // Already in there $fn .= ''.esc_html($key + 1).''; } } $author_markup_arr[] = $fn; } $institutions_out = ''; if (!empty($institutions)) { $institutions_arr = array(); foreach ($institutions as $key => $value) { $institutions_arr[] = ''.($key + 1).' '.esc_html($value); } $institutions_out = '
'.implode(', ', $institutions_arr).'
'; } $authors_out = implode(', ', $author_markup_arr); return $authors_out.$institutions_out; } /** * Header markup for pdfs **/ public static function header_markup() { $header_img = get_header_image(); if (empty($header_img)) { $journal_name = cfct_get_option('journal_name'); $out = empty($journal_name) ? '' : $journal_name.'. '; $section_name = get_bloginfo('name'); $out .= empty($section_name) ? '' : $section_name.'.'; } else { $out = ''; } return $out; } /** * Footer markup for pdfs **/ public static function footer_markup() { // DOMpdf markup // @see http://code.google.com/p/dompdf/wiki/Usage $out = ' '; return $out; } } Anno_PDF_Download::i()->add_actions(); /** * Get the PDF download link for a post * * @param int $id * @return string */ function anno_pdf_download_url($id = null) { return Anno_PDF_Download::i()->get_download_url($id); } ?>