6 * Contains the functions to generate Printer-friendly pages.
8 * This file is included by the core PF module, and includes all the
9 * functions necessary to generate a PF version of the original page
15 $_print_urls = PRINT_URLS_DEFAULT
;
18 * Generate an HTML version of the printer-friendly page
20 * @see print_controller()
21 * @see _print_get_template()
23 function print_controller_html() {
24 $args = func_get_args();
25 $path = implode('/', $args);
26 $cid = isset($_GET['comment']) ?
(int)$_GET['comment'] : NULL
;
28 $print = print_controller($path, $cid, PRINT_HTML_FORMAT
);
29 if ($print !== FALSE
) {
30 $node = $print['node'];
31 include_once(DRUPAL_ROOT .
'/' .
_print_get_template(PRINT_HTML_FORMAT
, $print['type']));
33 $nodepath = (isset($node->path
)) ?
drupal_get_normal_path($node->path
) : 'node/' .
$path;
34 db_merge('print_page_counter')
35 ->key(array('path' => $nodepath))
38 'timestamp' => REQUEST_TIME
,
40 ->expression('totalcount', 'totalcount + :inc', array(':inc' => 1))
46 * Select the print generator function based on the page type
48 * Depending on the type of node, this functions chooses the appropriate
52 * path of the original page
54 * comment ID of the individual comment to be rendered
56 * format of the page being generated
58 * if set to TRUE, outputs only the node's teaser
60 * optional sender's message (used by the send e-mail module)
62 * array with the fields to be used in the template
63 * @see _print_generate_node()
64 * @see _print_generate_path()
65 * @see _print_generate_book()
67 function print_controller($path, $cid = NULL
, $format = PRINT_HTML_FORMAT
, $teaser = FALSE
, $message = NULL
) {
69 // If no path was provided, let's try to generate a page for the referer
72 $ref = $_SERVER['HTTP_REFERER'];
73 $path = preg_replace("!^$base_url/!", '', $ref);
78 if (!is_numeric($path)) {
79 // Indirect call with print/alias
80 // If there is a path alias with these arguments, generate a printer-friendly version for it
81 $path = drupal_get_normal_path($path);
82 $ret = preg_match('!^node/(.*)!i', $path, $matches);
87 $parts = explode('/', $path);
88 if (is_numeric($parts[0]) && (count($parts) == 1)) {
89 $print = _print_generate_node($path, $cid, $format, $teaser, $message);
92 $ret = preg_match('!^book/export/html/(.*)!i', $path, $matches);
94 // This is a book PF page link, handle trough the book handling functions
95 $print = _print_generate_book($matches[1], $format, $teaser, $message);
98 // If no content node was found, handle the page printing with the 'printable' engine
99 $print = _print_generate_path($path, $format, $teaser, $message);
107 * Generates a robots meta tag to tell them what they may index
110 * string with the meta robots tag
112 function _print_robots_meta_generator() {
113 $print_robots_noindex = variable_get('print_robots_noindex', PRINT_ROBOTS_NOINDEX_DEFAULT
);
114 $print_robots_nofollow = variable_get('print_robots_nofollow', PRINT_ROBOTS_NOFOLLOW_DEFAULT
);
115 $print_robots_noarchive = variable_get('print_robots_noarchive', PRINT_ROBOTS_NOARCHIVE_DEFAULT
);
116 $robots_meta = array();
118 if (!empty($print_robots_noindex)) {
119 $robots_meta[] = 'noindex';
121 if (!empty($print_robots_nofollow)) {
122 $robots_meta[] = 'nofollow';
124 if (!empty($print_robots_noarchive)) {
125 $robots_meta[] = 'noarchive';
128 if (count($robots_meta) > 0) {
129 $robots_meta = implode(', ', $robots_meta);
130 $robots_meta = "<meta name='robots' content='$robots_meta' />\n";
140 * Post-processor that fills the array for the template with common details
143 * generated node with a printer-friendly node body
145 * optional sender's message (used by the send e-mail module)
147 * id of current comment being generated (NULL when not generating
148 * an individual comment)
150 * array with the fields to be used in the template
152 function _print_var_generator($node, $message = NULL
, $cid = NULL
) {
153 global $base_url, $language, $_print_urls;
155 $path = empty($node->nid
) ?
$node->path
: "node/$node->nid";
157 // print module settings
158 $print_css = variable_get('print_css', PRINT_CSS_DEFAULT
);
159 $print_logo_options = variable_get('print_logo_options', PRINT_LOGO_OPTIONS_DEFAULT
);
160 $print_logo_url = variable_get('print_logo_url', PRINT_LOGO_URL_DEFAULT
);
161 $print_html_new_window = variable_get('print_html_new_window', PRINT_HTML_NEW_WINDOW_DEFAULT
);
162 $print_html_sendtoprinter = variable_get('print_html_sendtoprinter', PRINT_HTML_SENDTOPRINTER_DEFAULT
);
163 $print_html_windowclose = variable_get('print_html_windowclose', PRINT_HTML_WINDOWCLOSE_DEFAULT
);
164 $print_sourceurl_enabled = variable_get('print_sourceurl_enabled', PRINT_SOURCEURL_ENABLED_DEFAULT
);
165 $print_sourceurl_forcenode = variable_get('print_sourceurl_forcenode', PRINT_SOURCEURL_FORCENODE_DEFAULT
);
166 $print_sourceurl_date = variable_get('print_sourceurl_date', PRINT_SOURCEURL_DATE_DEFAULT
);
167 $print_footer_options = variable_get('print_footer_options', PRINT_FOOTER_OPTIONS_DEFAULT
);
168 $print_footer_user = variable_get('print_footer_user', PRINT_FOOTER_USER_DEFAULT
);
170 $print['language'] = $language->language
;
171 $print['title'] = check_plain($node->title
);
172 $print['head'] = drupal_get_html_head();
173 $print['scripts'] = drupal_get_js();
174 $print['robots_meta'] = _print_robots_meta_generator();
175 $print['url'] = url($path, array('absolute' => TRUE
));
176 $print['base_href'] = "<base href='" .
$print['url'] .
"' />\n";
177 $print['favicon'] = theme_get_setting('toggle_favicon') ?
"<link rel='shortcut icon' href='" .
theme_get_setting('favicon') .
"' type='image/x-icon' />\n" : '';
179 if (!empty($print_css)) {
180 $replace_pairs = array('%b' => base_path(), '%t' => path_to_theme());
181 $user_css = check_url(strtr($print_css, $replace_pairs));
184 drupal_add_css(drupal_get_path('module', 'print') .
'/css/print.css');
186 $drupal_css = drupal_add_css();
187 foreach ($drupal_css as
$key => $types) {
188 // Unset the theme's CSS
189 $drupal_css[$key]['theme'] = array();
192 // If we are sending a message via e-mail, the CSS must be embedded
193 if (!empty($message)) {
195 $css_files = array();
196 foreach ($drupal_css as
$types) {
197 foreach ($types as
$values) {
198 $css_files = array_merge($css_files, array_keys($values));
201 if (!empty($print_css)) {
202 // Convert to a local path, by removing the base_path
203 $pattern = '!^' .
base_path() .
'!';
204 $css_files[] = preg_replace($pattern, '', $user_css);
206 foreach ($css_files as
$filename) {
207 $res = file_exists($filename) ?
file_get_contents($filename, TRUE
) : FALSE
;
212 $print['css'] = "<style type='text/css' media='all'>$style</style>\n";
215 $print['css'] = drupal_get_css($drupal_css);
216 if (!empty($print_css)) {
217 $query_string = '?'.
substr(variable_get('css_js_query_string', '0'), 0, 1);
218 $print['css'] .
= "<link type='text/css' rel='stylesheet' media='all' href='${user_css}${query_string}' />\n";
222 $window_close = ($print_html_new_window && $print_html_windowclose) ?
'window.close();' : '';
223 $print['sendtoprinter'] = $print_html_sendtoprinter ?
" onload='window.print();$window_close'" : '';
225 switch ($print_logo_options) {
230 $logo_url = theme_get_setting('logo');
232 case
2: // user-specifed
233 $logo_url = strip_tags($print_logo_url);
236 $print['logo'] = $logo_url ?
"<img class='print-logo' src='" .
check_url($logo_url) .
"' alt='' />\n" : '';
238 switch ($print_footer_options) {
243 $footer = variable_get('site_footer', FALSE
) .
"\n" .
theme('blocks', 'footer');
245 case
2: // user-specifed
246 $footer = $print_footer_user;
249 $print['footer_message'] = filter_xss_admin($footer);
251 $published_site = variable_get('site_name', 0);
252 if ($published_site) {
253 $print_text_published = filter_xss(variable_get('print_text_published', t('Published on %site_name')));
254 $published = t($print_text_published, array('%site_name' => $published_site));
255 $print['site_name'] = $published .
' (' .
l($base_url, $base_url) .
')';
258 $print['site_name'] = '';
261 if ($print_sourceurl_enabled == 1) {
262 /* Grab and format the src URL */
263 if (empty($print_sourceurl_forcenode)) {
264 $url = $print['url'];
267 $url = $base_url .
'/' .
(((bool
)variable_get('clean_url', '0')) ?
'' : '?q=') .
$path;
270 $url .
= '#comment-$cid';
272 $retrieved_date = format_date(REQUEST_TIME
, 'small');
273 $print_text_retrieved = filter_xss(variable_get('print_text_retrieved', t('retrieved on %date')));
274 $retrieved = t($print_text_retrieved, array('%date' => $retrieved_date));
275 $print['printdate'] = $print_sourceurl_date ?
" ($retrieved)" : '';
277 $source_url = filter_xss(variable_get('print_text_source_url', t('Source URL')));
278 $print['source_url'] = '<strong>' .
$source_url .
$print['printdate'] .
':</strong> ' .
l($url, $url);
281 $print['source_url'] = '';
284 if (isset($node->type
)) {
285 $node_type = $node->type
;
287 if (theme_get_setting("toggle_node_info_$node_type")) {
288 $print_text_by = filter_xss(variable_get('print_text_by', t('By %author')));
289 $by_author = ($node->name ?
$node->name
: variable_get('anonymous', t('Anonymous')));
290 $print['submitted'] = t($print_text_by, array('%author' => $by_author));
292 $print_text_created = filter_xss(variable_get('print_text_created', t('Created %date')));
293 $created_datetime = format_date($node->created
, 'small');
294 $print['created'] = t($print_text_created, array('%date' => $created_datetime));
297 $print['submitted'] = '';
298 $print['created'] = '';
301 $print['type'] = $node->type
;
304 $print['submitted'] = '';
305 $print['created'] = '';
309 menu_set_active_item($path);
310 $breadcrumb = drupal_get_breadcrumb();
311 if (!empty($breadcrumb)) {
312 $breadcrumb[] = menu_get_active_title();
313 $print['breadcrumb'] = filter_xss(implode(' > ', $breadcrumb));
316 $print['breadcrumb'] = '';
319 // Display the collected links at the bottom of the page. Code once taken from Kjartan Mannes' project.module
320 $print['pfp_links'] = '';
321 if (!empty($_print_urls)) {
322 $urls = _print_friendly_urls();
326 for ($i = 0; $i < $max; $i++) {
327 $pfp_links .
= '[' .
($i + 1) .
'] ' .
check_plain($urls[$i]) .
"<br />\n";
329 $links = filter_xss(variable_get('print_text_links', t('Links')));
330 $print['pfp_links'] = "<p><strong>$links:</strong><br />$pfp_links</p>";
334 if (module_exists('taxonomy')) {
335 // TODO make this work when taxonomy works again
336 // $terms = taxonomy_link('taxonomy terms', $node);
337 $terms = taxonomy_preview_terms($node);
338 $print['taxonomy'] = theme('links', $terms);
341 $print['content'] = $node->body
;
342 $print['node'] = $node;
343 $print['message'] = $message;
349 * Callback function for the preg_replace_callback for URL-capable patterns
351 * Manipulate URLs to make them absolute in the URLs list, and to add a
352 * [n] footnote marker.
355 * array with the matched tag patterns, usually <a...>+text+</a>
357 * tag with re-written URL and when appropriate the [n] index to the
360 function _print_rewrite_urls($matches) {
361 global $base_url, $base_root, $_print_urls;
363 // first, split the html into the different tag attributes
364 $pattern = '!\s*(\w+\s*=\s*"(?:\\\"|[^"])*")\s*|\s*(\w+\s*=\s*\'(?:\\\\\'|[^\'])*\')\s*|\s*(\w+\s*=\s*\w+)\s*|\s+!';
365 $attribs = preg_split($pattern, $matches[1], -1, PREG_SPLIT_NO_EMPTY
| PREG_SPLIT_DELIM_CAPTURE
);
366 foreach ($attribs as
$key => $value) {
367 $attribs[$key] = preg_replace('!(\w)\s*=\s*(.*)!', '$1=$2', $value);
370 $size = count($attribs);
371 for ($i=1; $i < $size; $i++) {
372 // If the attribute is href or src, we may need to rewrite the URL in the value
373 if (preg_match('!^(?:href|src)\s*?=(.*)!i', $attribs[$i], $urls) > 0) {
374 $url = trim($urls[1], " \t\n\r\0\x0B\"'");
376 if (strpos(html_entity_decode($url), '://') || preg_match('!^mailto:.*?@.*?\..*?$!iu', html_entity_decode($url))) {
377 // URL is absolute, do nothing
381 if ($url[0] == '#') {
382 // URL is an anchor tag
383 if (!empty($_print_urls)) {
384 $path = explode('/', $_GET['q']);
386 $path = implode('/', $path);
387 if (is_numeric($path)) {
388 $path = "node/$path";
390 // Printer-friendly URLs is on, so we need to make it absolute
391 $newurl = url($path, array('fragment' => substr($url, 1), 'absolute' => TRUE
));
393 // Because base href is the original page, change the link to
394 // still be usable inside the print page
395 $matches[1] = str_replace($url, base_path() .
$_GET['q'] .
$url, $matches[1]);
398 // URL is relative, convert it into absolute URL
399 if ($url[0] == '/') {
400 // If it starts with '/' just append it to the server name
401 $newurl = $base_root .
'/' .
trim($url, '/');
403 elseif (preg_match('!^(?:index.php)?\?q=!i', $url)) {
404 // If it starts with ?q=, just prepend with the base URL
405 $newurl = $base_url .
'/' .
trim($url, '/');
408 $newurl = url(trim($url, '/'), array('absolute' => TRUE
));
410 $matches[1] = str_replace($url, $newurl, $matches[1]);
416 $ret = '<' .
$matches[1] .
'>';
417 if (count($matches) == 4) {
418 $ret .
= $matches[2] .
$matches[3];
419 if ((!empty($_print_urls)) && (isset($newurl))) {
420 $ret .
= ' <span class="print-footnote">[' .
_print_friendly_urls(trim($newurl)) .
']</span>';
428 * Auxiliary function to store the Printer-friendly URLs list as static.
431 * absolute URL to be inserted in the list
433 * list of URLs previously stored if $url is 0, or the current count
436 function _print_friendly_urls($url = 0) {
437 static
$urls = array();
439 $url_idx = array_search($url, $urls);
440 if ($url_idx !== FALSE
) {
441 return ($url_idx + 1);
454 * Choose most appropriate template
456 * Auxiliary function to resolve the most appropriate template trying to find
457 * a content specific template in the theme or module dir before falling back
458 * on a generic template also in those dirs.
461 * format of the PF page being rendered (html, pdf, etc.)
463 * name of the node type being rendered in a PF page
465 * filename of the most suitable template
467 function _print_get_template($format = NULL
, $type = NULL
) {
468 $filenames = array();
469 // First try to find a template defined both for the format and then the type
470 if (!empty($format) && !empty($type)) {
471 $filenames[] = "print_$format.node-$type.tpl.php";
473 // Then only for the format
474 if (!empty($format)) {
475 $filenames[] = "print_$format.tpl.php";
477 // If the node type is known, then try to find that type's template file
479 $filenames[] = "print.node-$type.tpl.php";
481 // Finally search for a generic template file
482 $filenames[] = 'print.tpl.php';
484 foreach ($filenames as
$value) {
485 // First in the theme directory
486 $file = drupal_get_path('theme', $GLOBALS['theme_key']) .
'/' .
$value;
487 if (file_exists($file)) {
490 // Then in the module directory
491 $file = drupal_get_path('module', 'print') .
'/' .
$value;
492 if (file_exists($file)) {
499 * Check URL list settings for this node
504 * format of the page being generated
506 * TRUE if URL list should be displayed, FALSE otherwise
508 function _print_url_list_enabled($node, $format = PRINT_HTML_FORMAT
) {
510 case PRINT_HTML_FORMAT
:
511 $node_urllist = isset($node->print_display_urllist
) ?
$node->print_display_urllist
: TRUE
;
514 case PRINT_MAIL_FORMAT
:
515 $node_urllist = isset($node->print_mail_display_urllist
) ?
$node->print_mail_display_urllist
: TRUE
;
516 $fmt = $format .
'_';
518 case PRINT_PDF_FORMAT
:
519 $node_urllist = isset($node->print_pdf_display_urllist
) ?
$node->print_pdf_display_urllist
: TRUE
;
520 $fmt = $format .
'_';
523 if (!isset($node_urllist)) $node_urllist = TRUE
;
525 // Get value of Printer-friendly URLs setting
526 return (variable_get('print_urls', PRINT_URLS_DEFAULT
) && ($node_urllist) &&
527 (isset($node->type
) ?
variable_get('print_' .
$fmt .
'display_urllist_' .
$node->type
, PRINT_TYPE_URLLIST_DEFAULT
) : PRINT_TYPE_URLLIST_DEFAULT
));
531 * Prepare a Printer-friendly-ready node body for content nodes
534 * node ID of the node to be rendered into a printer-friendly page
536 * comment ID of the individual comment to be rendered
538 * format of the page being generated
540 * if set to TRUE, outputs only the node's teaser
542 * optional sender's message (used by the send e-mail module)
544 * filled array ready to be used in the template
546 function _print_generate_node($nid, $cid = NULL
, $format = PRINT_HTML_FORMAT
, $teaser = FALSE
, $message = NULL
) {
549 // We can take a node id
550 $node = node_load($nid);
556 elseif (!node_access('view', $node)) {
558 drupal_access_denied();
561 drupal_set_title($node->title
);
563 //alert other modules that we are generating a printer-friendly page, so they can choose to show/hide info
564 $node->printing
= TRUE
;
566 //use the proper build_mode
567 $node->build_mode
= 'print';
569 // Turn off Pagination by the Paging module
571 unset($node->page_count
);
573 // Make this page a member of the original page's organic group
574 if (function_exists('og_set_group_context') && isset($node->og_groups
)) {
575 og_set_group_context($node->og_groups
);
579 // Adapted (simplified) version of node_view
580 //Render the node content
581 $node = node_build_content($node, $teaser, TRUE
);
582 // Disable the links area
583 unset($node->content
['links']);
584 // Disable fivestar widget output
585 unset($node->content
['fivestar_widget']);
586 // Disable service links module output
587 unset($node->content
['service_links']);
589 $content = drupal_render($node->content
);
591 // Disable the AdSense module ads
592 $content = preg_replace('!<div class=[\'"]adsense[\'"].*?</div>!sim', '', $content);
595 $node->teaser
= $content;
599 $node->body
= $content;
600 unset($node->teaser
);
604 $print_comments = variable_get('print_comments', PRINT_COMMENTS_DEFAULT
);
606 if (function_exists('comment_render') && (($cid != NULL
) || ($print_comments))) {
607 //Print only the requested comment (or if $cid is NULL, all of them)
608 $comments = comment_render($node, $cid);
610 //Remove the comment forms
611 $comments = preg_replace('!<form.*?id="comment-.*?">.*?</form>!sim', '', $comments);
612 //Remove the 'Post new comment' title
613 $comments = preg_replace('!<h2.*?>' .
t('Post new comment') .
'</h2>!', '', $comments);
614 //Remove the comment title hyperlink
615 $comments = preg_replace('!(<h3.*?>.*?)<a.*?>(.*?)</a>(.*?</h3>)!i', '$1$2$3', $comments);
616 //Remove the comment author link
617 $pattern = '!(<(?:span|div) class="submitted">.*?)<a.*?>(.*?)</a>(.*?</(?:span|div)>)!sim';
618 if (preg_match($pattern, $comments)) {
619 $comments = preg_replace($pattern , '$1$2$3', $comments);
621 //Remove the comment links
622 $comments = preg_replace('!\s*<ul class="links">.*?</ul>!sim', '', $comments);
624 // Single comment requested, output only the comment
627 $node->body .
= $comments;
630 module_invoke_all('node_alter', $node, $teaser, TRUE
);
633 $node->body
= $node->teaser
;
634 unset($node->teaser
);
637 //Check URL list settings
638 $_print_urls = _print_url_list_enabled($node, $format);
640 // Convert the a href elements
641 $pattern = '!<(a\s[^>]*?)>(.*?)(</a>)!is';
642 $node->body
= preg_replace_callback($pattern, '_print_rewrite_urls', $node->body
);
646 $print = _print_var_generator($node, $message, $cid);
652 * Prepare a Printer-friendly-ready node body for non-content pages
655 * path of the node to be rendered into a printer-friendly page
657 * format of the page being generated
659 * if set to TRUE, outputs only the node's teaser
661 * optional sender's message (used by the send e-mail module)
663 * filled array ready to be used in the template
665 function _print_generate_path($path, $format = PRINT_HTML_FORMAT
, $teaser = FALSE
, $message = NULL
) {
669 $parts = explode('/', $path);
670 if (is_numeric($parts[0]) && (count($parts) > 1)) {
671 $path = 'node/' .
$path;
674 $path = drupal_get_normal_path($path);
676 menu_set_active_item($path);
677 // Adapted from index.php.
678 $node = new
stdClass();
679 $node->body
= menu_execute_active_handler($path);
681 // It may happen that a drupal_not_found is called in the above call
682 if (preg_match('/404 Not Found/', drupal_get_headers()) == 1) {
686 if (is_int($node->body
)) {
687 switch ($node->body
) {
692 case MENU_ACCESS_DENIED
:
693 drupal_access_denied();
699 $node->title
= drupal_get_title();
702 // Delete any links area
703 $node->body
= preg_replace('!\s*<div class="links">.*?</div>!sim', '', $node->body
);
705 //Check URL list settings
706 $_print_urls = _print_url_list_enabled($node, $format);
708 // Convert the a href elements
709 $pattern = '!<(a\s[^>]*?)>(.*?)(</a>)!is';
710 $node->body
= preg_replace_callback($pattern, '_print_rewrite_urls', $node->body
);
714 $print = _print_var_generator($node, $message);
721 * Prepare a Printer-friendly-ready node body for book pages
724 * node ID of the node to be rendered into a printer-friendly page
726 * format of the page being generated
728 * if set to TRUE, outputs only the node's teaser
730 * optional sender's message (used by the send e-mail module)
732 * filled array ready to be used in the template
734 function _print_generate_book($nid, $format = PRINT_HTML_FORMAT
, $teaser = FALSE
, $message = NULL
) {
737 $node = node_load($nid);
743 elseif (!node_access('view', $node) || (!user_access('access printer-friendly version'))) {
745 drupal_access_denied();
749 $tree = book_menu_subtree_data($node->book
);
750 $node->body
= book_export_traverse($tree, 'book_node_export');
752 //Check URL list settings
753 $_print_urls = _print_url_list_enabled($node, $format);
755 // Convert the a href elements
756 $pattern = '!<(a\s[^>]*?)>(.*?)(</a>)!is';
757 $node->body
= preg_replace_callback($pattern, '_print_rewrite_urls', $node->body
);
761 $print = _print_var_generator($node, $message);
762 // The title is already displayed by the book_recurse, so avoid duplication
763 $print['title'] = '';