Mercurial > hg > rr-repo
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 } |