comparison sites/all/modules/pdf_to_imagefield/pdf_to_image.module @ 4:ce11bbd8f642

added modules
author danieleb <danielebarchiesi@me.com>
date Thu, 19 Sep 2013 10:38:44 +0100
parents
children
comparison
equal deleted inserted replaced
3:b28be78d8160 4:ce11bbd8f642
1 <?php
2
3 /**
4 * @file
5 * Extends file fields with a process that generates a thumbnail image
6 * or multiple image pages from an uploaded PDF
7 *
8 * @author dman dan@coders.co.nz
9 *
10 * Based on earlier worn by Elaman, fatcrobat, InternetDevels.Com, and others
11 */
12
13 /**
14 * Announces that we have additional widget options that extend file fields.
15 *
16 * @see file_field_widget_info()
17 *
18 * Implements hook_field_widget_info().
19 */
20 function pdf_to_image_field_widget_info() {
21 return array(
22 'pdf_to_image' => array(
23 'label' => t('PDF to Image'),
24 'field types' => array('file'),
25 'settings' => array(
26 'pdf_to_image' => array(
27 'target_field' => NULL,
28 'density' => '25x25',
29 'extra_args' => '',
30 ),
31 ),
32 'behaviors' => array(
33 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
34 'default value' => FIELD_BEHAVIOR_NONE,
35 ),
36 ),
37 );
38 }
39
40
41 /**
42 * Adds options to the field configuration form in the content type admin setup.
43 *
44 * Implements hook_field_widget_settings_form().
45 */
46 function pdf_to_image_field_widget_settings_form($field, $instance) {
47 $widget = $instance['widget'];
48 $pdf_to_image_settings = $widget['settings']['pdf_to_image'];
49
50 // Use the file widget settings form.
51 $form = file_field_widget_settings_form($field, $instance);
52
53 // Plus our own extras
54 $form['pdf_to_image'] = array(
55 '#type' => 'fieldset',
56 '#title' => t('PDF conversion options'),
57 );
58
59 $fields = field_info_instances($instance['entity_type'], $instance['bundle']);
60 $options = array();
61 foreach ((array) $fields as $field) {
62 $field_info = field_info_field($field['field_name']);
63 if ($field_info['type'] == 'image') {
64 $options[$field['field_name']] = $field['label'];
65 }
66 }
67
68 // @TODO: make this field required.
69 $form['pdf_to_image']['target_field'] = array(
70 '#title' => t('Target Image Field'),
71 '#type' => 'select',
72 '#empty_option' => '<' . (count($options) ? t('No Image Field selected') : t('No Image Field found')) . '>',
73 '#default_value' => $pdf_to_image_settings['target_field'],
74 '#description' => t('PDF to Image field processing requires an image field where the resulting images of extracted PDF pages should be stored. The image field must be assigned to the same node type. For all pages to be processed, the image field should allow multiple uploads. If the image field allows only one item, only the cover page will be processed.'),
75 '#options' => $options,
76 );
77
78 $form['pdf_to_image']['density'] = array(
79 '#title' => t('Density used for rendering PDF'),
80 '#description' => t('Horizontal and vertical density of the image XxY (e.g. 100x100). Default is 25x25 (25%). <em>This is not image dimensions!</em> It\'s the sampling quality of the generated image. 100x100 means the generated image will be "full size" compared to a PDF viewed at 100%. If you are only wanting preview thumbnails, you might be fine (and a LOT faster) with just 20x20 (20% size) images. Only generate full-size (100x100) images if you intend to display full-size pages. To adjust the <b>display</b> sizes on the web page, manage the display of the image field as usual.'),
81 '#type' => 'textfield',
82 '#default_value' => $pdf_to_image_settings['density'],
83 '#element_validate' => array('pdf_to_image_validate_density'),
84 '#size' => 15,
85 '#maxlength' => 10,
86 );
87
88 $form['pdf_to_image']['extra_args'] = array(
89 '#title' => t('Extra conversion arguments'),
90 '#type' => 'textfield',
91 '#description' => t('Enter optional <a href="http://imagemagick.org/Usage/formats/#ps">additional parameters to be used by the imagemagick conversion</a> if needed.<br/>eg <code>-trim +repage</code>'),
92 '#default_value' => !empty($pdf_to_image_settings['extra_args']) ? $pdf_to_image_settings['extra_args'] : '',
93 '#size' => 20,
94 );
95
96 // @TODO: implement this.
97 $form['pdf_to_image']['extra_args']['#description'] .= '<br />' . t('WARNING! not working feature now.');
98
99 $form['pdf_to_image']['hide_imagefield'] = array(
100 '#type' => 'checkbox',
101 '#title' => t('Hide target image field on edit form'),
102 '#default_value' => !empty($pdf_to_image_settings['hide_imagefield']),
103 '#description' => t('If rendering the preview image in place of the uploaded file, you may want to hide the image field from the edit form entirely to prevent editors from adding their own.'),
104 );
105
106 // @TODO: add radiobuttons to choose way of pages generating,
107 // means batch (be default now) or queue or runtime,
108 // and implements this ways.
109 return $form;
110 }
111
112
113 /**
114 * Validate string for density settings.
115 */
116 function pdf_to_image_validate_density($element, &$form_state) {
117 $value = $element['#value'];
118 if (!empty($value) && !preg_match('/^[0-9]+x[0-9]+$/', $value)) {
119 form_set_error('density', t('Please specify a density in the format XxY (e.g. 100x100).'));
120 }
121 }
122
123
124
125 /**
126 * Adds another method for displaying PDF files - embedded live preview.
127 *
128 * Implements hook_field_formatter_info().
129 */
130 function pdf_to_image_field_formatter_info() {
131 $formatters = array(
132 'pdf_view' => array(
133 'label' => t('PDF preview'),
134 'field types' => array('file'),
135 'settings' => array(
136 'pdf_width' => '100%',
137 'pdf_height' => '450',
138 ),
139 ),
140 );
141 return $formatters;
142 }
143
144
145 /**
146 * Implements hook_field_formatter_settings_summary().
147 */
148 function pdf_to_image_field_formatter_settings_summary($field, $instance, $view_mode) {
149 $display = $instance['display'][$view_mode];
150 $settings = $display['settings'];
151 if ($display['type'] == 'pdf_view') {
152 $summary = t('Region of @width to @height, and text at download link - "@alt"', array(
153 '@width' => $settings['pdf_width'],
154 '@height' => $settings['pdf_height'],
155 '@alt' => $settings['pdf_alt'],
156 ));
157 return $summary;
158 }
159 }
160
161
162 /**
163 * Implements hook_field_formatter_view().
164 */
165 function pdf_to_image_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
166 if (!isset($items[0])) {
167 return;
168 }
169
170 // @TOFO fix public / private handlers, apply stream wrappers functions.
171 global $base_url;
172 $path = $base_url . '/' . variable_get('file_public_path') . substr($items[0]['uri'], 8);
173
174 if ($display['type'] == 'pdf_view') {
175 $width = $display['settings']['pdf_width'];
176 $height = $display['settings']['pdf_height'];
177 $alt = $display['settings']['pdf_alt'];
178 $object = '<object width="' . $width . '" height="' . $height . '" type="application/pdf" data="' . $path . '"><a href="' . $path . '">' . $alt . '</a></object>';
179 }
180
181 if ($display['type'] == 'pdf_link') {
182 // @TODO add title support.
183 $object = l(t('Download file'), $path);
184 }
185
186 $element = array();
187 $element[0]['#markup'] = $object;
188
189 return $element;
190 }
191
192
193 /**
194 * Implements hook_form_alter().
195 *
196 * Hides the target image field from editors, if you don't want them to provide
197 * their own snapshot.
198 *
199 * Should be able to run on any fielded entity type edit form, not just nodes.
200 */
201 function pdf_to_image_form_alter(&$form, &$form_state, $form_id) {
202 if (!isset($form['#entity_type']) || !isset($form['#bundle'])) {
203 return;
204 }
205
206 // Find if this entity has any fields that use the pdf_to_image widget.
207 $fields_pdf = pdf_to_image_source_fields($form['#entity_type'], $form['#bundle']);
208 // If so, do they want us to hide anything?
209 if (count($fields_pdf)) {
210 foreach ($fields_pdf as $field) {
211 if (!empty($field['widget']['settings']['pdf_to_image']['hide_imagefield'])) {
212 $target_field = $field['widget']['settings']['pdf_to_image']['target_field'];
213 if (isset($form[$target_field])) {
214 $form[$target_field]['#access'] = FALSE;
215 }
216 }
217 }
218 }
219 }
220
221 /**
222 * Returns a list of field names that are used as source files for pdf conversion.
223 *
224 * @param $entity_type eg 'node'
225 * @param $bundle eg 'document'
226 */
227 function pdf_to_image_source_fields($entity_type, $bundle) {
228 // Find if this entity has any fields that use the pdf_to_image widget.
229 $fields = field_info_instances($entity_type, $bundle);
230 $pdf_fields = array();
231 foreach ((array) $fields as $field) {
232 if ($field['widget']['type'] == 'pdf_to_image') {
233 $pdf_fields[$field['field_name']] = $field;
234 }
235 }
236 return $pdf_fields;
237 }
238
239 /**
240 * Adds config options to settings form at /admin/config/media/image-toolkit
241 *
242 * @param array $form
243 * @param array $form_state
244 */
245 function pdf_to_image_form_system_image_toolkit_settings_alter(&$form, &$form_state) {
246 // We have better diagnostics if imagemagick is used,
247 // but it's not a hard dependency.
248 // It's still handy to expose the path configuration for it though.
249 // Do this here if the imagemagick module isn't doing it.
250 if (! module_exists('imagemagick')) {
251 $form['imagemagick_convert'] = array(
252 '#type' => 'textfield',
253 '#title' => t('Path to the "convert" binary'),
254 '#default_value' => variable_get('imagemagick_convert', 'convert'),
255 '#required' => TRUE,
256 '#element_validate' => array('pdf_to_image_is_executable_validate'),
257 '#description' => t('The complete path and filename of the ImageMagick <kbd>convert</kbd> binary used by pdf_to_image. For example: <kbd>/usr/bin/convert</kbd> or <kbd>C:\Program Files\ImageMagick-6.3.4-Q16\convert.exe</kbd>'),
258 );
259 }
260 $form['gs_path'] = array(
261 '#type' => 'textfield',
262 '#title' => t('Path to the ghostscript "gs" binary'),
263 '#default_value' => variable_get('gs_path', '/usr/local/bin/gs'),
264 '#required' => FALSE,
265 '#element_validate' => array('pdf_to_image_is_executable_validate'),
266 '#description' => t('The complete path and filename of the Ghostscript <kbd>gs</kbd> binary used by pdf_to_image. For example: <kbd>/usr/local/bin/gs</kbd>. This is optional, but if available may be a little faster.'),
267 );
268 }
269
270
271 /**
272 * Adds extra options to file upload widget.
273 *
274 * It's basically a stub around file_field_widget_form().
275 *
276 * Implements hook_field_widget_form().
277 */
278 function pdf_to_image_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
279
280 // This is a file widget, plus extra options.
281 $elements = file_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);
282 $settings = $instance['settings'];
283
284 foreach (element_children($elements) as $delta) {
285 // If not using custom extension validation, ensure this is a pdf.
286 $elements[$delta]['#upload_validators']['file_validate_extensions'][0] = 'pdf';
287 // File fields need extra processing. Our one even more so.
288 #$elements[$delta]['#process'][] = 'pdf_to_image_field_widget_process';
289 }
290 $elements[0]['#description'] .= '<br/>' . t('This file will produce an image thumbnail and store it in %target_field when uploaded', array('%target_field' => $instance['widget']['settings']['pdf_to_image']['target_field']));
291
292 return $elements;
293 }
294
295
296 /**
297 * A field widget process callback, triggered when a form containing our widget type
298 * of file is geting rendered.
299 *
300 * @see file_field_widget_process.
301 */
302 function pdf_to_image_field_widget_process($element, &$form_state, $form) {
303 /*
304 $item = $element['#value'];
305 $item['fid'] = $element['fid']['#value'];
306
307 $field = field_widget_field($element, $form_state);
308 $instance = field_widget_instance($element, $form_state);
309 $settings = $instance['widget']['settings'];
310
311 dpm('could show a thumbnail here');
312 */
313 return $element;
314 }
315
316 /**
317 * hook_entity_insert()
318 *
319 * Update any of our fields when a fieldable entity is being updated.
320 *
321 * @param stdclass $entity
322 * @param string $entity_type
323 */
324 function pdf_to_image_entity_insert($entity, $entity_type) {
325 return pdf_to_image_entity_update($entity, $entity_type);
326 }
327
328 /**
329 * When a fieldable entity is being updated, regenerate the files if appropriate
330 *
331 * This is the first step where generation really starts happening.
332 *
333 * @param stdclass $entity
334 * @param string $type
335 */
336 function pdf_to_image_entity_update($entity, $entity_type) {
337 // What is this thing?
338 $info = entity_get_info($entity_type);
339 list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
340
341 $pdf_fields = pdf_to_image_source_fields($entity_type, $bundle);
342 foreach ($pdf_fields as $field_id => $field_instance) {
343 pdf_to_image_generate_process($entity_type, $entity, $field_id, $field_instance);
344 }
345 }
346
347 /**
348 * Processing pdf file creation.
349 *
350 * This sets up the batch job with all the neccessary parameters
351 */
352 function pdf_to_image_generate_process($entity_type, $entity, $field_id, $field_instance) {
353 $field_lang = field_language($entity_type, $entity, $field_id);
354 list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
355 $items = $entity->{$field_id};
356
357 if (empty($items[$field_lang][0]['fid'])) {
358 // No file attachment found in the source field.
359 // Do nothing.
360 return;
361 }
362 $pdf_file = file_load($items[$field_lang][0]['fid']);
363 $pdf_realpath = file_stream_wrapper_get_instance_by_uri($pdf_file->uri)->realpath();
364 $count = pdf_to_image_count_pages($pdf_realpath);
365 if (!empty($field_instance['widget']['settings']['pdf_to_image']['target_field'])) {
366 $target_field = $field_instance['widget']['settings']['pdf_to_image']['target_field'];
367 }
368 // Should I check if the target field is already populated and stop then?
369
370 if ($target_field && $count) {
371 // Arguments to give to the batch job.
372 $params = array(
373 'entity' => $entity, // Don't actually need the whole thing, just the id really.
374 'entity_type' => $entity_type,
375 'entity_id' => $id,
376 'image' => array(
377 'field' => field_info_field($target_field),
378 'instance' => field_info_instance($entity_type, $target_field, $field_instance['bundle']),
379 ),
380 'pdf' => array(
381 'instance' => $field_instance,
382 'file' => $pdf_file,
383 ),
384 );
385
386 // Prepare count parameter.
387 if ($params['image']['field']['cardinality'] != -1 && $count > $params['image']['field']['cardinality']) {
388 $count = $params['image']['field']['cardinality'];
389 }
390 $operations = array(
391 array('pdf_to_image_generate_process_page', array($params, 0)),
392 );
393
394 watchdog('pdf_to_image', 'Starting a process to convert attached PDF %file to image previews', array('%file' => $params['pdf']['file']->uri), WATCHDOG_INFO);
395 for ($page = 1; $page < $count; $page++) {
396 $operations[] = array('pdf_to_image_generate_process_page', array($params, $page));
397 }
398 batch_set(array(
399 'title' => t('Converting PDF, %count pages', array('%count' => $count)),
400 'operations' => $operations,
401 'finished' => 'pdf_to_image_generate_process_attach',
402 'progress_message' => t('Processed @current out of @total.'),
403 ));
404 // @TODO: save node with one entity without batch.
405 // else {
406 // $file = pdf_to_image_generate_page($params, 0);
407 // }
408 }
409 else {
410 // No target image field set, or invalid count from the PDF.
411 }
412 }
413
414
415 /**
416 * Generate a single page (of the given index) inside a batch process.
417 *
418 * A batch task, no return.
419 */
420 function pdf_to_image_generate_process_page($params, $page_number, &$context) {
421 $context['results']['params'] = $params;
422 if (!isset($context['results']['files'])) {
423 $context['results']['files'] = array();
424 }
425 $file = pdf_to_image_generate_page($params, $page_number);
426 if (is_object($file) && isset($file->fid)) {
427 $context['results']['files'][$page_number] = $file;
428 }
429 }
430
431
432 /**
433 * Generate a single page for the given pdf file.
434 */
435 function pdf_to_image_generate_page($params, $page_number = 0) {
436 $source_file = drupal_realpath($params['pdf']['file']->uri);
437 if (!file_exists($source_file)) {
438 watchdog('pdf_to_image', 'Invalid file given to convert. Could not read %file (%source_file)', array('%file' => $params['pdf']['file']->uri, '%source_file' => $source_file), WATCHDOG_ERROR);
439 return NULL;
440 }
441
442 $density = "-density " . $params['pdf']['instance']['widget']['settings']['pdf_to_image']['density'];
443 $extra_args = isset($params['pdf']['instance']['widget']['settings']['pdf_to_image']['extra_args']) ? $params['pdf']['instance']['widget']['settings']['pdf_to_image']['extra_args'] : "";
444
445 // We need to know both the uri and realpath versions of the paths we want to
446 // work with.
447 $image_dir_uri = file_stream_wrapper_uri_normalize($params['image']['field']['settings']['uri_scheme'] . '://' . $params['image']['instance']['settings']['file_directory']);
448 file_prepare_directory($image_dir_uri, FILE_CREATE_DIRECTORY);
449 $image_uri = $image_dir_uri . '/' . $params['pdf']['file']->fid . "-" . $page_number . '.jpg';
450 $image_realpath = drupal_realpath($image_uri);
451
452 if (empty($image_uri)) {
453 watchdog('pdf_to_image', 'Failed to calculate a destination filename for conversion', array(), WATCHDOG_ERROR);
454 return FALSE;
455 }
456
457 // Check to see if the target image file already exists, is registered in the
458 // database. Why?
459 $query = db_select('file_managed', 'f')
460 ->fields('f', array('fid'))
461 ->condition('uri', $image_uri)
462 ->execute()->fetchCol();
463 if (!empty($query)) {
464 $file = file_load(array_shift($query));
465 watchdog('pdf_to_image', 'PDF preview %image already exists. Re-attaching it.', array('%image' => $image_uri), WATCHDOG_INFO);
466 return $file;
467 }
468
469 watchdog('pdf_to_image', 'Converting PDF: %file page %page_number: to image: %image', array(
470 '%file' => $params['pdf']['file']->uri,
471 '%page_number' => $page_number,
472 '%image' => $image_uri,
473 ), WATCHDOG_INFO
474 );
475
476 pdf_to_image_convert_exec($source_file . '[' . $page_number . ']', $image_realpath, array(), array($density, $extra_args));
477
478 if (file_exists($image_realpath)) {
479 watchdog('pdf_to_image', 'PDF preview %image created', array(
480 '%image' => $image_uri,
481 ), WATCHDOG_INFO
482 );
483
484 global $user;
485 $file = (object) array(
486 'uid' => $user->uid,
487 'filename' => basename($image_uri),
488 'uri' => $image_uri,
489 'filemime' => file_get_mimetype($image_uri),
490 'filesize' => @filesize($image_uri),
491 'timestamp' => REQUEST_TIME,
492 'status' => FALSE,
493 'is_new' => TRUE,
494 );
495 file_save($file);
496 return $file;
497 }
498 watchdog('pdf_to_image', 'Failed to generate image', array(), WATCHDOG_ERROR);
499 return FALSE;
500 }
501
502
503 /**
504 * Attach generated files to the content entity (node) at the end of batch mode.
505 */
506 function pdf_to_image_generate_process_attach($success, $results, $operations) {
507 if (!(isset($results['files']) && count($results['files']))) {
508 watchdog('pdf_to_image', 'No files produced from processing document', array(), WATCHDOG_NOTICE);
509 return;
510 }
511
512 $field_name = $results['params']['image']['field']['field_name'];
513 $entity_id = $results['params']['entity_id'];
514 $entity_type = $results['params']['entity_type'];
515
516 if (isset($entity_id) && is_numeric($entity_id)) {
517 // Don't use the entity as given, load it again as things may have happened
518 // to it since the batch job began.
519 $entity = entity_load_unchanged($entity_type, $entity_id);
520 if (is_object($entity)) {
521 $field_lang = field_language($entity_type, $entity, $field_name);
522 // This removes the existing images by emptying the list.
523 // The (re?) attaches the generated ones.
524 $entity->{$field_name}[$field_lang] = array();
525 ksort($results['files'], SORT_NUMERIC);
526 foreach ($results['files'] as $file) {
527 #$entity->{$field_name}[$field_lang][]['fid'] = $file->fid;
528 $entity->{$field_name}[$field_lang][] = (array) $file;
529 }
530 watchdog('pdf_to_image', 'Attached converted images to content.', array(), WATCHDOG_INFO);
531 $saved = pdf_to_image_entity_save($entity_type, $entity);
532 }
533 else {
534 watchdog('pdf_to_image', 'Invalid content object. Cannot attach generated images to it.', array(), WATCHDOG_ERROR);
535 }
536 }
537 else {
538 watchdog('pdf_to_image', 'Invalid content id given to attach generated images to.', array(), WATCHDOG_ERROR);
539 }
540 }
541
542 /**
543 * Why is there no entity_save in core??
544 * Stolen this from contrib entity.module!
545 *
546 * Needed this to allow any entity, not just node to use these fields.
547 */
548 function pdf_to_image_entity_save($entity_type, $entity) {
549 $info = entity_get_info($entity_type);
550 if (method_exists($entity, 'save')) {
551 return $entity->save();
552 }
553 elseif (isset($info['save callback'])) {
554 $info['save callback']($entity);
555 }
556 elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) {
557 return entity_get_controller($entity_type)->save($entity);
558 }
559 elseif (function_exists("{$entity_type}_save")) {
560 $func = "{$entity_type}_save";
561 return $func($entity);
562 }
563 else {
564 return FALSE;
565 }
566 }
567
568
569 /**
570 * Use imagemagick routine to count the number of pages in a given PDF
571 */
572 function pdf_to_image_count_pages($filepath) {
573 // Assume the 'identify' binary lives next to the 'convert' binary.
574 $convert_path = variable_get('imagemagick_convert', '/usr/bin/convert');
575 $identify_path = dirname($convert_path) . '/identify';
576
577 // Identify renders every page in the pdf to count the number of pages which
578 // can be a problem (server timeout) when processing a pdf with many pages.
579 // The better command commented because it working very slow.
580 // "{$identify_path} -format %n " . escapeshellarg($fpath) . ' 2> /dev/null';
581
582 // Apparently this method is even faster, from
583 // http://drupal.org/node/1537658
584 // Though we'll only use it if ghostscript is present and configured.
585 $gs_path = variable_get('gs_path', '/usr/local/bin/gs');
586 if (is_executable($gs_path)) {
587 $command = "{$gs_path} -q -dNODISPLAY -c \"(". trim(escapeshellcmd($filepath),"'") .") (r) file runpdfbegin pdfpagecount = quit\"";
588 }
589 else {
590 // This one instead asks for more pages than the document has, then reads the
591 // error message that tells us what the last page number was instead :-}
592 $command = "{$identify_path} " . escapeshellarg($filepath) . '[9999] | grep "Requested FirstPage" | cut -d : -f2';
593 }
594
595 $count = pdf_to_image_shell_exec($command);
596 return (int) trim($count);
597 }
598
599 /**
600 * Check the binary path of 'convert' on the server.
601 *
602 * Returns NULL if it failed, the version info of ImageMagick if it succeeds.
603 */
604 function pdf_to_image_check_imagemagick() {
605 static $response;
606 if (isset($response)) {
607 return $response;
608 }
609 $convert_path = variable_get('imagemagick_convert', '/usr/bin/convert');
610 $response = pdf_to_image_shell_exec($convert_path . ' -version');
611 return $response;
612 }
613
614
615 /**
616 * Verifies that the given path to a binary exists and is executable.
617 */
618 function pdf_to_image_is_executable_validate($element, &$form_state, $form) {
619 $path = $element['#value'];
620 if (!empty($path) && ! is_executable($path)) {
621 form_set_error($element['#name'], t('%title : %path was not found, valid or executable. Check to see if this exists on your system in that location', array('%title' => $element['#title'], '%path' => $element['#value'])));
622 }
623 }
624
625 /**
626 * FWIW, may not need imagemagick module, just for this one func.
627 * If you want debugging and Win32 support, use imagemagick.module.
628 * Otherwise, here's a short and dirty version of the same thing.
629 *
630 */
631 function pdf_to_image_convert_exec($source, $dest, $args = array(), $extra = array()) {
632 $args['quality'] = '-quality ' . escapeshellarg(variable_get('imagemagick_quality', 75));
633 $command = implode(' ', $extra) . ' ' . escapeshellarg($source) . ' ' . implode(' ', $args) . ' ' . escapeshellarg($dest);
634
635 if (function_exists('imagemagick_convert_exec')) {
636 if (_imagemagick_convert_exec($command, $output, $errors) !== TRUE) {
637 $errors_txt = '<pre>' . (is_array($errors) ? implode("\n", $errors) : $errors) . '</pre>';
638 watchdog('pdf to image : imageapi imagemagick', $errors_txt, array(), WATCHDOG_ERROR);
639 return FALSE;
640 }
641 return file_exists($dest);
642 }
643
644 // Else do it myself.
645 // Paranoia.
646 if (! pdf_to_image_check_imagemagick()) {
647 drupal_set_message(t('Imagemagick must be installed and the <a href="!admin_path">path on the server set</a> for PDFs to be processed.', array('!admin_path' => url('admin/config/media/image-toolkit'))), 'error');
648 return FALSE;
649 }
650
651 $convert_path = variable_get('imagemagick_convert', '/usr/bin/convert');
652 $response = pdf_to_image_shell_exec($convert_path . ' ' . $command);
653 return file_exists($dest);
654 }
655
656 /**
657 * Run the given command (expected to be an imageMagick Commandline)
658 * Wrapped in a bugfix workaround.
659 *
660 * @param string $command
661 */
662 function pdf_to_image_shell_exec($command) {
663 // Horrible bug. If running on Acquia dev desktop, it sets a custom path for
664 // the dynamic link library. But 2012-04 that was OLDER than the libraries
665 // My OS expected to use to run graphics utilities with.
666 // Reason: Incompatible library version: dot requires version 10.0.0 or later, but libltdl.7.dylib provides version 9.0.0
667 // UNSET the Acquia DYLD_LIBRARY_PATH before dropping to commandline to run.
668 $DYLD_LIBRARY_PATH = getenv("DYLD_LIBRARY_PATH");
669 putenv("DYLD_LIBRARY_PATH=");
670
671 watchdog('pdf_to_image', 'Running commandline %command', array('%command' => $command), WATCHDOG_DEBUG);
672
673 $response = shell_exec($command);
674
675 // Put it back just in case it matters
676 putenv("DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH");
677
678 return $response;
679 }
680
681
682 /**
683 * To enhance filefield_paths, publish the filename of the source document that could be used to create the filepath of the derived document.
684 *
685 * Implements hook_token_info().
686 */
687 function pdf_to_image_token_info() {
688 $info['tokens']['node']['pdf-source-filename'] = array(
689 'name' => t("File name"),
690 'description' => t("File name of the source PDF without extension."),
691 );
692
693 /*
694 // Provide tokens for any fields that are used as pdf sources.
695 // Do a complex lookup here to avoid having to do it in the token call itself.
696 $all_fields = field_info_instances();
697 // All entities, all bundles.
698 foreach ($all_fields as $entity_type => $entity_fields) {
699 foreach ($entity_fields as $bundle_id => $bundle_fields) {
700 foreach (pdf_to_image_source_fields($entity_type, $bundle_id) as $field_id => $field_instance) {
701 // THIS is a field I need to provide a token from
702 // TODO - I really should index by field ID so the token generator
703 // knows what to look for without analyzing the content type each time.
704 // $info['tokens'][$entity_type][$field_id . ':pdf-filename'] = array(
705 // But how...
706 $info['tokens'][$entity_type]['pdf-source-filename'] = array(
707 'name' => $field['label'],
708 'description' => t("File name of the source PDF without extension."),
709 );
710 }
711
712 }
713 }
714 // Fail. If I encode the field name into the token, then I can't scan for it
715 // later until I know what the field name is. So no win there.
716 */
717 return $info;
718 }
719
720 /**
721 * Fills in a token with the filename of the source pdf field that may be
722 * attached to an entity. Not very well-structured here yet.
723 *
724 * Implements hook_tokens().
725 */
726 function pdf_to_image_tokens($type, $tokens, array $data = array(), array $options = array()) {
727 $sanitize = !empty($options['sanitize']);
728
729 $replacements = array();
730 if ($type == 'entity' && !empty($data['entity'])) {
731 $entity = $data['entity'];
732 $entity_type = $data['entity_type'];
733 list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
734
735 // Find any fields on this entity that may be source files for a PDF conversion.
736 $pdf_fields = pdf_to_image_source_fields($entity_type, $bundle);
737 foreach ($pdf_fields as $field_id => $field) {
738 if (isset($entity->{$field_id})) {
739 $source_field_values = $entity->{$field_id};
740 $field_lang = field_language($data['entity_type'], $entity, $field_id);
741 $source_values = $source_field_values[$field_lang];
742 // Assumed to be multiple
743 foreach ($source_values as $delta => $file_info) {
744 // fall-through. We've found $file_info now. Messy, but dunno what to do with multiple sources.
745 }
746 }
747 }
748
749 // This looks like a slow way to do things, no?
750 foreach ($tokens as $name => $original) {
751 switch ($name) {
752 case 'pdf-source-filename':
753 $info = pathinfo($file_info['filename']);
754 $replacements[$original] = $info['filename'];
755 break;
756 }
757 }
758 }
759
760 return $replacements;
761 }
762
763 /**
764 * Implements hook_filefield_sources_widgets().
765 *
766 * Adds PDF to Image to the list of widgets compatible with FileField Sources.
767 */
768 function pdf_to_image_filefield_sources_widgets() {
769 return array('pdf_to_image');
770 }