print_pdf.module 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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', 0);
  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. define('PRINT_PDF_CACHE_LIFETIME_DEFAULT', 86400); // 1 day
  17. /**
  18. * Implements hook_print_link().
  19. */
  20. function print_pdf_print_link() {
  21. return array(
  22. 'format' => 'pdf',
  23. 'text' => t('PDF version'),
  24. 'description' => t('Display a PDF version of this page.'),
  25. 'path' => 'printpdf',
  26. 'class' => 'print-pdf',
  27. 'icon' => 'pdf_icon.png',
  28. 'module' => 'print_pdf',
  29. );
  30. }
  31. /**
  32. * Implements hook_print_new_window_alter().
  33. */
  34. function print_pdf_print_new_window_alter(&$new_window, $format) {
  35. $new_window = (variable_get('print_pdf_content_disposition', PRINT_PDF_CONTENT_DISPOSITION_DEFAULT) == 1);
  36. }
  37. /**
  38. * Implements hook_permission().
  39. */
  40. function print_pdf_permission() {
  41. return array(
  42. 'access PDF version' => array(
  43. 'title' => t('Access the PDF version'),
  44. 'description' => t('View the PDF versions and the links to them in the original pages.'),
  45. ),
  46. );
  47. }
  48. /**
  49. * Implements hook_init().
  50. */
  51. function print_pdf_init() {
  52. if (variable_get('print_pdf_autoconfig', PRINT_PDF_AUTOCONFIG_DEFAULT)) {
  53. $print_pdf_pdf_tool = variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT);
  54. $tool = explode('|', $print_pdf_pdf_tool);
  55. $function = $tool[0] . '_pdf_tool_info';
  56. if (function_exists($function)) {
  57. $info = $function();
  58. }
  59. if (isset($info['public_dirs'])) {
  60. foreach ($info['public_dirs'] as $dir) {
  61. $directory = 'public://print_pdf/' . $tool[0] . '/' . $dir;
  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. switch ($delta) {
  147. case 'print_pdf-top':
  148. $block['subject'] = t('Most PDFd');
  149. $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)
  150. ->fetchAll();
  151. if (count($result)) {
  152. $items = array();
  153. foreach ($result as $obj) {
  154. $items[] = l(_print_get_title($obj->path), $obj->path);
  155. }
  156. $block['content'] = theme('item_list', array('items' => $items, 'type' => 'ul'));
  157. }
  158. break;
  159. }
  160. return $block;
  161. }
  162. /**
  163. * Implements hook_requirements().
  164. */
  165. function print_pdf_requirements($phase) {
  166. $requirements = array();
  167. $t = get_t();
  168. switch ($phase) {
  169. // At runtime, make sure that a PDF generation tool is selected
  170. case 'runtime':
  171. $print_pdf_pdf_tool = variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT);
  172. if (empty($print_pdf_pdf_tool)) {
  173. $requirements['print_pdf_tool'] = array(
  174. 'title' => $t('Printer, email and PDF versions - PDF generation library'),
  175. 'value' => $t('No PDF tool selected'),
  176. 'description' => $t('Please configure it in the !url.', array('!url' => l($t('PDF settings page'), 'admin/config/user-interface/print/pdf'))),
  177. 'severity' => REQUIREMENT_ERROR,
  178. );
  179. }
  180. else {
  181. // Tool is defined, get some data from it's handler module
  182. $tool = explode('|', $print_pdf_pdf_tool);
  183. $function = $tool[0] . '_pdf_tool_info';
  184. if (function_exists($function)) {
  185. $info = $function();
  186. }
  187. // Is the file there?
  188. if (!is_file($tool[1]) || !is_readable($tool[1])) {
  189. $requirements['print_pdf_tool'] = array(
  190. 'title' => $t('Printer, email and PDF versions - PDF generation library'),
  191. 'value' => $t('File not found'),
  192. 'description' => $t('The currently selected PDF generation library (%file) is no longer accessible.', array('%file' => $tool[1])),
  193. 'severity' => REQUIREMENT_ERROR,
  194. );
  195. }
  196. else {
  197. // Get the version number
  198. $function = $tool[0] . '_pdf_tool_version';
  199. if (function_exists($function)) {
  200. $version = $function($tool[1]);
  201. if (isset($info['min_version']) && version_compare($version, $info['min_version'], '<')) {
  202. $requirements['print_pdf_tool_version'] = array(
  203. 'title' => $t('Printer, email and PDF versions - PDF generation library'),
  204. 'value' => $t('Unsupported %lib version', array('%lib' => $info['name'])),
  205. 'description' => $t('The currently selected version of %lib (@version) is not supported. Please update to a !url.',
  206. array('%lib' => $info['name'], '@version' => $version, '!url' => l($t('newer version'), $info['url']))),
  207. 'severity' => REQUIREMENT_ERROR,
  208. );
  209. }
  210. else {
  211. $requirements['print_pdf_tool_version'] = array(
  212. 'title' => $t('Printer, email and PDF versions - PDF generation library'),
  213. 'value' => $info['name'] . ' ' . $version,
  214. );
  215. }
  216. }
  217. }
  218. // If auto-config is on, check for write access to the appropriate dirs
  219. if (variable_get('print_pdf_autoconfig', PRINT_PDF_AUTOCONFIG_DEFAULT)) {
  220. $directories = array();
  221. if (isset($info['public_dirs'])) {
  222. foreach ($info['public_dirs'] as $dir) {
  223. $directories[] = 'public://print_pdf/' . $tool[0] . '/' . $dir;
  224. }
  225. }
  226. if (isset($info['tool_dirs'])) {
  227. foreach ($info['tool_dirs'] as $dir) {
  228. $directories[] = dirname($tool[1]) . '/' . $dir;
  229. }
  230. }
  231. foreach ($directories as $dir) {
  232. if (!is_dir($dir) || !is_writable($dir)) {
  233. $requirements['print_pdf_tool_' . $dir] = array(
  234. 'title' => $t('%lib directory', array('%lib' => $info['name'])),
  235. 'value' => $t('Non-writable permissions'),
  236. 'description' => $t('You must change the %libdir permissions to be writable, as %lib requires write-access to that directory.', array('%lib' => $info['name'], '%libdir' => $dir)),
  237. 'severity' => REQUIREMENT_ERROR,
  238. );
  239. }
  240. }
  241. }
  242. }
  243. break;
  244. }
  245. return $requirements;
  246. }
  247. /**
  248. * Implements hook_node_load().
  249. */
  250. function print_pdf_node_load($nodes, $types) {
  251. $ids = array();
  252. foreach ($nodes as $node) {
  253. $ids[] = $node->nid;
  254. }
  255. $link = print_pdf_print_link();
  256. $size = 'print_' . $link['format'] . '_size';
  257. $orientation = 'print_' . $link['format'] . '_orientation';
  258. $result = db_query('SELECT nid, size, orientation FROM {print_pdf_node_conf} WHERE nid IN (:nids)', array(':nids' => $ids))->fetchAllAssoc('nid');
  259. foreach ($nodes as $node) {
  260. $node->{$size} = (isset($result[$node->nid]) && !empty($result[$node->nid]->size)) ? $result[$node->nid]->size : variable_get($size . '_' . $node->type);
  261. $node->{$orientation} = (isset($result[$node->nid]) && !empty($result[$node->nid]->orientation)) ? $result[$node->nid]->orientation : variable_get($orientation . '_' . $node->type);
  262. }
  263. }
  264. /**
  265. * Implements hook_node_insert().
  266. */
  267. function print_pdf_node_insert($node) {
  268. return print_pdf_node_update($node);
  269. }
  270. /**
  271. * Implements hook_node_update().
  272. */
  273. function print_pdf_node_update($node) {
  274. if (user_access('administer print') || user_access('node-specific print configuration')) {
  275. $link = print_pdf_print_link();
  276. $size = 'print_' . $link['format'] . '_size';
  277. $orientation = 'print_' . $link['format'] . '_orientation';
  278. if (!isset($node->{$size})) $node->{$size} = variable_get($size . '_' . $node->type);
  279. if (!isset($node->{$orientation})) $node->{$orientation} = variable_get($orientation . '_' . $node->type);
  280. db_merge('print_pdf_node_conf')
  281. ->key(array('nid' => $node->nid))
  282. ->fields(array(
  283. 'size' => $node->{$size},
  284. 'orientation' => $node->{$orientation},
  285. ))
  286. ->execute();
  287. }
  288. print_pdf_cache_delete($node->nid);
  289. }
  290. /**
  291. * Implements hook_node_delete().
  292. */
  293. function print_pdf_node_delete($node) {
  294. db_delete('print_pdf_page_counter')
  295. ->condition('path', 'node/' . $node->nid)
  296. ->execute();
  297. print_pdf_cache_delete($node->nid);
  298. }
  299. /**
  300. * Implements hook_form_alter().
  301. */
  302. function print_pdf_form_alter(&$form, &$form_state, $form_id) {
  303. // Add the node-type settings option to activate the printer-friendly version link
  304. if ((user_access('administer print') || user_access('node-specific print configuration')) &&
  305. (($form_id == 'node_type_form') || !empty($form['#node_edit_form']))) {
  306. $link = print_pdf_print_link();
  307. $size = 'print_' . $link['format'] . '_size';
  308. $orientation = 'print_' . $link['format'] . '_orientation';
  309. $form['print']['print_' . $link['format']][$size] = array(
  310. '#type' => 'select',
  311. '#title' => t('Paper size'),
  312. '#options' => _print_pdf_paper_sizes(TRUE),
  313. '#description' => t('Choose the paper size of the generated PDF.'),
  314. );
  315. $form['print']['print_' . $link['format']][$orientation] = array(
  316. '#type' => 'select',
  317. '#title' => t('Page orientation'),
  318. '#options' => array('' => 'Unchanged', 'portrait' => t('Portrait'), 'landscape' => t('Landscape')),
  319. '#description' => t('Choose the page orientation of the generated PDF.'),
  320. );
  321. if ($form_id == 'node_type_form') {
  322. $form['print']['print_' . $link['format']][$size]['#default_value'] = variable_get($size . '_' . $form['#node_type']->type);
  323. $form['print']['print_' . $link['format']][$orientation]['#default_value'] = variable_get($orientation . '_' . $form['#node_type']->type);
  324. }
  325. else {
  326. $node = $form['#node'];
  327. $form['print']['print_' . $link['format']][$size]['#default_value'] = isset($node->{$size}) ? $node->{$size} : variable_get($size . '_' . $node->type);
  328. $form['print']['print_' . $link['format']][$orientation]['#default_value'] = isset($node->{$orientation}) ? $node->{$orientation} : variable_get($orientation . '_' . $node->type);
  329. }
  330. }
  331. }
  332. /**
  333. * Auxiliary function to display a formatted PDF version link
  334. *
  335. * Function made available so that developers may call this function from
  336. * their defined pages/blocks.
  337. *
  338. * @param string $path
  339. * path to be used in the link. If not specified, the current URL is used.
  340. * @param object $node
  341. * node object, to be used in checking node access. If the path argument is
  342. * not provided, the path used will be node/nid.
  343. * @param string $location
  344. * where in the page where the link is being inserted ('link', 'corner',
  345. * 'block', 'help').
  346. *
  347. * @return bool
  348. * string with the HTML link to the printer-friendly page
  349. *
  350. * @ingroup print_api
  351. */
  352. function print_pdf_insert_link($path = NULL, $node = NULL, $location = '') {
  353. if (function_exists('print_ui_insert_link')) {
  354. return print_ui_insert_link(print_pdf_print_link(), array('path' => $path, 'node' => $node, 'location' => $location));
  355. }
  356. else {
  357. return FALSE;
  358. }
  359. }
  360. /**
  361. * Check if the link to the PDF version is allowed depending on the settings
  362. *
  363. * @param array $args
  364. * array containing the possible parameters:
  365. * view_mode, node, type, path
  366. *
  367. * @return bool
  368. * FALSE if not allowed, TRUE otherwise
  369. */
  370. function print_pdf_link_allowed($args) {
  371. $print_pdf_pdf_tool = variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT);
  372. return (user_access('access PDF version') && (!empty($print_pdf_pdf_tool)));
  373. }
  374. /**
  375. * Implements hook_cron().
  376. */
  377. function print_pdf_cron() {
  378. print_pdf_cache_clean();
  379. }
  380. /**
  381. * Removes pdf files for nodes/paths if they are older than the lifetime.
  382. */
  383. function print_pdf_cache_clean() {
  384. $lifetime = variable_get('print_pdf_cache_lifetime', PRINT_PDF_CACHE_LIFETIME_DEFAULT);
  385. if ($lifetime > 0) {
  386. $files = file_scan_directory(print_pdf_cache_dir(), '!\d+\.pdf$!');
  387. foreach ($files as $file) {
  388. // For all files in the cache directory, see when they were last accessed
  389. $result = db_query("SELECT timestamp FROM {print_pdf_page_counter} WHERE path = :path", array(':path' => 'node/' . $file->name))
  390. ->fetchField();
  391. // Keep the file only if the last access was within the cache max life value
  392. if (($result === FALSE) || ($result + $lifetime < REQUEST_TIME)) {
  393. print_pdf_cache_delete($file->name);
  394. }
  395. }
  396. }
  397. }
  398. /**
  399. * Returns the cache directory.
  400. *
  401. * @return string
  402. * The scheme://path of the cache directory
  403. */
  404. function print_pdf_cache_dir() {
  405. $scheme = 'private';
  406. if (!file_stream_wrapper_valid_scheme($scheme)) {
  407. $scheme = 'temporary';
  408. }
  409. return $scheme . '://print_pdf/cache';
  410. }
  411. /**
  412. * Deletes one or more files from the PDF cache directory.
  413. *
  414. * @param int nid
  415. * The node ID of the page for which the cached PDF should be deleted.
  416. * If not provided, the entire cache directory will be deleted.
  417. */
  418. function print_pdf_cache_delete($nid = NULL) {
  419. $directory = print_pdf_cache_dir();
  420. if ($nid) {
  421. $filename = $directory . '/' . $nid . '.pdf';
  422. if (is_file($filename)) {
  423. file_unmanaged_delete($filename);
  424. }
  425. }
  426. else {
  427. // If no nid is provided, flush the entire cache.
  428. if (is_dir($directory)) {
  429. file_unmanaged_delete_recursive($directory);
  430. }
  431. }
  432. }
  433. /**
  434. * Displays the PDF as inline or a downloadable file.
  435. *
  436. * @param string $pdf
  437. * PDF content string
  438. * @param string $filename
  439. * Filename of the generated PDF
  440. *
  441. * @return string
  442. * The disposed PDF file
  443. */
  444. function print_pdf_dispose_content($pdf, $filename) {
  445. if (headers_sent()) {
  446. exit('Unable to stream pdf: headers already sent');
  447. }
  448. header('Cache-Control: private');
  449. header('Content-Type: application/pdf');
  450. $content_disposition = variable_get('print_pdf_content_disposition', PRINT_PDF_CONTENT_DISPOSITION_DEFAULT);
  451. $attachment = ($content_disposition == 2) ? 'attachment' : 'inline';
  452. header("Content-Disposition: $attachment; filename=\"$filename\"");
  453. echo $pdf;
  454. flush();
  455. return TRUE;
  456. }
  457. /**
  458. * Generate a PDF version of the provided HTML.
  459. *
  460. * @param string $html
  461. * HTML content of the PDF
  462. * @param array $meta
  463. * Meta information to be used in the PDF
  464. * - url: original URL
  465. * - name: author's name
  466. * - title: Page title
  467. * - node: node object
  468. * @param string $filename
  469. * (optional) Filename of the generated PDF
  470. * @param string $paper_size
  471. * (optional) Paper size of the generated PDF
  472. * @param string $page_orientation
  473. * (optional) Page orientation of the generated PDF
  474. *
  475. * @return
  476. * generated PDF page, or NULL in case of error
  477. *
  478. * @see print_pdf_controller()
  479. *
  480. * @ingroup print_api
  481. */
  482. function print_pdf_generate_html($html, $meta, $filename = NULL, $paper_size = NULL, $page_orientation = NULL) {
  483. $pdf_tool = explode('|', variable_get('print_pdf_pdf_tool', PRINT_PDF_PDF_TOOL_DEFAULT));
  484. module_load_include('inc', $pdf_tool[0], $pdf_tool[0] . '.pages');
  485. $function = $pdf_tool[0] . '_print_pdf_generate';
  486. if (function_exists($function)) {
  487. $pdf = $function($html, $meta, $paper_size, $page_orientation);
  488. }
  489. if ($filename) {
  490. return print_pdf_dispose_content($pdf, $filename);
  491. }
  492. return $pdf;
  493. }
  494. /**
  495. * Implements hook_views_api().
  496. */
  497. function print_pdf_views_api() {
  498. return array(
  499. 'api' => 2.0,
  500. 'path' => drupal_get_path('module', 'print_pdf'),
  501. );
  502. }
  503. /**
  504. * Lists all possible paper sizes
  505. *
  506. * @return
  507. * array of strings with the available paper sizes
  508. */
  509. function _print_pdf_paper_sizes($include_default = FALSE) {
  510. $ret = array();
  511. $ret = ($include_default) ? array('' => 'Unchanged') : array();
  512. $ret += array(
  513. '4A0' => '4A0', '2A0' => '2A0', 'A0' => 'A0',
  514. 'A1' => 'A1', 'A2' => 'A2', 'A3' => 'A3', 'A4' => 'A4',
  515. 'A5' => 'A5', 'A6' => 'A6', 'A7' => 'A7', 'A8' => 'A8',
  516. 'A9' => 'A9', 'A10' => 'A10', 'B0' => 'B0', 'B1' => 'B1',
  517. 'B2' => 'B2', 'B3' => 'B3', 'B4' => 'B4', 'B5' => 'B5',
  518. 'B6' => 'B6', 'B7' => 'B7', 'B8' => 'B8', 'B9' => 'B9',
  519. 'B10' => 'B10', 'C0' => 'C0', 'C1' => 'C1', 'C2' => 'C2',
  520. 'C3' => 'C3', 'C4' => 'C4', 'C5' => 'C5', 'C6' => 'C6',
  521. 'C7' => 'C7', 'C8' => 'C8', 'C9' => 'C9', 'C10' => 'C10',
  522. 'RA0' => 'RA0', 'RA1' => 'RA1', 'RA2' => 'RA2',
  523. 'RA3' => 'RA3', 'RA4' => 'RA4', 'SRA0' => 'SRA0',
  524. 'SRA1' => 'SRA1', 'SRA2' => 'SRA2', 'SRA3' => 'SRA3',
  525. 'SRA4' => 'SRA4', 'LETTER' => 'Letter', 'LEGAL' => 'Legal',
  526. 'EXECUTIVE' => 'Executive', 'FOLIO' => 'Folio',
  527. );
  528. return $ret;
  529. }