Fix #291816: Use CCK's 6.x release path for its CSS file
[project/print.git] / print.pages.inc
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * Contains the functions to generate Printer-friendly pages.
7 *
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
10 * in HTML format.
11 */
12
13 /**
14 * Generate an HTML version of the printer-friendly page
15 *
16 * @see print_controller()
17 * @see _print_get_template()
18 */
19 function print_controller_html() {
20 $args = func_get_args();
21 // Remove the print/ prefix
22 $path = implode('/', $args);
23 $cid = isset($_GET['comment']) ? $_GET['comment'] : NULL;
24
25 $print = print_controller($path, $cid);
26 $node = $print['node'];
27 include_once(_print_get_template('html', $print['type']));
28 }
29
30 /**
31 * Select the print generator function based on the page type
32 *
33 * Depending on the type of node, this functions chooses the appropriate
34 * generator function.
35 *
36 * @param $path
37 * path of the original page
38 * @param $cid
39 * comment ID of the individual comment to be rendered
40 * @param $teaser
41 * if set to TRUE, outputs only the node's teaser
42 * @param $message
43 * optional sender's message (used by the send e-mail module)
44 * @return
45 * array with the fields to be used in the template
46 * @see _print_generate_node()
47 * @see _print_generate_path()
48 * @see _print_generate_book()
49 */
50 function print_controller($path, $cid, $teaser = FALSE, $message = NULL) {
51 if (!is_numeric($path)) {
52 // Indirect call with print/alias
53 // If there is a path alias with these arguments, generate a printer-friendly version for it
54 $path = drupal_get_normal_path($path);
55 $ret = preg_match('!^node/(.*)!i', $path, $matches);
56 if ($ret == 1) {
57 $path = $matches[1];
58 }
59 }
60 $parts = explode('/', $path);
61 if (is_numeric($parts[0])) {
62 $print = _print_generate_node($path, $cid, $teaser, $message);
63 }
64 else {
65 $ret = preg_match('!^book/export/html/(.*)!i', $path, $matches);
66 if ($ret == 1) {
67 // This is a book PF page link, handle trough the book handling functions
68 $print = _print_generate_book($matches[1], $teaser, $message);
69 }
70 else {
71 // If no content node was found, handle the page printing with the 'printable' engine
72 $print = _print_generate_path($path, $teaser, $message);
73 }
74 }
75
76 return $print;
77 }
78
79 /**
80 * Generates a robots meta tag to tell them what they may index
81 *
82 * @return
83 * string with the meta robots tag
84 */
85 function _print_robots_meta_generator() {
86 $print_robots_noindex = variable_get('print_robots_noindex', PRINT_ROBOTS_NOINDEX_DEFAULT);
87 $print_robots_nofollow = variable_get('print_robots_nofollow', PRINT_ROBOTS_NOFOLLOW_DEFAULT);
88 $print_robots_noarchive = variable_get('print_robots_noarchive', PRINT_ROBOTS_NOARCHIVE_DEFAULT);
89 $robots_meta = array();
90
91 if (!empty($print_robots_noindex)) {
92 $robots_meta[] = 'noindex';
93 }
94 if (!empty($print_robots_nofollow)) {
95 $robots_meta[] = 'nofollow';
96 }
97 if (!empty($print_robots_noarchive)) {
98 $robots_meta[] = 'noarchive';
99 }
100
101 if (count($robots_meta) > 0) {
102 $robots_meta = implode(', ', $robots_meta);
103 $robots_meta = "<meta name='robots' content='$robots_meta' />\n";
104 }
105 else {
106 $robots_meta = '';
107 }
108
109 return $robots_meta;
110 }
111
112 /**
113 * Post-processor that fills the array for the template with common details
114 *
115 * @param $node
116 * generated node with a printer-friendly node body
117 * @param $message
118 * optional sender's message (used by the send e-mail module)
119 * @param $cid
120 * id of current comment being generated (NULL when not generating
121 * an individual comment)
122 * @return
123 * array with the fields to be used in the template
124 */
125 function _print_var_generator($node, $message = NULL, $cid = NULL) {
126 global $base_url, $language;
127
128 $path = empty($node->nid) ? $node->path : "node/$node->nid";
129
130 $themed = theme('print_text');
131
132 // print module settings
133 $print_css = variable_get('print_css', PRINT_CSS_DEFAULT);
134 $print_urls = variable_get('print_urls', PRINT_URLS_DEFAULT);
135 $print_logo_options = variable_get('print_logo_options', PRINT_LOGO_OPTIONS_DEFAULT);
136 $print_logo_url = variable_get('print_logo_url', PRINT_LOGO_URL_DEFAULT);
137 $print_html_sendtoprinter = variable_get('print_html_sendtoprinter', PRINT_HTML_SENDTOPRINTER_DEFAULT);
138 $print_sourceurl_enabled = variable_get('print_sourceurl_enabled', PRINT_SOURCEURL_ENABLED_DEFAULT);
139 $print_sourceurl_forcenode = variable_get('print_sourceurl_forcenode', PRINT_SOURCEURL_FORCENODE_DEFAULT);
140 $print_sourceurl_date = variable_get('print_sourceurl_date', PRINT_SOURCEURL_DATE_DEFAULT);
141
142 $print['language'] = $language->language;
143 $print['title'] = $node->title;
144 $print['head'] = drupal_get_html_head();
145 $print['scripts'] = drupal_get_js();
146 $print['robots_meta'] = _print_robots_meta_generator();
147 $print['url'] = url($path, array('absolute' => TRUE));
148 $print['base_href'] = "<base href='". $print['url'] ."' />\n";
149 $print['favicon'] = theme_get_setting('toggle_favicon') ? "<link rel='shortcut icon' href='". theme_get_setting('favicon') ."' type='image/x-icon' />\n" : '';
150
151 $css_files = array();
152 if (!empty($print_css)) {
153 $replace_pairs = array('%b' => base_path(), '%t' => path_to_theme());
154 $css_files[] = strip_tags(strtr($print_css, $replace_pairs));
155 }
156 else {
157 $css_files[] = base_path() . drupal_get_path('module', 'print') .'/print.css';
158 }
159 // If the current language is RTL add the RTL stylesheet.
160 if (defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL) {
161 $css_files[] = base_path() . drupal_get_path('module', 'print') .'/print-rtl.css';
162 }
163 // Add CCK's CSS file to properly display the fields
164 if (module_exists('content')) {
165 $css_files[] = base_path() . drupal_get_path('module', 'content') .'/theme/content.css';
166 }
167
168 // If we are sending a message via e-mail, the CSS must be embedded
169 if (!empty($message)) {
170 $style = '';
171 $pattern = '!^'. base_path() .'!';
172 foreach ($css_files as $filename) {
173 // Convert to a local path, by removing the base_path
174 $filename = preg_replace($pattern, '', $filename);
175 $res = file_get_contents($filename, TRUE);
176 if ($res != FALSE) {
177 $style .= $res;
178 }
179 }
180 $print['css'] = "<style type='text/css' media='all'>$style</style>\n";
181 }
182 else {
183 $print['css'] = '';
184 foreach ($css_files as $value) {
185 $print['css'] .= "<link type='text/css' rel='stylesheet' media='all' href='$value' />\n";
186 }
187 }
188
189 $print['sendtoprinter'] = $print_html_sendtoprinter ? ' onload="window.print();"' : '';
190
191 switch ($print_logo_options) {
192 case 0: // none
193 $logo_url = 0;
194 break;
195 case 1: // theme's
196 $logo_url = theme_get_setting('logo');
197 break;
198 case 2: // user-specifed
199 $logo_url = strip_tags($print_logo_url);
200 break;
201 }
202 $print['logo'] = $logo_url ? "<img class='print-logo' src='$logo_url' alt='' />\n" : '';
203
204 $published_site = variable_get('site_name', 0);
205 if ($published_site) {
206 $published = (empty($themed['published'])) ? t('Published on %site_name', array('%site_name' => $published_site)) : ($themed['published'] .' '. $published_site);
207 $print['site_name'] = $published .' ('. l($base_url, $base_url) .')';
208 }
209 else {
210 $print['site_name'] = '';
211 }
212
213 if ($print_sourceurl_enabled == 1) {
214 /* Grab and format the src URL */
215 if (empty($print_sourceurl_forcenode)) {
216 $url = $print['url'];
217 }
218 else {
219 $url = $base_url .'/'. (((bool)variable_get('clean_url', '0')) ? '' : '?q=') . $path;
220 }
221 if ($cid) {
222 $url .= '#comment-$cid';
223 }
224 $retrieved_date = format_date(time(), 'small');
225 $retrieved = (empty($themed['retrieved'])) ? t('retrieved on %date', array('%date' => $retrieved_date)) : ($themed['retrieved'] .' '. $retrieved_date);
226 $print['printdate'] = $print_sourceurl_date ? " ($retrieved)" : '';
227
228 $source_url = (empty($themed['sourceURL'])) ? t('Source URL') : $themed['sourceURL'];
229 $print['source_url'] = '<strong>'. $source_url . $print['printdate'] .':</strong> '. l($url, $url);
230 }
231 else {
232 $print['source_url'] = '';
233 }
234
235 if (isset($node->type)) {
236 $node_type = $node->type;
237
238 if (theme_get_setting('toggle_node_info_$node_type')) {
239 $by_author = ($node->name ? $node->name : variable_get('anonymous', t('Anonymous')));
240 $by = (empty($themed['by'])) ? t('By %author', array('%author' => $by_author)) : ($themed['by'] .' '. $by_author);
241 $print['submitted'] = $by;
242
243 $created_datetime = format_date($node->created, 'small');
244 $created = (empty($themed['created'])) ? t('Created %date', array('%date' => $created_datetime)) : ($themed['created'] .' '. $created_datetime);
245 $print['created'] = $created;
246 }
247 else {
248 $print['submitted'] = '';
249 $print['created'] = '';
250 }
251
252 $print['type'] = $node->type;
253 }
254 else {
255 $print['submitted'] = '';
256 $print['created'] = '';
257 $print['type'] = '';
258 }
259
260 menu_set_active_item($path);
261 $breadcrumb = drupal_get_breadcrumb();
262 if (!empty($breadcrumb)) {
263 $breadcrumb[] = menu_get_active_title();
264 $print['breadcrumb'] = implode(' > ', $breadcrumb);
265 }
266 else {
267 $print['breadcrumb'] = '';
268 }
269
270 // Display the collected links at the bottom of the page. Code once taken from Kjartan Mannes' project.module
271 if (!empty($print_urls)) {
272 $urls = _print_friendly_urls();
273 $max = count($urls);
274 $pfp_links = '';
275 if ($max) {
276 for ($i = 0; $i < $max; $i++) {
277 $pfp_links .= '['. ($i + 1) .'] '. $urls[$i] ."<br />\n";
278 }
279 $links = (empty($themed['links'])) ? t('Links') : $themed['links'];
280 $print['pfp_links'] = "<p><strong>$links:</strong><br />$pfp_links</p>";
281 }
282 }
283
284 if (module_exists('taxonomy')) {
285 $terms = taxonomy_link('taxonomy terms', $node);
286 $print['taxonomy'] = theme('links', $terms);
287 }
288
289 $print['content'] = $node->body;
290 $print['node'] = $node;
291 $print['message'] = $message;
292 $print['footer_message'] = filter_xss_admin(variable_get('site_footer', FALSE)) ."\n". theme('blocks', 'footer') ;
293
294 return $print;
295 }
296
297 /**
298 * Callback function for the preg_replace_callback for URL-capable patterns
299 *
300 * Manipulate URLs to make them absolute in the URLs list, and to add a
301 * [n] footnote marker.
302 *
303 * @param $matches
304 * array with the matched tag patterns, usually <a...>+text+</a>
305 * @return
306 * tag with re-written URL and when appropriate the [n] index to the
307 * URL list
308 */
309 function _print_rewrite_urls($matches) {
310 global $base_url, $base_root;
311
312 // Get value of Printer-friendly URLs setting
313 $print_urls = variable_get('print_urls', PRINT_URLS_DEFAULT);
314
315 // first, split the html into the different tag attributes
316 $pattern = '!\s*(\w+\s*=\s*"(?:\\\"|[^"])*")\s*|\s*(\w+\s*=\s*\'(?:\\\'|[^\'])*\')\s*|\s*(\w+\s*=\s\w+)\s*|\s+!im';
317 $attribs = preg_split($pattern, $matches[1], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
318 foreach ($attribs as $key => $value) {
319 $attribs[$key] = preg_replace('!(\w)\s*=\s*(.*)!', '$1=$2', $value);
320 }
321
322 $size = count($attribs);
323 for ($i=1; $i < $size; $i++) {
324 // If the attribute is href or src, we may need to rewrite the URL in the value
325 if (preg_match('!^(?:href|src)\s*?=(.*)!i', $attribs[$i], $urls) > 0) {
326 $url = trim($urls[1], " \t\n\r\0\x0B\"'");
327
328 if (strpos($url, '://') || preg_match('!^mailto:.*?@.*?\..*?$!iu', $url)) {
329 // URL is absolute, do nothing
330 $newurl = urldecode($url);
331 }
332 else {
333 if ($url[0] == '#') {
334 // URL is an anchor tag
335 if (!empty($print_urls)) {
336 $path = explode('/', $_GET['q']);
337 unset($path[0]);
338 $path = implode('/', $path);
339 if (is_numeric($path)) {
340 $path = "node/$path";
341 }
342 // Printer-friendly URLs is on, so we need to make it absolute
343 $newurl = url($path, array('fragment' => substr(urldecode($url), 1), 'absolute' => TRUE));
344 }
345 // Because base href is the original page, change the link to
346 // still be usable inside the print page
347 $matches[1] = str_replace($url, $_GET['q'] . $url, $matches[1]);
348 }
349 else {
350 // URL is relative, convert it into absolute URL
351 if ($url[0] == '/') {
352 // If it starts with '/' just append it to the server name
353 $newurl = $base_root .'/'. trim(urldecode($url), '/');
354 }
355 elseif (preg_match('!^(?:index.php)?\?q=!i', $url)) {
356 // If it starts with ?q=, just prepend with the base URL
357 $newurl = $base_url .'/'. trim(urldecode($url), '/');
358 }
359 else {
360 $newurl = url(trim(urldecode($url), '/'), array('absolute' => TRUE));
361 }
362 }
363 }
364 }
365 }
366
367 $ret = '<'. $matches[1] .'>';
368 if (count($matches) == 4) {
369 $ret .= $matches[2] . $matches[3];
370 if ((!empty($print_urls)) && (isset($newurl))) {
371 $ret .= ' <span class="print-footnote">['. _print_friendly_urls(trim(stripslashes($newurl))) .']</span>';
372 }
373 }
374
375 return $ret;
376 }
377
378 /**
379 * Auxiliary function to store the Printer-friendly URLs list as static.
380 *
381 * @param $url
382 * absolute URL to be inserted in the list
383 * @return
384 * list of URLs previously stored if $url is 0, or the current count
385 * otherwise.
386 */
387 function _print_friendly_urls($url = 0) {
388 static $urls = array();
389 if ($url) {
390 $url_idx = array_search($url, $urls);
391 if ($url_idx !== FALSE) {
392 return ($url_idx + 1);
393 }
394 else {
395 $urls[] = $url;
396 return count($urls);
397 }
398 }
399 $ret = $urls;
400 $urls = array();
401 return $ret;
402 }
403
404 /**
405 * Choose most appropriate template
406 *
407 * Auxiliary function to resolve the most appropriate template trying to find
408 * a content specific template in the theme or module dir before falling back
409 * on a generic template also in those dirs.
410 *
411 * @param format
412 * format of the PF page being rendered (html, pdf, etc.)
413 * @param $type
414 * name of the node type being rendered in a PF page
415 * @return
416 * filename of the most suitable template
417 */
418 function _print_get_template($format = NULL, $type = NULL) {
419 $filenames = array();
420 // First try to find a template defined both for the format and then the type
421 if (!empty($format) && !empty($type)) {
422 $filenames[] = "print_$format.node-$type.tpl.php";
423 }
424 // Then only for the format
425 if (!empty($format)) {
426 $filenames[] = "print_$format.tpl.php";
427 }
428 // If the node type is known, then try to find that type's template file
429 if (!empty($type)) {
430 $filenames[] = "print.node-$type.tpl.php";
431 }
432 // Finally search for a generic template file
433 $filenames[] = 'print.tpl.php';
434
435 foreach ($filenames as $value) {
436 // First in the theme directory
437 $file = drupal_get_path('theme', $GLOBALS['theme_key']) .'/'. $value;
438 if (file_exists($file)) {
439 return $file;
440 }
441 // Then in the module directory
442 $file = drupal_get_path('module', 'print') .'/'. $value;
443 if (file_exists($file)) {
444 return $file;
445 }
446 }
447 }
448
449 /**
450 * Prepare a Printer-friendly-ready node body for content nodes
451 *
452 * @param $nid
453 * node ID of the node to be rendered into a printer-friendly page
454 * @param $cid
455 * comment ID of the individual comment to be rendered
456 * @param $teaser
457 * if set to TRUE, outputs only the node's teaser
458 * @param $message
459 * optional sender's message (used by the send e-mail module)
460 * @return
461 * filled array ready to be used in the template
462 */
463 function _print_generate_node($nid, $cid = NULL, $teaser = FALSE, $message = NULL) {
464 // We can take a node id
465 $node = node_load(array('nid' => $nid));
466 if (!node_access('view', $node)) {
467 // Access is denied
468 return drupal_access_denied();
469 }
470 drupal_set_title($node->title);
471
472 //alert other modules that we are generating a printer-friendly page, so they can choose to show/hide info
473 $node->printing = TRUE;
474 // Turn off Pagination by the Paging module
475 unset($node->pages);
476 unset($node->page_count);
477
478 if ($teaser) {
479 unset($node->body);
480 }
481 else {
482 unset($node->teaser);
483 }
484 $node = (object)$node;
485 if ($cid === NULL) {
486 // Adapted (simplified) version of node_view for Drupal 5.x
487 //Render the node content
488 $node = node_build_content($node, $teaser, TRUE);
489 // Disable fivestar widget output
490 unset($node->content['fivestar_widget']);
491 // Disable service links module output
492 unset($node->content['service_links']);
493
494 $node->body = drupal_render($node->content);
495 //TODO the following was part of the fix for http://drupal.org/node/254863
496 //check if it is reproducible and find the exact condition which
497 //triggered it
498 //$node->body = html_entity_decode($node->body);
499 }
500
501 $print_comments = variable_get('print_comments', PRINT_COMMENTS_DEFAULT);
502
503 if (function_exists('comment_render') && (($cid != NULL) || ($print_comments))) {
504 //Print only the requested comment (or if $cid is NULL, all of them)
505 $comments = comment_render($node, $cid);
506
507 //Remove the comment forms
508 $comments = preg_replace('!<form.*?id="comment-.*?">.*?</form>!sim', '', $comments);
509 //Remove the 'Post new comment' title
510 $comments = preg_replace('!<h2.*?>Post new comment</h2>!', '', $comments);
511 //Remove the comment title hyperlink
512 $comments = preg_replace('!(<h3.*?>)(<a.*?>)(.*?)</a>(</h3>)!', '$1$3$4', $comments);
513 //Remove the comment author link
514 $pattern = '!(<span class="submitted">)(.*?)<a.*?>(.*?)</a>(</span>)!sim';
515 if (preg_match($pattern, $comments)) {
516 $comments = preg_replace($pattern , '$1$2$3$4', $comments);
517 }
518 //Remove the comment links
519 $comments = preg_replace('!\s*<ul class="links">.*?</ul>!sim', '', $comments);
520 if ($cid != NULL) {
521 // Single comment requested, output only the comment
522 unset($node->body);
523 }
524 $node->body .= $comments;
525 }
526
527 node_invoke_nodeapi($node, 'alter', FALSE, TRUE);
528
529 // Convert the a href elements
530 $pattern = '!<(a\s[^>]*?)>(.*?)(</a>)!is';
531 $node->body = preg_replace_callback($pattern, '_print_rewrite_urls', $node->body);
532
533 init_theme();
534
535 $print = _print_var_generator($node, $message, $cid);
536
537 return $print;
538 }
539
540 /**
541 * Prepare a Printer-friendly-ready node body for non-content pages
542 *
543 * @param $path
544 * path of the node to be rendered into a printer-friendly page
545 * @param $teaser
546 * if set to TRUE, outputs only the node's teaser
547 * @param $message
548 * optional sender's message (used by the send e-mail module)
549 * @return
550 * filled array ready to be used in the template
551 */
552 function _print_generate_path($path, $teaser = FALSE, $message = NULL) {
553 $path = drupal_get_normal_path($path);
554
555 menu_set_active_item($path);
556 // Adapted from index.php.
557 $node = new stdClass();
558 $node->body = menu_execute_active_handler($path);
559 $node->title = drupal_get_title();
560 $node->path = $path;
561
562 // It may happen that a drupal_not_found is called in the above call
563 if (preg_match('/404 Not Found/', drupal_get_headers()) == 1) {
564 return;
565 }
566
567 if (is_int($node->body)) {
568 switch ($node->body) {
569 case MENU_NOT_FOUND:
570 return drupal_not_found();
571 break;
572 case MENU_ACCESS_DENIED:
573 return drupal_access_denied();
574 break;
575 }
576 }
577
578 // Delete any links area
579 $node->body = preg_replace('!\s*<div class="links">.*?</div>!sim', '', $node->body);
580
581 // Convert the a href elements
582 $pattern = '!<(a\s[^>]*?)>(.*?)(</a>)!is';
583 $node->body = preg_replace_callback($pattern, '_print_rewrite_urls', $node->body);
584
585 init_theme();
586
587 $print = _print_var_generator($node, $message);
588
589 return $print;
590 }
591
592
593 /**
594 * Prepare a Printer-friendly-ready node body for book pages
595 *
596 * @param $nid
597 * node ID of the node to be rendered into a printer-friendly page
598 * @param $teaser
599 * if set to TRUE, outputs only the node's teaser
600 * @param $message
601 * optional sender's message (used by the send e-mail module)
602 * @return
603 * filled array ready to be used in the template
604 */
605 function _print_generate_book($nid, $teaser = FALSE, $message = NULL) {
606 $node = node_load(array('nid' => $nid));
607 if (!node_access('view', $node) || (!user_access('access printer-friendly version'))) {
608 // Access is denied
609 return drupal_access_denied();
610 }
611
612 $tree = book_menu_subtree_data($node->book);
613 $node->body = book_export_traverse($tree, 'book_node_export');
614
615 // Convert the a href elements
616 $pattern = '!<(a\s[^>]*?)>(.*?)(</a>)!is';
617 $node->body = preg_replace_callback($pattern, '_print_rewrite_urls', $node->body);
618
619 init_theme();
620
621 $print = _print_var_generator($node, $message);
622 // The title is already displayed by the book_recurse, so avoid duplication
623 $print['title'] = '';
624
625 return $print;
626 }