print_pdf.module 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. <?php
  2. /**
  3. * @file
  4. * Displays Printer-friendly versions of Drupal pages.
  5. *
  6. * @ingroup print
  7. */
  8. define('PRINT_PDF_PDF_TOOL_DEFAULT', FALSE);
  9. define('PRINT_PDF_CONTENT_DISPOSITION_DEFAULT', 2);
  10. define('PRINT_PDF_PAPER_SIZE_DEFAULT', 'A4');
  11. define('PRINT_PDF_PAGE_ORIENTATION_DEFAULT', 'portrait');
  12. define('PRINT_PDF_IMAGES_VIA_FILE_DEFAULT', 0);
  13. define('PRINT_PDF_AUTOCONFIG_DEFAULT', 1);
  14. define('PRINT_PDF_FILENAME_DEFAULT', '[site:name] - [node:title] - [node:changed:custom:Y-m-d]');
  15. define('PRINT_PDF_CACHE_ENABLED_DEFAULT', 0);
  16. // 1 day.
  17. define('PRINT_PDF_CACHE_LIFETIME_DEFAULT', 86400);
  18. /**
  19. * Implements hook_print_link().
  20. */
  21. function print_pdf_print_link() {
  22. return array(
  23. 'format' => 'pdf',
  24. 'text' => t('PDF version'),
  25. 'description' => t('Display a PDF version of this page.'),
  26. 'path' => 'printpdf',
  27. 'class' => 'print-pdf',
  28. 'icon' => 'pdf_icon.png',
  29. 'module' => 'print_pdf',
  30. );
  31. }
  32. /**
  33. * Implements hook_print_new_window_alter().
  34. */
  35. function print_pdf_print_new_window_alter(&$new_window, $format) {
  36. $new_window = (variable_get('print_pdf_content_disposition', PRINT_PDF_CONTENT_DISPOSITION_DEFAULT) == 1);
  37. }
  38. /**
  39. * Implements hook_permission().
  40. */
  41. function print_pdf_permission() {
  42. return array(
  43. 'access PDF version' => array(
  44. 'title' => t('Access the PDF version'),
  45. 'description' => t('View the PDF versions and the links to them in the original pages.'),
  46. ),
  47. );
  48. }
  49. /**
  50. * Implements hook_init().
  51. */
  52. function print_pdf_init() {
  53. if (variable_get('print_pdf_autoconfig', PRINT_PDF_AUTOCONFIG_DEFAULT)) {
  54. $print_pdf_pdf_tool = variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT);
  55. $tool = explode('|', $print_pdf_pdf_tool);
  56. $function = $tool[0] . '_pdf_tool_info';
  57. $info = function_exists($function) ? $function() : array();
  58. if (isset($info['public_dirs'])) {
  59. foreach ($info['public_dirs'] as $dir) {
  60. $directory = 'public://print_pdf/' . $tool[0] . '/' . $dir;
  61. /** @var DrupalLocalStreamWrapper $wrapper */
  62. $wrapper = file_stream_wrapper_get_instance_by_uri($directory);
  63. $real_directory_path = $wrapper->getDirectoryPath() . "/" . file_uri_target($directory);
  64. $result = file_prepare_directory($real_directory_path, FILE_CREATE_DIRECTORY);
  65. if (!$result) {
  66. watchdog('print_pdf', 'Failed to create directory "%dir" for %tool.', array('%dir' => $directory, '%tool' => $tool[0]), WATCHDOG_CRITICAL);
  67. }
  68. }
  69. }
  70. }
  71. if (variable_get('print_pdf_cache_enabled', PRINT_PDF_CACHE_ENABLED_DEFAULT)) {
  72. $directory = print_pdf_cache_dir();
  73. $wrapper = file_stream_wrapper_get_instance_by_uri($directory);
  74. $real_directory_path = $wrapper->getDirectoryPath() . "/" . file_uri_target($directory);
  75. $result = file_prepare_directory($real_directory_path, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY);
  76. if (!$result) {
  77. watchdog('print_pdf', 'Failed to create directory "%dir" for print_pdf cache.', array('%dir' => $directory), WATCHDOG_CRITICAL);
  78. }
  79. }
  80. }
  81. /**
  82. * Implements hook_flush_caches().
  83. */
  84. function print_pdf_flush_caches() {
  85. print_pdf_cache_delete();
  86. return array();
  87. }
  88. /**
  89. * Implements hook_menu().
  90. */
  91. function print_pdf_menu() {
  92. $link = print_pdf_print_link();
  93. $items = array();
  94. $items[$link['path']] = array(
  95. 'title' => 'Printer-friendly PDF',
  96. 'page callback' => 'print_pdf_controller',
  97. 'access arguments' => array('access PDF version'),
  98. 'type' => MENU_CALLBACK,
  99. 'file' => 'print_pdf.pages.inc',
  100. );
  101. $items[$link['path'] . '/' . $link['path']] = array(
  102. 'access callback' => FALSE,
  103. );
  104. $items['admin/config/user-interface/print/pdf'] = array(
  105. 'title' => 'PDF',
  106. 'description' => 'Configure the settings of the PDF generation functionality.',
  107. 'page callback' => 'drupal_get_form',
  108. 'page arguments' => array('print_pdf_settings'),
  109. 'access arguments' => array('administer print'),
  110. 'weight' => 3,
  111. 'type' => MENU_LOCAL_TASK,
  112. 'file' => 'print_pdf.admin.inc',
  113. );
  114. $items['admin/config/user-interface/print/pdf/options'] = array(
  115. 'title' => 'Options',
  116. 'weight' => -1,
  117. 'type' => MENU_DEFAULT_LOCAL_TASK,
  118. );
  119. return $items;
  120. }
  121. /**
  122. * Implements hook_variable_info().
  123. */
  124. function print_pdf_variable_info($options) {
  125. $link = print_pdf_print_link();
  126. $variable['print_pdf_link_text'] = array(
  127. 'title' => t('PDF version'),
  128. 'description' => t('Text used in the link to the PDF version.'),
  129. 'type' => 'string',
  130. 'default' => t($link['text']),
  131. );
  132. return $variable;
  133. }
  134. /**
  135. * Implements hook_block_info().
  136. */
  137. function print_pdf_block_info() {
  138. $block['print_pdf-top']['info'] = t('Most PDFd');
  139. $block['print_pdf-top']['cache'] = DRUPAL_CACHE_GLOBAL;
  140. return $block;
  141. }
  142. /**
  143. * Implements hook_block_view().
  144. */
  145. function print_pdf_block_view($delta = 0) {
  146. $block = array();
  147. switch ($delta) {
  148. case 'print_pdf-top':
  149. $block['subject'] = t('Most PDFd');
  150. $result = db_query_range("SELECT path FROM {print_pdf_page_counter} LEFT JOIN {node} n ON path = CONCAT('node/', n.nid) WHERE status <> 0 OR status IS NULL ORDER BY totalcount DESC", 0, 3)
  151. ->fetchAll();
  152. if (count($result)) {
  153. $items = array();
  154. foreach ($result as $obj) {
  155. $items[] = l(_print_get_title($obj->path), $obj->path);
  156. }
  157. $block['content'] = theme('item_list', array('items' => $items, 'type' => 'ul'));
  158. }
  159. break;
  160. }
  161. return $block;
  162. }
  163. /**
  164. * Implements hook_node_load().
  165. */
  166. function print_pdf_node_load($nodes, $types) {
  167. $ids = array();
  168. foreach ($nodes as $node) {
  169. $ids[] = $node->nid;
  170. }
  171. $link = print_pdf_print_link();
  172. $size = 'print_' . $link['format'] . '_size';
  173. $orientation = 'print_' . $link['format'] . '_orientation';
  174. $result = db_query('SELECT nid, size, orientation FROM {print_pdf_node_conf} WHERE nid IN (:nids)', array(':nids' => $ids))->fetchAllAssoc('nid');
  175. foreach ($nodes as $node) {
  176. $node->{$size} = (isset($result[$node->nid]) && !empty($result[$node->nid]->size)) ? $result[$node->nid]->size : variable_get($size . '_' . $node->type);
  177. $node->{$orientation} = (isset($result[$node->nid]) && !empty($result[$node->nid]->orientation)) ? $result[$node->nid]->orientation : variable_get($orientation . '_' . $node->type);
  178. }
  179. }
  180. /**
  181. * Implements hook_node_insert().
  182. */
  183. function print_pdf_node_insert($node) {
  184. print_pdf_node_update($node);
  185. }
  186. /**
  187. * Implements hook_node_update().
  188. */
  189. function print_pdf_node_update($node) {
  190. if (user_access('administer print') || user_access('node-specific print configuration')) {
  191. $link = print_pdf_print_link();
  192. $size = 'print_' . $link['format'] . '_size';
  193. $orientation = 'print_' . $link['format'] . '_orientation';
  194. if (!isset($node->{$size})) {
  195. $node->{$size} = variable_get($size . '_' . $node->type);
  196. }
  197. if (!isset($node->{$orientation})) {
  198. $node->{$orientation} = variable_get($orientation . '_' . $node->type);
  199. }
  200. db_merge('print_pdf_node_conf')
  201. ->key(array('nid' => $node->nid))
  202. ->fields(array(
  203. 'size' => $node->{$size},
  204. 'orientation' => $node->{$orientation},
  205. ))
  206. ->execute();
  207. }
  208. print_pdf_cache_delete($node->nid);
  209. }
  210. /**
  211. * Implements hook_node_delete().
  212. */
  213. function print_pdf_node_delete($node) {
  214. db_delete('print_pdf_page_counter')
  215. ->condition('path', 'node/' . $node->nid)
  216. ->execute();
  217. print_pdf_cache_delete($node->nid);
  218. }
  219. /**
  220. * Implements hook_form_alter().
  221. */
  222. function print_pdf_form_alter(&$form, &$form_state, $form_id) {
  223. // Add the node-type settings to activate the printer-friendly version link.
  224. if ((user_access('administer print') || user_access('node-specific print configuration')) &&
  225. (($form_id == 'node_type_form') || !empty($form['#node_edit_form']))) {
  226. $link = print_pdf_print_link();
  227. $size = 'print_' . $link['format'] . '_size';
  228. $orientation = 'print_' . $link['format'] . '_orientation';
  229. $form['print']['print_' . $link['format']][$size] = array(
  230. '#type' => 'select',
  231. '#title' => t('Paper size'),
  232. '#options' => _print_pdf_paper_sizes(TRUE),
  233. '#description' => t('Choose the paper size of the generated PDF.'),
  234. );
  235. $form['print']['print_' . $link['format']][$orientation] = array(
  236. '#type' => 'select',
  237. '#title' => t('Page orientation'),
  238. '#options' => array(
  239. '' => 'Unchanged',
  240. 'portrait' => t('Portrait'),
  241. 'landscape' => t('Landscape'),
  242. ),
  243. '#description' => t('Choose the page orientation of the generated PDF.'),
  244. );
  245. if ($form_id == 'node_type_form') {
  246. $form['print']['print_' . $link['format']][$size]['#default_value'] = variable_get($size . '_' . $form['#node_type']->type);
  247. $form['print']['print_' . $link['format']][$orientation]['#default_value'] = variable_get($orientation . '_' . $form['#node_type']->type);
  248. }
  249. else {
  250. $node = $form['#node'];
  251. $form['print']['print_' . $link['format']][$size]['#default_value'] = isset($node->{$size}) ? $node->{$size} : variable_get($size . '_' . $node->type);
  252. $form['print']['print_' . $link['format']][$orientation]['#default_value'] = isset($node->{$orientation}) ? $node->{$orientation} : variable_get($orientation . '_' . $node->type);
  253. }
  254. }
  255. }
  256. /**
  257. * Auxiliary function to display a formatted PDF version link.
  258. *
  259. * Function made available so that developers may call this function from
  260. * their defined pages/blocks.
  261. *
  262. * @param string $path
  263. * Path to be used in the link. If not specified, the current URL is used.
  264. * @param object $node
  265. * Node object, to be used in checking node access. If the path argument is
  266. * not provided, the path used will be node/nid.
  267. * @param string $location
  268. * Where in the page where the link is being inserted ('link', 'corner',
  269. * 'block', 'help').
  270. *
  271. * @return bool
  272. * string with the HTML link to the printer-friendly page
  273. *
  274. * @ingroup print_api
  275. */
  276. function print_pdf_insert_link($path = NULL, $node = NULL, $location = '') {
  277. if (function_exists('print_ui_insert_link')) {
  278. return print_ui_insert_link(print_pdf_print_link(), array(
  279. 'path' => $path,
  280. 'node' => $node,
  281. 'location' => $location,
  282. ));
  283. }
  284. else {
  285. return FALSE;
  286. }
  287. }
  288. /**
  289. * Check if the link to the PDF version is allowed depending on the settings.
  290. *
  291. * @param array $args
  292. * Array containing the possible parameters:
  293. * view_mode, node, type, path.
  294. *
  295. * @return bool
  296. * FALSE if not allowed, TRUE otherwise
  297. */
  298. function print_pdf_link_allowed($args) {
  299. $print_pdf_pdf_tool = variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT);
  300. return (user_access('access PDF version') && (!empty($print_pdf_pdf_tool)));
  301. }
  302. /**
  303. * Implements hook_cron().
  304. */
  305. function print_pdf_cron() {
  306. print_pdf_cache_clean();
  307. }
  308. /**
  309. * Removes pdf files for nodes/paths if they are older than the lifetime.
  310. */
  311. function print_pdf_cache_clean() {
  312. $lifetime = variable_get('print_pdf_cache_lifetime', PRINT_PDF_CACHE_LIFETIME_DEFAULT);
  313. if ($lifetime > 0) {
  314. $files = file_scan_directory(print_pdf_cache_dir(), '!\d+\.pdf$!');
  315. foreach ($files as $file) {
  316. // For all files in the cache directory, see when they were last accessed.
  317. $result = db_query("SELECT timestamp FROM {print_pdf_page_counter} WHERE path = :path", array(':path' => 'node/' . $file->name))
  318. ->fetchField();
  319. // Keep the file only if last access was within the cache max life value.
  320. if (($result === FALSE) || ($result + $lifetime < REQUEST_TIME)) {
  321. print_pdf_cache_delete($file->name);
  322. }
  323. }
  324. }
  325. }
  326. /**
  327. * Returns the cache directory.
  328. *
  329. * @return string
  330. * The scheme://path of the cache directory
  331. */
  332. function print_pdf_cache_dir() {
  333. global $language_url;
  334. $scheme = 'private';
  335. if (!file_stream_wrapper_valid_scheme($scheme)) {
  336. $scheme = 'temporary';
  337. }
  338. return $scheme . '://print_pdf/cache/' . $language_url->language;
  339. }
  340. /**
  341. * Deletes one or more files from the PDF cache directory.
  342. *
  343. * @param int $nid
  344. * The node ID of the page for which the cached PDF should be deleted.
  345. * If not provided, the entire cache directory will be deleted.
  346. */
  347. function print_pdf_cache_delete($nid = NULL) {
  348. $directory = print_pdf_cache_dir();
  349. if ($nid) {
  350. $filename = $directory . '/' . $nid . '.pdf';
  351. if (is_file($filename)) {
  352. file_unmanaged_delete($filename);
  353. }
  354. }
  355. else {
  356. // If no nid is provided, flush the entire cache.
  357. if (is_dir($directory)) {
  358. file_unmanaged_delete_recursive($directory);
  359. }
  360. }
  361. }
  362. /**
  363. * Displays the PDF as inline or a downloadable file.
  364. *
  365. * @param string $pdf
  366. * PDF content string.
  367. * @param string $filename
  368. * Filename of the generated PDF.
  369. *
  370. * @return string
  371. * The disposed PDF file
  372. */
  373. function print_pdf_dispose_content($pdf, $filename) {
  374. if (headers_sent()) {
  375. exit('Unable to stream pdf: headers already sent');
  376. }
  377. header('Cache-Control: private');
  378. header('Content-Type: application/pdf');
  379. $content_disposition = variable_get('print_pdf_content_disposition', PRINT_PDF_CONTENT_DISPOSITION_DEFAULT);
  380. $attachment = ($content_disposition == 2) ? 'attachment' : 'inline';
  381. header("Content-Disposition: $attachment; filename=\"$filename\"");
  382. echo $pdf;
  383. flush();
  384. return TRUE;
  385. }
  386. /**
  387. * Generate a PDF version of the provided HTML.
  388. *
  389. * @param string $html
  390. * HTML content of the PDF.
  391. * @param array $meta
  392. * Meta information to be used in the PDF
  393. * - url: original URL
  394. * - name: author's name
  395. * - title: Page title
  396. * - node: node object.
  397. * @param string $filename
  398. * (optional) Filename of the generated PDF.
  399. * @param string $paper_size
  400. * (optional) Paper size of the generated PDF.
  401. * @param string $page_orientation
  402. * (optional) Page orientation of the generated PDF.
  403. *
  404. * @return string|null
  405. * generated PDF page, or NULL in case of error
  406. *
  407. * @see print_pdf_controller()
  408. *
  409. * @ingroup print_api
  410. */
  411. function print_pdf_generate_html($html, $meta, $filename = NULL, $paper_size = NULL, $page_orientation = NULL) {
  412. $pdf_tool = explode('|', variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT));
  413. module_load_include('inc', $pdf_tool[0], $pdf_tool[0] . '.pages');
  414. $function = $pdf_tool[0] . '_print_pdf_generate';
  415. $pdf = function_exists($function) ? $function($html, $meta, $paper_size, $page_orientation) : NULL;
  416. if ($filename) {
  417. return print_pdf_dispose_content($pdf, $filename);
  418. }
  419. return $pdf;
  420. }
  421. /**
  422. * Implements hook_views_api().
  423. */
  424. function print_pdf_views_api() {
  425. return array(
  426. 'api' => 2.0,
  427. 'path' => drupal_get_path('module', 'print_pdf'),
  428. );
  429. }
  430. /**
  431. * Lists all possible paper sizes.
  432. *
  433. * @param bool $include_default
  434. * Flag indicating whether to include the tool's default value.
  435. *
  436. * @return array
  437. * array of strings with the available paper sizes
  438. */
  439. function _print_pdf_paper_sizes($include_default = FALSE) {
  440. $ret = ($include_default) ? array('' => 'Unchanged') : array();
  441. $ret += array(
  442. '4A0' => '4A0',
  443. '2A0' => '2A0',
  444. 'A0' => 'A0',
  445. 'A1' => 'A1',
  446. 'A2' => 'A2',
  447. 'A3' => 'A3',
  448. 'A4' => 'A4',
  449. 'A5' => 'A5',
  450. 'A6' => 'A6',
  451. 'A7' => 'A7',
  452. 'A8' => 'A8',
  453. 'A9' => 'A9',
  454. 'A10' => 'A10',
  455. 'B0' => 'B0',
  456. 'B1' => 'B1',
  457. 'B2' => 'B2',
  458. 'B3' => 'B3',
  459. 'B4' => 'B4',
  460. 'B5' => 'B5',
  461. 'B6' => 'B6',
  462. 'B7' => 'B7',
  463. 'B8' => 'B8',
  464. 'B9' => 'B9',
  465. 'B10' => 'B10',
  466. 'C0' => 'C0',
  467. 'C1' => 'C1',
  468. 'C2' => 'C2',
  469. 'C3' => 'C3',
  470. 'C4' => 'C4',
  471. 'C5' => 'C5',
  472. 'C6' => 'C6',
  473. 'C7' => 'C7',
  474. 'C8' => 'C8',
  475. 'C9' => 'C9',
  476. 'C10' => 'C10',
  477. 'RA0' => 'RA0',
  478. 'RA1' => 'RA1',
  479. 'RA2' => 'RA2',
  480. 'RA3' => 'RA3',
  481. 'RA4' => 'RA4',
  482. 'SRA0' => 'SRA0',
  483. 'SRA1' => 'SRA1',
  484. 'SRA2' => 'SRA2',
  485. 'SRA3' => 'SRA3',
  486. 'SRA4' => 'SRA4',
  487. 'LETTER' => 'Letter',
  488. 'LEGAL' => 'Legal',
  489. 'EXECUTIVE' => 'Executive',
  490. 'FOLIO' => 'Folio',
  491. );
  492. return $ret;
  493. }