/[drupal]/contributions/modules/image_resize_filter/image_resize_filter.module
ViewVC logotype

Contents of /contributions/modules/image_resize_filter/image_resize_filter.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.32 - (show annotations) (download) (as text)
Thu Oct 22 02:25:37 2009 UTC (4 weeks, 6 days ago) by quicksketch
Branch: MAIN
CVS Tags: DRUPAL-6--1-5
Changes since 1.31: +6 -6 lines
File MIME type: text/x-php
#532984 by Anclywen: Making matches case-insensitive.
1 <?php
2 // $Id: image_resize_filter.module,v 1.31 2009/10/22 02:09:22 quicksketch Exp $
3
4 /**
5 * @file image_resize_filter.module
6 *
7 * After adding to an input format, this filter will parse the contents of
8 * submitted content and automatically scale image files to match the set
9 * dimensions of img tags.
10 *
11 * Image that have been created take on the ownership of the original file.
12 * Making so when the primary node is deleted, the images it provided are
13 * deleted also.
14 *
15 */
16
17 /**
18 * Implementation of hook_filter().
19 */
20 function image_resize_filter_filter($op, $delta = 0, $format = -1, $text = '', $cache_id = 0) {
21
22 switch ($op) {
23 case 'list':
24 return array(0 => t('Image resize filter'));
25
26 case 'description':
27 return t('Resizes images to the exact dimensions specified in the &lt;img&gt; tag.');
28
29 case 'settings':
30 return image_resize_filter_form($format);
31
32 case 'process':
33 $settings['link'] = variable_get('image_resize_filter_link_'. $format, 0);
34 $settings['class'] = variable_get('image_resize_filter_link_class_'. $format, '');
35 $settings['rel'] = variable_get('image_resize_filter_link_rel_'. $format, '');
36 $settings['image_locations'] = variable_get('image_resize_filter_image_locations_'. $format, array('local'));
37 $images = image_resize_filter_get_images($settings, $text);
38 return image_resize_filter_process($images, $text, $settings);
39
40 default:
41 return $text;
42 }
43 }
44
45 /**
46 * Implementation of hook_nodeapi().
47 */
48 function image_resize_filter_nodeapi(&$node, $op, $teaser, $page) {
49 if (($op == 'presave' || $op == 'delete') && isset($node->files)) {
50 // Delete upload.module derivatives.
51 foreach ($node->files as $fid => $file) {
52 // File is an an object on delete, but array on presave.
53 $file = (array)$file;
54 if ($file['remove'] || $op == 'delete') {
55 image_resize_filter_delete_derivatives($file['filepath']);
56 }
57 }
58 }
59 }
60
61 /**
62 * Implementation of hook_file_delete().
63 */
64 function image_resize_filter_file_delete($file) {
65 if (isset($file->filepath)) {
66 image_resize_filter_delete_derivatives($file->filepath);
67 }
68 }
69
70 /**
71 * Implementation of hook_theme().
72 */
73 function image_resize_filter_theme() {
74 return array(
75 'image_resize_filter_form' => array(
76 'arguments' => array('form' => NULL),
77 ),
78 );
79 }
80
81 /**
82 * Implementation of hook_file_download().
83 */
84 function image_resize_filter_file_download($filepath) {
85 // If this is a resized image, use the same access as the original image.
86 $matches = array();
87 if (preg_match('/^resize\/(.*)?-\d+x\d+(\.png|jpg|jpeg|gif)$/i', $filepath, $matches)) {
88 $headers = module_invoke_all('file_download', $matches[1] . $matches[2]);
89 if (in_array(-1, $headers)) {
90 return -1;
91 }
92 if (count($headers)) {
93 return array(
94 'Content-Type: ' . file_get_mimetype($filepath),
95 'Content-Length: ' . filesize(file_create_path($filepath)),
96 );
97 }
98 }
99 }
100
101 /**
102 * Implementation of hook_form_[form_id]_alter().
103 */
104 function image_resize_filter_form_system_file_system_settings_alter($form, $form_state) {
105 $form['#submit'][] = 'image_resize_filter_file_system_settings_submit';
106 }
107
108 /**
109 * Additional #submit handler for the system_file_system_settings form.
110 */
111 function image_resize_filter_file_system_settings_submit($form, $form_state) {
112 // Clear filter caches when changing file system information.
113 cache_clear_all('*', 'cache_filter');
114 }
115
116 /**
117 * The form for configuring the Image Resize Filter.
118 */
119 function image_resize_filter_form($format) {
120 $form = array();
121
122 $form['image_resize'] = array(
123 '#type' => 'fieldset',
124 '#title' => t('Image resize settings'),
125 '#collapsible' => TRUE,
126 '#description' => t('The image resize filter will analyze &lt;img&gt; tags and compare the given height and width attributes to the actual file. If the file dimensions are different than those given in the &lt;img&gt; tag, the image will be copied and the src attribute will be updated to point to the resized image.'),
127 '#theme' => 'image_resize_filter_form',
128 '#format' => $format,
129 );
130
131 $form['image_resize']['image_resize_filter_image_locations_'. $format] = array(
132 '#type' => 'checkboxes',
133 '#title' => t('Resize images stored'),
134 '#options' => array('local' => t('Locally'), 'remote' => t('On remote servers')),
135 '#default_value' => variable_get('image_resize_filter_image_locations_'. $format, array('local')),
136 '#required' => TRUE,
137 '#description' => t('This option will determine which images will be analyzed for &lt;img&gt; tag differences. Enabling resizing of remote images can have performance impacts, as all images in the filtered text needs to be transfered via HTTP each time the filter cache is cleared.'),
138 );
139
140 $form['image_resize']['image_resize_filter_link_'. $format] = array(
141 '#type' => 'checkbox',
142 '#title' => t('If resized, add a link to the original image.'),
143 '#default_value' => variable_get('image_resize_filter_link_'. $format, 0),
144 );
145
146 $form['image_resize']['image_resize_filter_link_class_'. $format] = array(
147 '#type' => 'textfield',
148 '#title' => t('Optionally, give it the class'),
149 '#size' => '10',
150 '#default_value' => variable_get('image_resize_filter_link_class_'. $format, ''),
151 );
152
153 $form['image_resize']['image_resize_filter_link_rel_'. $format] = array(
154 '#type' => 'textfield',
155 '#title' => t('and/or a rel attribute'),
156 '#size' => '10',
157 '#default_value' => variable_get('image_resize_filter_link_rel_'. $format, ''),
158 );
159
160 return $form;
161 }
162
163 /**
164 * Theme callback to theme the Image Resize Filter form.
165 */
166 function theme_image_resize_filter_form($form) {
167 $format = $form['#format'];
168
169 $link = 'image_resize_filter_link_'. $format;
170 $class = 'image_resize_filter_link_class_'. $format;
171 $rel = 'image_resize_filter_link_rel_'. $format;
172
173 $class_element = ' ';
174 $class_element .= '<span class="image-resize-filter-class">';
175 $class_element .= check_plain($form[$class]['#title']) .': ';
176 $form[$class]['#title'] = NULL;
177 $class_element .= drupal_render($form[$class]);
178 $class_element .= '</span>';
179
180 $rel_element = ' ';
181 $rel_element .= '<span class="image-resize-filter-rel">';
182 $rel_element .= check_plain($form[$rel]['#title']) .': ';
183 $form[$rel]['#title'] = NULL;
184 $rel_element .= drupal_render($form[$rel]);
185 $rel_element .= '</span>';
186
187 $link_element = drupal_render($form[$link]);
188
189 $output = '';
190 $output .= '<div class="container-inline image-resize-filter-link-options">';
191 $output .= $link_element;
192 $output .= $class_element;
193 $output .= $rel_element;
194 $output .= '</div>';
195
196 $placeholder = array(
197 '#type' => 'element',
198 '#title' => t('Link to the original'),
199 '#children' => $output,
200 '#description' => t('Linking to the original can be helpful to give users a full-size view of the image. Adding the class "thickbox" is helpful if you have installed the <a href="http://drupal.org/project/thickbox">thickbox module</a>. The rel attribute may be useful when used with the <a href="http://drupal.org/project/lightbox2">lightbox2</a> and <a href="http://drupal.org/project/shadowbox">shadowbox</a> modules.'),
201 );
202
203 // Add a little bit of JavaScript. Not cached since it's only used here.
204 drupal_add_js(drupal_get_path('module', 'image_resize_filter') .'/image_resize_filter.js', 'module', 'header', FALSE, FALSE);
205
206 return drupal_render($form) . theme('form_element', $placeholder, $output);
207 }
208
209 /**
210 * Parsing function to locate all images in a piece of text that need replacing.
211 *
212 * @param $settings
213 * An array of settings that will be used to identify which images need
214 * updating. Includes the following:
215 *
216 * - image_locations: An array of acceptable image locations. May contain any
217 * of the following values: "remote". Remote image will be downloaded and
218 * saved locally. This procedure is intensive as the images need to
219 * be retrieved to have their dimensions checked.
220 *
221 * @param $text
222 * The text to be updated with the new img src tags.
223 */
224 function image_resize_filter_get_images($settings, $text) {
225 $images = array();
226
227 // Find all image tags, ensuring that they have a src.
228 $matches = array();
229 preg_match_all('/((<a [^>]*>)[ ]*)?(<img[^>]*?src[ ]*=[ ]*"([^"]+)"[^>]*>)/i', $text, $matches);
230
231 // Loop through matches and find if replacements are necessary.
232 // $matches[0]: All complete image tags and preceeding anchors.
233 // $matches[1]: The anchor tag of each match (if any).
234 // $matches[2]: The anchor tag and trailing whitespace of each match (if any).
235 // $matches[3]: The complete img tag.
236 // $matches[4]: The src value of each match.
237 foreach ($matches[0] as $key => $match) {
238 $has_link = (bool) $matches[1][$key];
239 $img_tag = $matches[3][$key];
240 $src = $matches[4][$key];
241
242 $width = NULL;
243 $height = NULL;
244 $needs_height = FALSE;
245 $needs_width = FALSE;
246
247 // Because we don't know the order of the attributes and images might not
248 // have both attributes, match individually for height and width.
249 foreach (array('width', 'height') as $property) {
250 $property_matches = array();
251 preg_match_all('/'. $property .'[ ]*([=:])[ ]*"?([0-9]+)"?/i', $img_tag, $property_matches);
252 // In the odd scenario there is both a style="width: xx" and a width="xx"
253 // tag, base our calculations off the style tag, since that's what the
254 // browser will display.
255 $property_key = 0;
256 $needs_property = FALSE;
257 if (count($property_matches[1]) > 1) {
258 $property_key = array_search(':', $property_matches[1]);
259 }
260 // Only a style property found, we'll need to add a real height/width tag
261 // to the HTML later. This specifically prevents problems with FCKeditor
262 // that only adds style tags when resizing images.
263 elseif (!empty($property_matches[1]) && $property_matches[1][$property_key] == ':'){
264 $needs_property = TRUE;
265 }
266 ${$property} = !empty($property_matches[2][$property_key]) ? $property_matches[2][$property_key] : FALSE;
267 ${'needs_'. $property} = $needs_property;
268 }
269
270 // If height and width are both missing, nothing to do here.
271 if (!$width && !$height) {
272 continue;
273 }
274
275 // Find the image title if any.
276 $title = NULL;
277 $title_matches = array();
278 preg_match('/title[ ]*=[ ]*"([^"]+)"/i', $img_tag, $title_matches);
279 if (isset($title_matches[1])) {
280 $title = $title_matches[1];
281 }
282
283 // Check the image extension.
284 $extension_matches = array();
285 preg_match('/.([a-zA-Z0-9]+)$/', $src, $extension_matches);
286 if (empty($extension_matches) || !in_array(drupal_strtolower($extension_matches[1]), array('png', 'jpg', 'jpeg', 'gif'))) {
287 continue;
288 }
289 $extension = strtolower($extension_matches[1]);
290
291 // Determine if this is a local or remote file.
292 $location = 'unknown';
293 if (strpos($src, '/') === 0 || strpos($src, '.') === 0) {
294 $location = 'local';
295 }
296 elseif (preg_match('/http[s]?:\/\/'. preg_quote($_SERVER['HTTP_HOST'], '/') .'/', $src)) {
297 $location = 'local';
298 }
299 elseif (strpos($src, 'http') === 0) {
300 $location = 'remote';
301 }
302
303 // If not resizing images in this location, continue on to the next image.
304 if (!in_array($location, $settings['image_locations'])) {
305 continue;
306 }
307
308 // Convert the URL to a local path.
309 $local_path = NULL;
310 if ($location == 'local') {
311 if (strpos($src, '.') !== 0) {
312 // Remove the http:// and base path.
313 $local_path = preg_replace('/(http[s]?:\/\/'. preg_quote($_SERVER['HTTP_HOST'], '/') .')?'. preg_quote(base_path(), '/') .'/', '', $src, 1);
314 // Convert to a file system path if using private files.
315 if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE && preg_match('/^(\?q\=)?system\/files\//', $local_path)) {
316 $local_path = file_directory_path() . '/' . preg_replace('/^(\?q\=)?system\/files\//', '', $local_path);
317 }
318 }
319 $local_path = urldecode($local_path);
320 }
321
322 // If this is an ImageCache preset, generate the source image if necessary.
323 if (!file_exists($local_path) && strpos($local_path, 'imagecache') !== FALSE && function_exists('imagecache_build_derivative')) {
324 $imagecache_path = preg_replace('/^'. preg_quote(file_directory_path(), '/') .'\/imagecache\//', '', $local_path);
325 $imagecache_args = explode('/', $imagecache_path);
326 $preset_name = array_shift($imagecache_args);
327 $original_path = implode('/', $imagecache_args);
328 if ($preset = imagecache_preset_by_name($preset_name)) {
329 imagecache_build_derivative($preset['actions'], $original_path, imagecache_create_path($preset_name, $original_path));
330 }
331 }
332
333 // Get the image size.
334 if ($location == 'local') {
335 $image_size = @getimagesize($local_path);
336 }
337 else {
338 $image_size = @getimagesize($src);
339 }
340
341 // All this work and the image isn't even there. Bummer. Next image please.
342 if ($image_size == FALSE) {
343 continue;
344 }
345
346 $actual_width = $image_size[0];
347 $actual_height = $image_size[1];
348
349 // If either height or width is missing, calculate the other.
350 if (!$height) {
351 $ratio = $actual_height/$actual_width;
352 $height = round($ratio * $width);
353 }
354 elseif (!$width) {
355 $ratio = $actual_width/$actual_height;
356 $width = round($ratio * $height);
357 }
358
359 // Finally, if width and heights match up, don't do anything.
360 if ($actual_width == $width && $actual_height == $height) {
361 continue;
362 }
363
364 // If getting this far, the image exists and is not the right size.
365 // Add all information to a list of images that need resizing.
366 $images[] = array(
367 'expected_size' => array('width' => $width, 'height' => $height),
368 'actual_size' => array('width' => $image_size[0], 'height' => $image_size[1]),
369 'add_properties' => array('width' => $needs_width, 'height' => $needs_height),
370 'title' => $title,
371 'has_link' => $has_link,
372 'original' => $src,
373 'location' => $location,
374 'local_path' => $local_path,
375 'mime' => $image_size['mime'],
376 'extension' => $extension,
377 );
378 }
379
380 return $images;
381 }
382
383
384 /**
385 * Processing function for image resize filter. Replace img src properties
386 * with a URL to a resized image.
387 *
388 * @param $images
389 * An array of image information, detailing images that need to be replaced.
390 * @param $text
391 * The original text of the post that needs src tags updated.
392 * @param $settings
393 * An array of setting for generating the image tag.
394 */
395 function image_resize_filter_process($images, $text, $settings) {
396 $file_directory_path = file_directory_path();
397 $local_file_path = '';
398 foreach ($images as $image) {
399 // Copy remote images locally.
400 if ($image['location'] == 'remote') {
401 $result = drupal_http_request($image['original']);
402 if ($result->code == 200) {
403 $tmp_file = tempnam(file_directory_temp(), 'image_resize_filter_');
404 $path_info = image_resize_filter_pathinfo($image['original']);
405
406 $handle = fopen($tmp_file, 'w');
407 fwrite($handle, $result->data);
408 fclose($handle);
409 $local_file_path = 'resize/remote/'. md5($result->data) .'-'. $image['expected_size']['width'] .'x'. $image['expected_size']['height'] .'.'. $path_info['extension'];
410 $image['local_path'] = $tmp_file;
411 $image['destination'] = $file_directory_path .'/'. $local_file_path;
412 }
413 else {
414 continue;
415 }
416 }
417 else {
418 $path_info = image_resize_filter_pathinfo($image['local_path']);
419 $local_file_dir = preg_replace('/^'. preg_quote($file_directory_path, '/') .'\/?/', '', $path_info['dirname']);
420 $local_file_dir = !empty($local_file_dir) ? $local_file_dir . '/' : '';
421 $local_file_path = 'resize/'. $local_file_dir . $path_info['filename'] .'-'. $image['expected_size']['width'] .'x'. $image['expected_size']['height'] .'.'. $path_info['extension'];
422 $image['destination'] = $file_directory_path .'/'. $local_file_path;
423 }
424
425 // Ensure that the destination directories exist.
426 $folders = explode('/', dirname($local_file_path));
427 $current_directory = $file_directory_path .'/';
428 foreach ($folders as $folder) {
429 $current_directory .= $folder .'/';
430 $check_directory = $current_directory;
431 // Use the "quiet" version of file_check_directory() provided by FileField
432 // if it exists. This suppresses "Directory was created" messages.
433 $file_check_directory = function_exists('field_file_check_directory') ? 'field_file_check_directory' : 'file_check_directory';
434 $file_check_directory($check_directory, FILE_CREATE_DIRECTORY);
435 }
436
437 // Resize the local image.
438 if (!file_exists($image['destination'])) {
439 if (module_exists('imageapi') && imageapi_default_toolkit()) {
440 $res = imageapi_image_open($image['local_path']);
441 imageapi_image_resize($res, $image['expected_size']['width'], $image['expected_size']['height']);
442 imageapi_image_close($res, $image['destination']);
443 }
444 else {
445 image_resize($image['local_path'], $image['destination'], $image['expected_size']['width'], $image['expected_size']['height']);
446 }
447 @chmod($image['destination'], 0664);
448 }
449
450 // Replace the existing image source with the resized image.
451 // Set the image we're currently updating in the callback function.
452 image_resize_filter_update_tag(NULL, $image, $settings);
453 $text = preg_replace_callback('/(<img[^>]*?src[ ]*=[ ]*")'. preg_quote($image['original'] ,'/') .'("[^>]*?)(\/?>)/i', 'image_resize_filter_update_tag', $text, 1);
454 }
455 return $text;
456 }
457
458 /**
459 * Regular expression callback.
460 *
461 * @param $matches
462 * The matches for a call to preg_replace_callback().
463 * @param $new_image
464 * If passed in, this will set a static variable so that this image data is
465 * available when this function is called from a regular expression.
466 */
467 function image_resize_filter_update_tag($matches = NULL, $new_image = NULL, $new_settings = NULL) {
468 global $base_url;
469 static $image, $settings;
470
471 $image = isset($new_image) ? $new_image : $image;
472 $settings = isset($new_settings) ? $new_settings : $settings;
473
474 if (!isset($matches)) {
475 return;
476 }
477
478 $src = $image['destination'];
479 // Make the new file private if the original was private.
480 if (strpos($image['original'], 'system/files/') !== FALSE && variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
481 $src = file_create_url($src);
482 }
483 else {
484 $src = $base_url . '/' . $src;
485 }
486 // Strip the http:// from the path if the original did not include it.
487 if (!preg_match('/^http[s]?:\/\/'. preg_quote($_SERVER['HTTP_HOST']) .'/', $image['original'])) {
488 $src = preg_replace('/^http[s]?:\/\/'. preg_quote($_SERVER['HTTP_HOST']) .'/', '', $src);
489 }
490
491 $output = '';
492 $output .= $matches[1]; // The start of the tag.
493 $output .= $src; // The new src.
494 $output .= $matches[2]; // The end of the tag, excluding the closing "/>".
495
496 // Add height and width properties if they are missing from the original tag.
497 $output .= $image['add_properties']['width'] ? ' width="'. $image['expected_size']['width'] .'"' : '';
498 $output .= $image['add_properties']['height'] ? ' height="'. $image['expected_size']['height'] .'"' : '';
499
500 $output .= $matches[3]; // The closing "/>".
501 if ($settings['link'] && !$image['has_link']) {
502 $class = !empty($settings['class']) ? ' class="'. $settings['class'] .'"' : '';
503 $rel = !empty($settings['rel']) ? ' rel="'. $settings['rel'] .'"' : '';
504 $title = !empty($image['title']) ? ' title="'. $image['title'] .'"' : '';
505 $output = '<a href="'. $image['original'] .'"'. $title . $class . $rel . '>'. $output .'</a>';
506 }
507
508 return $output;
509 }
510
511 /**
512 * Delete all generated image when the original file is removed.
513 */
514 function image_resize_filter_delete_derivatives($original_filepath) {
515
516 // First delete all derivatives in the saved file location.
517 $path_info = image_resize_filter_pathinfo($original_filepath);
518 $basename = $path_info['filename'];
519 $extension = $path_info['extension'];
520 $file_directory_path = file_directory_path();
521 $local_file_dir = str_replace($file_directory_path, '', $path_info['dirname']);
522 $local_file_dir = !empty($local_file_dir) ? $local_file_dir . '/' : '';
523
524 $directory = $file_directory_path .'/resize/'. $local_file_dir;
525
526 // Delete all the derivatives.
527 file_scan_directory($directory, quotemeta($basename) .'-[0-9]+[x][0-9]+\.'. quotemeta($extension), array('.', '..', 'CVS'), 'file_delete');
528
529 // Then work up the directories and delete any empty ones.
530 $folders = explode('/', $directory);
531 $directories = array();
532 $current_directory = '';
533 foreach ($folders as $folder) {
534 $current_directory .= $folder .'/';
535 $directories[] = $current_directory;
536 }
537
538 foreach (array_reverse($directories) as $directory) {
539 if ($directory == ($file_directory_path .'/')) {
540 break;
541 }
542
543 $directory_files = file_scan_directory($directory, '.*');
544 if (empty($directory_files)) {
545 @rmdir($directory);
546 }
547 else {
548 break;
549 }
550 }
551 }
552
553 /**
554 * Delete the entire set of cached images.
555 */
556 function image_resize_filter_delete_all() {
557 $directory = file_directory_path() .'/resize';
558 image_resize_filter_delete_recursive($directory);
559 cache_clear_all('*', 'cache_filter');
560 }
561
562 /**
563 * Recursive deletion function for clearing out resized images directory.
564 */
565 function image_resize_filter_delete_recursive($path) {
566 if (is_file($path) || is_link($path)) {
567 unlink($path);
568 }
569 elseif (is_dir($path)) {
570 $dir = dir($path);
571 while (($entry = $dir->read()) !== false) {
572 if ($entry == '.' || $entry == '..') {
573 continue;
574 }
575 $entry_path = $path .'/'. $entry;
576 image_resize_filter_delete_recursive($entry_path);
577 }
578 rmdir($path);
579 }
580 }
581
582 /**
583 * Utility function to return path information.
584 */
585 function image_resize_filter_pathinfo($path) {
586 $info = pathinfo($path);
587 // Filename was added in PHP 5.2, add it for older PHP versions.
588 if (!isset($info['filename'])) {
589 $info['filename'] = basename($path, '.' . $info['extension']);
590 }
591 return $info;
592 }

  ViewVC Help
Powered by ViewVC 1.1.2