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

Contents of /contributions/modules/image/image.module

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


Revision 1.322 - (show annotations) (download) (as text)
Sun Sep 13 20:21:04 2009 UTC (2 months, 1 week ago) by joachim
Branch: MAIN
CVS Tags: HEAD
Changes since 1.321: +2 -2 lines
File MIME type: text/x-php
#226121 by sp3boy: Fixed bad logic from previous patch to this issue.
1 <?php
2 // $Id: image.module,v 1.321 2009/09/09 19:06:00 joachim Exp $
3
4 define('IMAGE_ORIGINAL', '_original');
5 define('IMAGE_PREVIEW', 'preview');
6 define('IMAGE_THUMBNAIL', 'thumbnail');
7
8 define('IMAGE_LINK_HIDDEN', 0);
9 define('IMAGE_LINK_SHOWN', 1);
10 define('IMAGE_LINK_NEW', 2);
11
12 /**
13 * Implementation of hook_help().
14 */
15 function image_help($path, $arg) {
16 switch ($path) {
17 case 'admin/help#image':
18 $output = '<p>' . t('The Image module is used to create and administer images for your site. Each image is stored as a post, with thumbnails of the original generated automatically. There are two default derivative image sizes, "thumbnail" and "preview". The "thumbnail" size is shown as preview image in posts and when browsing image galleries. The "preview" size is the default size when viewing an image node page.') . '</p>';
19 $output .= '<p>' . t('The settings page for Image module allows the image directory and the image sizes to be configured.') . '</p>';
20 $output .= '<p>' . t('Image module ships with contributed modules. Their settings can be accessed from the image settings page.') . '</p>';
21 $output .= '<ul>';
22 $output .= '<li>' . t('Image attach is used to add an existing or new image to a node. The selected image will show up in a predefined spot on the selected node.') . '</li>';
23 $output .= '<li>' . t('Image gallery is used to organize and display images in galleries. The list tab allows users to edit existing image gallery names, descriptions, parents and relative position, known as a weight. The add galleries tab allows you to create a new image gallery defining name, description, parent and weight. If the <a href="@views-url">Views module</a> is installed, then the Image gallery module settings are mostly replaced by settings of the view.', array('@views-url' => 'http://drupal.org/project/views')) . '</li>';
24 $output .= '<li>' . t('Image import is used to import batches of images. The administration page lets you define the folder from which images will be imported.') . '</li>';
25 $output .= '<li>' . t('The separate <a href="@img-assist-url">Image assist module</a> can be installed to upload and insert images into posts.', array('@img-assist-url' => 'http://drupal.org/project/img_assist')) . '</li>';
26 $output .= '</ul>';
27 $output .= '<p>' . t('You can:') . '</p>';
28 $output .= '<ul>';
29 $output .= '<li>' . t('Configure image sizes and file directories at <a href="@image-settings-url">Administer &raquo; Site configuration &raquo; Image</a>.', array('@image-settings-url' => url('admin/settings/image'))) . '</li>';
30 $output .= '</ul>';
31 $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@image-url">Image module</a>.', array('@image-url' => 'http://drupal.org/handbook/modules/image')) . '</p>';
32 return $output;
33 }
34 }
35
36 /**
37 * Implementation of hook_theme().
38 */
39 function image_theme() {
40 return array(
41 'image_settings_sizes_form' => array(
42 'arguments' => array('form' => NULL),
43 ),
44 'image_teaser' => array(
45 'arguments' => array('node' => NULL, 'size' => IMAGE_THUMBNAIL),
46 ),
47 'image_body' => array(
48 'arguments' => array('node' => NULL, 'size' => IMAGE_PREVIEW),
49 ),
50 'image_block_random' => array(
51 'arguments' => array('images' => NULL, 'size' => IMAGE_THUMBNAIL),
52 ),
53 'image_block_latest' => array(
54 'arguments' => array('images' => NULL, 'size' => IMAGE_THUMBNAIL),
55 ),
56 'image_display' => array(
57 'arguments' => array(
58 'node' => NULL,
59 'label' => NULL,
60 'url' => NULL,
61 'attributes' => NULL,
62 ),
63 ),
64 );
65 }
66
67 /**
68 * Implementation of hook_node_info
69 */
70 function image_node_info() {
71 return array(
72 'image' => array(
73 'name' => t('Image'),
74 'module' => 'image',
75 'description' => t('An image (with thumbnail). This is ideal for publishing photographs or screenshots.'),
76 )
77 );
78 }
79
80 /**
81 * Implementation of hook_perm
82 */
83 function image_perm() {
84 return array('view original images', 'create images', 'edit own images', 'edit any images', 'delete own images', 'delete any images');
85 }
86
87 /**
88 * Implementation of hook_access().
89 */
90 function image_access($op, $node, $account) {
91 switch ($op) {
92 case 'create':
93 if (user_access('create images', $account)) {
94 return TRUE;
95 }
96 break;
97
98 case 'update':
99 if (user_access('edit any images', $account) || ($account->uid == $node->uid && user_access('edit own images', $account))) {
100 return TRUE;
101 }
102 break;
103
104 case 'delete':
105 if (user_access('delete any images', $account) || ($account->uid == $node->uid && user_access('delete own images', $account))) {
106 return TRUE;
107 }
108 break;
109 }
110 }
111
112 /**
113 * Implementation of hook_menu
114 */
115 function image_menu() {
116 $items = array();
117
118 $items['image/view'] = array(
119 'title' => 'image',
120 'access arguments' => array('access content'),
121 'type' => MENU_CALLBACK,
122 'page callback' => 'image_fetch',
123 );
124 $items['admin/settings/image'] = array(
125 'title' => 'Images',
126 'description' => 'Configure the location of image files and image sizes. Also, if enabled, configure image attachments and options for image galleries and image imports.',
127 'page callback' => 'drupal_get_form',
128 'page arguments' => array('image_admin_settings'),
129 'access arguments' => array('administer site configuration'),
130 'type' => MENU_NORMAL_ITEM,
131 'file' => 'image.admin.inc',
132 );
133 $items['admin/settings/image/nodes'] = array(
134 'title' => 'Files and sizes',
135 'description' => 'Configure the location of image files and image sizes.',
136 'access arguments' => array('administer site configuration'),
137 'type' => MENU_DEFAULT_LOCAL_TASK,
138 'weight' => '-10',
139 );
140
141 return $items;
142 }
143
144 /**
145 * Implements hook_cron. (deletes old temp images)
146 */
147 function image_cron() {
148 $path = file_directory_path() .'/'. variable_get('image_default_path', 'images') .'/temp';
149 $files = file_scan_directory(file_create_path($path), '.*');
150 foreach ($files as $file => $info) {
151 if (time() - filemtime($file) > 60*60*6) {
152 file_delete($file);
153 }
154 }
155 }
156
157 /**
158 * Implementation of hook_node_operations().
159 */
160 function image_node_operations() {
161 $operations = array(
162 'rebuild_thumbs' => array(
163 'label' => t('Rebuild derivative images'),
164 'callback' => 'image_operations_rebuild',
165 ),
166 );
167 return $operations;
168 }
169
170 function image_operations_rebuild($nids) {
171 foreach ($nids as $nid) {
172 if ($node = node_load($nid)) {
173 if ($node->type == 'image') {
174 $node->rebuild_images = TRUE;
175 image_update($node);
176 }
177 }
178 }
179 }
180
181 /**
182 * Implementation of hook_file_download().
183 *
184 * Note that in Drupal 5, the upload.module's hook_file_download() checks its
185 * permissions for all files in the {files} table. We store our file
186 * information in {files} if private files transfers are selected and the
187 * upload.module is enabled, users will the 'view uploaded files' permission to
188 * view images.
189 */
190 function image_file_download($filename) {
191 $filepath = file_create_path($filename);
192 $result = db_query("SELECT i.nid, f.filemime, f.filesize FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE f.filepath = '%s'", $filepath);
193 if ($file = db_fetch_object($result)) {
194 $node = node_load(array('type' => 'image', 'nid' => $file->nid));
195 if (node_access('view', $node)) {
196 // The user either needs to have 'view original images' permission or
197 // the path must be listed for something other than the node's original
198 // size. This will be the case when the orignal is smaller than a
199 // derivative size.
200 $images = (array) $node->images;
201 unset($images[IMAGE_ORIGINAL]);
202 if (user_access('view original images') || in_array($filepath, $images)) {
203 return array(
204 'Content-Type: '. mime_header_encode($file->filemime),
205 'Content-Length: '. (int) $file->filesize,
206 );
207 }
208 }
209 return -1;
210 }
211 }
212
213 /**
214 * Implementation of hook_link.
215 */
216 function image_link($type, $node, $main = 0) {
217 $links = array();
218
219 if ($type == 'node' && $node->type == 'image' && !$main) {
220 $request = isset($_GET['size']) ? $_GET['size'] : IMAGE_PREVIEW;
221 foreach (image_get_sizes() as $key => $size) {
222 if ($size['link']) {
223 // For smaller images some derivative images may not have been created.
224 // The thumbnail and preview images will be equal to the original images
225 // but other sizes will not be set.
226 if (isset($node->images[$key]) && $node->images[$key] != $node->images[$request]) {
227 if ($size['link'] == IMAGE_LINK_NEW) {
228 $links['image_size_'. $key] = array(
229 'title' => t($size['label']),
230 'href' => "image/view/{$node->nid}/$key",
231 'attributes' => array('target' => '_blank'),
232 );
233 }
234 else {
235 $links['image_size_'. $key] = array(
236 'title' => t($size['label']),
237 'href' => 'node/'. $node->nid,
238 'query' => 'size='. urlencode($key)
239 );
240 }
241 }
242 }
243 }
244 if (!user_access('view original images')) {
245 unset($links['image_size_'. IMAGE_ORIGINAL]);
246 }
247 }
248
249 return $links;
250 }
251
252 /**
253 * Implementation of hook_block.
254 *
255 * Offers 2 blocks: latest image and random image
256 */
257 function image_block($op = 'list', $delta = 0, $edit = array()) {
258 switch ($op) {
259 case 'list':
260 $block[0]['info'] = t('Latest image');
261 $block[1]['info'] = t('Random image');
262 return $block;
263
264 case 'configure':
265 $form['number_images'] = array(
266 '#type' => 'select',
267 '#title' => t('Number of images to display'),
268 '#options' => drupal_map_assoc(range(1, 99)),
269 '#default_value' => variable_get("image_block_{$delta}_number_images", 1),
270 );
271 return $form;
272
273 case 'view':
274 if (user_access('access content')) {
275 switch ($delta) {
276 case 0:
277 $images = image_get_latest(variable_get("image_block_{$delta}_number_images", 1));
278 $block['subject'] = t('Latest image');
279 $block['content'] = theme('image_block_latest', $images, IMAGE_THUMBNAIL);
280 break;
281
282 case 1:
283 $images = image_get_random(variable_get("image_block_{$delta}_number_images", 1));
284 $block['subject'] = t('Random image');
285 $block['content'] = theme('image_block_random', $images, IMAGE_THUMBNAIL);
286 break;
287 }
288 }
289 return $block;
290
291 case 'save':
292 variable_set("image_block_{$delta}_number_images", $edit['number_images']);
293 break;
294 }
295 }
296
297 function image_form_add_thumbnail($form, &$form_state) {
298 if ($form_state['values']['images'][IMAGE_THUMBNAIL]) {
299 $node = (object)($form_state['values']);
300 $form['#title'] = t('Thumbnail');
301 $form['#value'] = image_display($node, IMAGE_THUMBNAIL);
302 }
303 return $form;
304 }
305
306 /**
307 * Implementation of hook_form().
308 */
309 function image_form(&$node, $form_state) {
310 _image_check_settings();
311
312 if (!$_POST && !empty($_SESSION['image_upload'])) {
313 unset($_SESSION['image_upload']);
314 }
315
316 $type = node_get_types('type', $node);
317
318 $form['#validate'][] = 'image_form_validate';
319 $form['#submit'][] = 'image_form_submit';
320
321 $form['title'] = array(
322 '#type' => 'textfield',
323 '#title' => check_plain($type->title_label),
324 '#size' => 60,
325 '#maxlength' => 128,
326 '#required' => TRUE,
327 '#default_value' => $node->title
328 );
329
330 $form['images']['#tree'] = TRUE;
331 foreach (image_get_sizes() as $key => $size) {
332 $form['images'][$key] = array(
333 '#type' => 'value',
334 '#value' => isset($node->images[$key]) ? $node->images[$key] : '',
335 );
336 }
337
338 $form['new_file'] = array(
339 '#type' => 'value',
340 '#default_value' => isset($node->new_file) ? $node->new_file : FALSE,
341 );
342
343 $form['#attributes'] = array('enctype' => 'multipart/form-data');
344
345 $form['image'] = array(
346 '#prefix' => '<div class="image-field-wrapper">',
347 '#suffix' => '</div>',
348 );
349 $form['image']['thumbnail'] = array(
350 '#type' => 'item',
351 '#after_build' => array('image_form_add_thumbnail'),
352 );
353 $form['image']['image'] = array(
354 '#type' => 'file',
355 '#title' => t('Image'),
356 '#size' => 40,
357 '#description' => t('Select an image to upload.'),
358 );
359 $form['image']['rebuild_images'] = array(
360 '#type' => 'checkbox',
361 '#title' => t('Rebuild derivative images'),
362 '#default_value' => FALSE,
363 '#description' => t('Check this to rebuild the derivative images for this node.'),
364 '#access' => (!isset($node->nid) ? FALSE : TRUE),
365 );
366
367 if ($type->has_body) {
368 $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
369 }
370
371 return $form;
372 }
373
374 function image_form_validate($form, &$form_state) {
375 // Avoid blocking node deletion with missing image.
376 if ($form_state['values']['op'] == t('Delete')) {
377 return;
378 }
379
380 // Validators for file_save_upload().
381 $validators = array(
382 'file_validate_is_image' => array(),
383 );
384
385 // New image uploads need to be saved in images/temp in order to be viewable
386 // during node preview.
387 $temporary_file_path = file_create_path(file_directory_path() . '/' . variable_get('image_default_path', 'images') .'/temp');
388
389 if ($file = file_save_upload('image', $validators, $temporary_file_path)) {
390 // Resize the original.
391 $image_info = image_get_info($file->filepath);
392 $aspect_ratio = $image_info['height'] / $image_info['width'];
393 $original_size = image_get_sizes(IMAGE_ORIGINAL, $aspect_ratio);
394 if (!empty($original_size['width']) && !empty($original_size['height'])) {
395 $result = image_scale($file->filepath, $file->filepath, $original_size['width'], $original_size['height']);
396 if ($result) {
397 clearstatcache();
398 $file->filesize = filesize($file->filepath);
399 drupal_set_message(t('The original image was resized to fit within the maximum allowed resolution of %width x %height pixels.', array('%width' => $original_size['width'], '%height' => $original_size['height'])));
400 }
401 }
402
403 // Check the file size limit.
404 if ($file->filesize > variable_get('image_max_upload_size', 800) * 1024) {
405 form_set_error('image', t('The image you uploaded was too big. You are only allowed upload files less than %max_size but your file was %file_size.', array('%max_size' => format_size(variable_get('image_max_upload_size', 800) * 1024), '%file_size' => format_size($file->filesize))));
406 file_delete($file->filepath);
407 return;
408 }
409
410 // We're good to go.
411 $form_state['values']['images'][IMAGE_ORIGINAL] = $file->filepath;
412 $form_state['values']['rebuild_images'] = FALSE;
413 $form_state['values']['new_file'] = TRUE;
414
415 // Call hook to allow other modules to modify the original image.
416 module_invoke_all('image_alter', $form_state['values'], $form_state['values']['images'][IMAGE_ORIGINAL], IMAGE_ORIGINAL);
417 $form_state['values']['images'] = _image_build_derivatives((object) $form_state['values'], TRUE);
418
419 // Store the new file into the session.
420 $_SESSION['image_upload'] = $form_state['values']['images'];
421 }
422 elseif (empty($form_state['values']['images'][IMAGE_ORIGINAL])) {
423 if (empty($_SESSION['image_upload'])) {
424 form_set_error('image', t('You must upload an image.'));
425 }
426 }
427 }
428
429 function image_form_submit($form, &$form_state) {
430 if (!empty($_SESSION['image_upload'])) {
431 $form_state['values']['images'] = $_SESSION['image_upload'];
432 $form_state['values']['new_file'] = TRUE;
433 unset($_SESSION['image_upload']);
434 }
435 }
436
437 /**
438 * Implementation of hook_view
439 */
440 function image_view($node, $teaser = 0, $page = 0) {
441 $sizes = image_get_sizes();
442 $size = IMAGE_PREVIEW;
443 if (isset($_GET['size'])) {
444 // Invalid size specified.
445 if (!isset($sizes[$_GET['size']])) {
446 drupal_goto("node/$node->nid");
447 }
448 $size = $_GET['size'];
449 // Not allowed to view the original.
450 if ($size == IMAGE_ORIGINAL && !user_access('view original images')) {
451 drupal_goto("node/$node->nid");
452 }
453 }
454
455 $node = node_prepare($node, $teaser);
456 $node->content['image'] = array(
457 '#value' => theme($teaser ? 'image_teaser' : 'image_body', $node, $size),
458 '#weight' => 0,
459 );
460
461 return $node;
462 }
463
464 /**
465 * Implementation of hook_load().
466 */
467 function image_load(&$node) {
468 $result = db_query("SELECT i.image_size, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d", $node->nid);
469 $node->images = array();
470 while ($file = db_fetch_object($result)) {
471 $node->images[$file->image_size] = file_create_path($file->filepath);
472 }
473
474 $original_path = (isset($node->images[IMAGE_ORIGINAL]) ? $node->images[IMAGE_ORIGINAL] : NULL);
475 if (empty($original_path)) {
476 // There's no original image, we're in trouble...
477 return;
478 }
479
480 if ((arg(0) != 'batch') && (strpos($_GET['q'], 'admin/content/node') === FALSE)) {
481 _image_build_derivatives_if_needed($node);
482 }
483 }
484
485 /**
486 * Rebuild derivatives if needed. Helper function for image_load().
487 */
488 function _image_build_derivatives_if_needed(&$node) {
489 $node->rebuild_images = FALSE;
490
491 // Figure out which sizes should have been generated.
492 $all_sizes = image_get_sizes();
493 unset($all_sizes[IMAGE_ORIGINAL]);
494 $needed_sizes = array_keys(image_get_derivative_sizes($node->images[IMAGE_ORIGINAL]));
495 $unneeded_sizes = array_diff(array_keys($all_sizes), $needed_sizes);
496
497 // Derivative sizes that are larger than the original get set to the
498 // original.
499 foreach ($unneeded_sizes as $key) {
500 if (empty($node->images[$key])) {
501 $node->images[$key] = $node->images[IMAGE_ORIGINAL];
502 }
503 else {
504 // Need to remove an extra derivative image in the database.
505 $node->rebuild_images = TRUE;
506 }
507 }
508
509 // Check that the derivative images are present and current.
510 foreach ($needed_sizes as $key) {
511 // If the file is missing or created after the last change to the sizes,
512 // rebuild the derivatives.
513 if (empty($node->images[$key]) || !file_exists($node->images[$key])) {
514 $node->rebuild_images = TRUE;
515 }
516 // Derivative image had a timestamp that predates last changes to image
517 // size settings, so it needs to be rebuilt.
518 elseif (filemtime($node->images[$key]) < variable_get('image_updated', 0)) {
519 $node->rebuild_images = TRUE;
520 }
521 }
522
523 // Correct any problems with the derivative images.
524 if ($node->rebuild_images) {
525 // Possibly TODO: calling a hook implementation here isn't very elegant.
526 // but the code there is tangled and it works, so maybe leave it ;)
527 image_update($node);
528 watchdog('image', 'Derivative images were regenerated for %title.', array('%title' => $node->title), WATCHDOG_INFO, l(t('view'), 'node/'. $node->nid));
529 }
530 }
531
532 /**
533 * Implementation of hook_insert().
534 */
535 function image_insert($node) {
536 // If a new image node contains no new file, but has a translation source,
537 // insert all images from the source for this node.
538 if (empty($node->new_file) && !empty($node->translation_source)) {
539 db_query("INSERT INTO {image} (nid, fid, image_size) SELECT %d, fid, image_size FROM {image} WHERE nid = %d", $node->nid, $node->translation_source->nid);
540 return;
541 }
542
543 // Derivative images that aren't needed are set to the original file. Make
544 // note of the current path before calling _image_insert() because if it's
545 // in the temp directory it'll be moved. We'll need it later to determine
546 // which derivative images need to be saved with _image_insert().
547 $original_path = $node->images[IMAGE_ORIGINAL];
548
549 // Save the original first so that it if it's moved the derivatives are
550 // placed in the correct directory.
551 _image_insert($node, IMAGE_ORIGINAL, $original_path);
552
553 $sizes = image_get_derivative_sizes($node->images[IMAGE_ORIGINAL]);
554 foreach ($sizes as $key => $size_info) {
555 if (!empty($node->images[$key]) && $node->images[$key] != $original_path) {
556 _image_insert($node, $key, $node->images[$key]);
557 }
558 }
559 }
560
561 /**
562 * Implementation of hook_update().
563 *
564 * Take $node by reference so we can use this to save the node after
565 * rebuilding derivatives.
566 */
567 function image_update(&$node) {
568 if (!empty($node->new_file) || !empty($node->rebuild_images)) {
569 // Derivative images that aren't needed are set to the original file. Make
570 // note of the current path before calling _image_insert() because if it's
571 // in the temp directory it'll be moved. We'll need it later to determine
572 // which derivative images need to be saved with _image_insert().
573 $original_path = $node->images[IMAGE_ORIGINAL];
574
575 if (!empty($node->new_file)) {
576 // The derivative images were built during image_prepare() or
577 // image_create_node_from() so all we need to do is remove all the old,
578 // existing images.
579
580 // Remove all the existing images.
581 $result = db_query("SELECT f.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d", $node->nid);
582 while ($file = db_fetch_object($result)) {
583 db_query("DELETE FROM {image} WHERE nid = %d AND fid = %d", $node->nid, $file->fid);
584 _image_file_remove($file);
585 }
586
587 // Save the original first so that it if it's moved the derivatives are
588 // placed in the correct directory.
589 _image_insert($node, IMAGE_ORIGINAL, $original_path);
590 }
591 else if (!empty($node->rebuild_images)) {
592 // Find the original image.
593 $original_file = db_fetch_object(db_query("SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d AND i.image_size = '%s'", $node->nid, IMAGE_ORIGINAL));
594
595 // Delete all but the original image.
596 $result = db_query("SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d AND f.fid <> %d", $node->nid, $original_file->fid);
597 while ($file = db_fetch_object($result)) {
598 db_query("DELETE FROM {image} WHERE nid = %d AND fid = %d", $node->nid, $file->fid);
599 // Beware of derivative images that have the same path as the original.
600 if ($file->filepath != $original_file->filepath) {
601 _image_file_remove($file);
602 }
603 }
604
605 $node->images = _image_build_derivatives($node, FALSE);
606
607 // Prevent multiple rebuilds.
608 $node->rebuild_images = FALSE;
609 }
610
611 $sizes = image_get_derivative_sizes($node->images[IMAGE_ORIGINAL]);
612 foreach ($sizes as $key => $size_info) {
613 if (!empty($node->images[$key]) && $node->images[$key] != $original_path) {
614 _image_insert($node, $key, $node->images[$key]);
615 }
616 }
617 }
618 }
619
620 /**
621 * Implementation of hook_delete().
622 */
623 function image_delete($node) {
624 $result = db_query('SELECT i.fid, f.filepath FROM {image} i INNER JOIN {files} f ON i.fid = f.fid WHERE i.nid = %d', $node->nid);
625 while ($file = db_fetch_object($result)) {
626 db_query("DELETE FROM {image} WHERE nid = %d AND fid = %d", $node->nid, $file->fid);
627 _image_file_remove($file);
628 }
629 }
630
631 /**
632 * Create an <img> tag for an image.
633 */
634 function image_display(&$node, $label = IMAGE_PREVIEW, $attributes = array()) {
635 if (empty($node->images[$label])) {
636 return;
637 }
638
639 $image_info = image_get_info(file_create_path($node->images[$label]));
640 $attributes['class'] = "image image-$label ". (isset($attributes['class']) ? $attributes['class'] : "");
641 // Only output width/height attributes if image_get_info() was able to detect
642 // the image dimensions, since certain browsers interpret an empty attribute
643 // value as zero.
644 if (!empty($image_info['width'])) {
645 $attributes['width'] = $image_info['width'];
646 }
647 if (!empty($image_info['height'])) {
648 $attributes['height'] = $image_info['height'];
649 }
650
651 return theme('image_display', $node, $label, file_create_url($node->images[$label]), $attributes);
652 }
653
654 /**
655 * Fetches an image file, allows "shorthand" image urls such of the form:
656 * image/view/$nid/$label
657 * (e.g. image/view/25/thumbnail or image/view/14)
658 */
659 function image_fetch($nid = 0, $size = IMAGE_PREVIEW) {
660 if ($size == IMAGE_ORIGINAL && !user_access('view original images')) {
661 return drupal_access_denied();
662 }
663
664 if (isset($nid)) {
665 $node = node_load(array('type' => 'image', 'nid' => $nid));
666 if ($node) {
667 if (!node_access('view', $node)) {
668 return drupal_access_denied();
669 }
670
671 if (isset($node->images[$size])) {
672 $file = $node->images[$size];
673 $headers = image_file_download($file);
674 if ($headers == -1) {
675 return drupal_access_denied();
676 }
677 file_transfer($file, $headers);
678 }
679 }
680 }
681 return drupal_not_found();
682 }
683
684 /**
685 * Theme a teaser
686 */
687 function theme_image_teaser($node, $size) {
688 return l(image_display($node, IMAGE_THUMBNAIL), 'node/'. $node->nid, array('html' => TRUE));
689 }
690
691 /**
692 * Theme a body
693 */
694 function theme_image_body($node, $size) {
695 return image_display($node, $size);
696 }
697
698 /**
699 * Theme an img tag for displaying the image.
700 */
701 function theme_image_display($node, $label, $url, $attributes) {
702 $title = isset($attributes['title']) ? $attributes['title'] : $node->title;
703 $alt = isset($attributes['alt']) ? $attributes['alt'] : $node->title;
704 return theme('image', $url, $alt, $title, $attributes, FALSE);
705 }
706
707 /**
708 * Theme a random block
709 */
710 function theme_image_block_random($images, $size) {
711 $output = '';
712 foreach ($images as $image) {
713 $output .= l(image_display($image, $size), 'node/'. $image->nid, array('html' => TRUE));
714 }
715 return $output;
716 }
717
718 /**
719 * Theme a latest block
720 */
721 function theme_image_block_latest($images, $size) {
722 $output = '';
723 foreach ($images as $image) {
724 $output .= l(image_display($image, $size), 'node/'. $image->nid, array('html' => TRUE));
725 }
726 return $output;
727 }
728
729 /**
730 * Fetch a random N image(s) - optionally from a given term.
731 */
732 function image_get_random($count = 1, $tid = 0) {
733 if ($tid != 0) {
734 $result = db_query_range(db_rewrite_sql("SELECT DISTINCT(n.nid), RAND() AS rand FROM {term_node} tn LEFT JOIN {node} n ON n.nid = tn.nid WHERE n.type='image' AND n.status = 1 AND tn.tid = %d ORDER BY rand"), $tid, 0, $count);
735 }
736 else {
737 $result = db_query_range(db_rewrite_sql("SELECT DISTINCT(n.nid), RAND() AS rand FROM {node} n WHERE n.type = 'image' AND n.status = 1 ORDER BY rand"), 0, $count);
738 }
739 $output = array();
740 while ($nid = db_fetch_object($result)) {
741 $output[] = node_load(array('nid' => $nid->nid));
742 }
743 return $output;
744 }
745
746 /**
747 * Fetch the latest N image(s) - optionally from a given term.
748 */
749 function image_get_latest($count = 1, $tid = 0) {
750 if ($tid != 0) {
751 $result = db_query_range(db_rewrite_sql("SELECT n.nid FROM {term_node} tn LEFT JOIN {node} n ON n.nid=tn.nid WHERE n.type='image' AND n.status=1 AND tn.tid=%d ORDER BY n.changed DESC"), $tid, 0, $count);
752 }
753 else {
754 $result = db_query_range(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.type = 'image' AND n.status = 1 ORDER BY n.changed DESC"), 0, $count);
755 }
756 $output = array();
757 while ($nid = db_fetch_object($result)) {
758 $output[] = node_load(array('nid' => $nid->nid));
759 }
760 return $output;
761 }
762
763 /**
764 * Verify the image module and toolkit settings.
765 */
766 function _image_check_settings() {
767 // File paths
768 $image_path = file_create_path(file_directory_path() .'/'. variable_get('image_default_path', 'images'));
769 $temp_path = $image_path .'/temp';
770
771 if (!file_check_directory($image_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS, 'image_default_path')) {
772 return false;
773 }
774 if (!file_check_directory($temp_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS, 'image_default_path')) {
775 return false;
776 }
777
778 // Sanity check : make sure we've got a working toolkit
779 if (!image_get_toolkit()) {
780 drupal_set_message(t('No image toolkit is currently enabled. Without one the image module will not be able to resize your images. You can select one from the <a href="@link">image toolkit settings page</a>.', array('@link' => url('admin/settings/image-toolkit'))), 'error');
781 return false;
782 }
783 return true;
784 }
785
786 /**
787 * Determine which sizes of derivative images need to be built for this image.
788 *
789 * @param $image_path
790 * String file path to the image.
791 * @return
792 * Returns a subset of image_get_sizes()'s results depending on what
793 * derivative images are needed.
794 */
795 function image_get_derivative_sizes($image_path) {
796 $sizes = array();
797
798 // Can't do much if we can't read the image.
799 if (!$image_info = image_get_info($image_path)) {
800 return $sizes;
801 }
802
803 $all_sizes = image_get_sizes(NULL, $image_info['height'] / $image_info['width']);
804 foreach ($all_sizes as $key => $size) {
805 // We don't want to include the original.
806 if ($key == IMAGE_ORIGINAL) {
807 continue;
808 }
809
810 // If the original isn't bigger than the requested size then there's no
811 // need to resize it.
812 if ($image_info['width'] > $size['width'] || $image_info['height'] > $size['height']) {
813 $sizes[$key] = $size;
814 }
815 }
816
817 return $sizes;
818 }
819
820 /**
821 * Generate image derivatives.
822 *
823 * @param $node
824 * The node.
825 * @param $temp
826 * Boolean indicating if the derivatives should be saved to the temp
827 * directory.
828 * @return
829 * New array of images for the node.
830 */
831 function _image_build_derivatives($node, $temp = FALSE) {
832 $original_path = file_create_path($node->images[IMAGE_ORIGINAL]);
833
834 // Figure out which sizes we need to generate.
835 $all_sizes = image_get_sizes();
836 $needed_sizes = image_get_derivative_sizes($original_path);
837 $unneeded_sizes = array_diff(array_keys($all_sizes), array_keys($needed_sizes));
838
839 // Images that don't need a derivative image get set to the original.
840 $images[IMAGE_ORIGINAL] = $original_path;
841 foreach ($unneeded_sizes as $key) {
842 $images[$key] = $original_path;
843 }
844
845 // Resize for the necessary sizes.
846 $image_info = image_get_info($original_path);
847 foreach ($needed_sizes as $key => $size) {
848 $destination = _image_filename($original_path, $key, $temp);
849
850 $status = FALSE;
851 switch ($size['operation']) {
852 // Depending on the operation, the image will be scaled or resized & cropped
853 case 'scale':
854 $status = image_scale($original_path, $destination, $size['width'], $size['height']);
855 break;
856
857 case 'scale_crop':
858 $status = image_scale_and_crop($original_path, $destination, $size['width'], $size['height']);
859 break;
860 }
861
862 if (!$status) {
863 drupal_set_message(t('Unable to create scaled %label image.', array('%label' => $size['label'])), 'error');
864 return FALSE;
865 }
866 // Set standard file permissions for webserver-generated files
867 @chmod($destination, 0664);
868
869 $images[$key] = $destination;
870 module_invoke_all('image_alter', $node, $destination, $key);
871 }
872
873 return $images;
874 }
875
876 /**
877 * Creates an image filename.
878 *
879 * @param $filepath
880 * The full path and filename of the original image file,relative to Drupal
881 * root, eg 'sites/default/files/images/myimage.jpg'.
882 * @return
883 * A full path and filename with derivative image label inserted if required.
884 */
885 function _image_filename($filepath, $label = IMAGE_ORIGINAL, $temp = FALSE) {
886 // Get default path for a new file.
887 $path = file_directory_path() .'/'. variable_get('image_default_path', 'images');
888 if ($temp) {
889 $path .= '/temp';
890 }
891
892 $original_path = dirname($filepath);
893 $filename = basename($filepath);
894
895 if ($label && ($label != IMAGE_ORIGINAL)) {
896 // Keep resized images in the same path, where original is (does not
897 // apply to temporary files, these still use the default path).
898 if (!$temp && $original_path != '.') {
899 $path = $original_path;
900 }
901 // Insert the resized name in non-original images.
902 $pos = strrpos($filename, '.');
903 if ($pos === false) {
904 // The file had no extension - which happens in really old image.module
905 // versions, so figure out the extension.
906 $image_info = image_get_info(file_create_path($path .'/'. $filename));
907 $filename = $filename .'.'. $label .'.'. $image_info['extension'];
908 }
909 else {
910 $filename = substr($filename, 0, $pos) .'.'. $label . substr($filename, $pos);
911 }
912 }
913
914 return file_create_path($path .'/'. $filename);
915 }
916
917 /**
918 * Helper function to return the defined sizes (or proper defaults).
919 *
920 * @param $size
921 * An optional string to return only the image size with the specified key.
922 * @param $aspect_ratio
923 * Float value with the ratio of image height / width. If a size has only one
924 * dimension provided this will be used to compute the other.
925 * @return
926 * An associative array with width, height, and label fields for the size.
927 * If a $size parameter was specified and it cannot be found FALSE will be
928 * returned.
929 */
930 function image_get_sizes($size = NULL, $aspect_ratio = NULL) {
931 $defaults = array(
932 IMAGE_ORIGINAL => array('width' => '', 'height' => '', 'label' => t('Original'), 'operation' => 'scale', 'link' => IMAGE_LINK_SHOWN),
933 IMAGE_THUMBNAIL => array('width' => 100, 'height' => 100, 'label' => t('Thumbnail'), 'operation' => 'scale', 'link' => IMAGE_LINK_SHOWN),
934 IMAGE_PREVIEW => array('width' => 640, 'height' => 640, 'label' => t('Preview'), 'operation' => 'scale', 'link' => IMAGE_LINK_SHOWN),
935 );
936
937 $sizes = array();
938 foreach (variable_get('image_sizes', $defaults) as $key => $val) {
939 // Only return sizes with a label.
940 if (!empty($val['label'])) {
941 // For a size with only one dimension specified, compute the other
942 // dimension based on an aspect ratio.
943 if ($aspect_ratio && (empty($val['width']) || empty($val['height']))) {
944 if (empty($val['height']) && !empty($val['width'])) {
945 $val['height'] = (int)round($val['width'] * $aspect_ratio);
946 }
947 elseif (empty($val['width']) && !empty($val['height'])) {
948 $val['width'] = (int)round($val['height'] / $aspect_ratio);
949 }
950 }
951 $sizes[$key] = $val;
952 }
953 }
954
955 // If they requested a specific size return only that.
956 if (isset($size)) {
957 // Only return an array if it's available.
958 return isset($sizes[$size]) ? $sizes[$size] : FALSE;
959 }
960
961 return $sizes;
962 }
963
964 /**
965 * Helper function to preserve backwards compatibility. This has been
966 * deprecated in favor of image_get_sizes().
967 *
968 * @TODO: Remove this in a future version.
969 */
970 function _image_get_sizes($size = NULL, $aspect_ratio = NULL) {
971 return image_get_sizes($size, $aspect_ratio);
972 }
973
974
975 /**
976 * Is a given size a built-in, required size?
977 * @param $size
978 * One of the keys in the array returned by image_get_sizes().
979 * @return boolean
980 */
981 function _image_is_required_size($size) {
982 return in_array($size, array(IMAGE_THUMBNAIL, IMAGE_PREVIEW, IMAGE_ORIGINAL));
983 }
984
985 /**
986 * Moves temporary (working) images to the final directory and stores
987 * relevant information in the files table
988 */
989 function _image_insert(&$node, $size, $image_path) {
990 $original_path = $node->images[IMAGE_ORIGINAL];
991 if (file_move($image_path, _image_filename($original_path, $size))) {
992 // Update the node to reflect the actual filename, it may have been changed
993 // if a file of the same name already existed.
994 $node->images[$size] = $image_path;
995
996 $image_info = image_get_info($image_path);
997 $file = array(
998 'uid' => $node->uid,
999 'filename' => $size,
1000 'filepath' => $image_path,
1001 'filemime' => $image_info['mime_type'],
1002 'filesize' => $image_info['file_size'],
1003 'status' => FILE_STATUS_PERMANENT,
1004 'timestamp' => time(),
1005 );
1006 drupal_write_record('files', $file);
1007 $image = array(
1008 'fid' => $file['fid'],
1009 'nid' => $node->nid,
1010 'image_size' => $size,
1011 );
1012 drupal_write_record('image', $image);
1013 }
1014 }
1015
1016 /**
1017 * Remove image file if no other node references it.
1018 *
1019 * @param $file
1020 * An object representing a table row from {files}.
1021 */
1022 function _image_file_remove($file) {
1023 if (!db_result(db_query("SELECT COUNT(*) FROM {image} WHERE fid = %d", $file->fid))) {
1024 file_delete(file_create_path($file->filepath));
1025 db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
1026 }
1027 }
1028
1029 /**
1030 * Function to other modules to use to create image nodes.
1031 *
1032 * @param $filepath
1033 * String filepath of an image file. Note that this file will be moved into
1034 * the image module's images directory.
1035 * @param $title
1036 * String to be used as the node's title. If this is ommitted the filename
1037 * will be used.
1038 * @param $body
1039 * String to be used as the node's body.
1040 * @param $taxonomy
1041 * Taxonomy terms to assign to the node if the taxonomy.module is installed.
1042 * @return
1043 * A node object if the node is created successfully or FALSE on error.
1044 */
1045 function image_create_node_from($filepath, $title = NULL, $body = '', $taxonomy = NULL) {
1046 global $user;
1047
1048 if (!user_access('create images')) {
1049 return FALSE;
1050 }
1051
1052 // Ensure it's a valid image.
1053 if (!$image_info = image_get_info($filepath)) {
1054 return FALSE;
1055 }
1056
1057 // Ensure the file is within our size bounds.
1058 if ($image_info['file_size'] > variable_get('image_max_upload_size', 800) * 1024) {
1059 form_set_error('', t('The image you uploaded was too big. You are only allowed upload files less than %max_size but your file was %file_size.', array('%max_size' => format_size(variable_get('image_max_upload_size', 800) * 1024), '%file_size' => format_size($image_info['file_size']))), 'warning');
1060 return FALSE;
1061 }
1062
1063 // Make sure we can copy the file into our temp directory.
1064 $original_path = $filepath;
1065 if (!file_copy($filepath, _image_filename($filepath, IMAGE_ORIGINAL, TRUE))) {
1066 return FALSE;
1067 }
1068
1069 // Resize the original image.
1070 $aspect_ratio = $image_info['height'] / $image_info['width'];
1071 $size = image_get_sizes(IMAGE_ORIGINAL, $aspect_ratio);
1072 if (!empty($size['width']) && !empty($size['height'])) {
1073 image_scale($filepath, $filepath, $size['width'], $size['height']);
1074 }
1075
1076 // Build the node.
1077 $node = new stdClass();
1078 $node->type = 'image';
1079 $node->uid = $user->uid;
1080 $node->name = $user->name;
1081 $node->title = isset($title) ? $title : basename($filepath);
1082 $node->body = $body;
1083
1084 // Set the node's defaults... (copied this from node and comment.module)
1085 $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
1086 $node->status = in_array('status', $node_options);
1087 $node->promote = in_array('promote', $node_options);
1088 if (module_exists('comment')) {
1089 $node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE);
1090 }
1091 if (module_exists('taxonomy')) {
1092 $node->taxonomy = $taxonomy;
1093 }
1094
1095 $node->new_file = TRUE;
1096 $node->images[IMAGE_ORIGINAL] = $filepath;
1097
1098 // Save the node.
1099 $node = node_submit($node);
1100 node_save($node);
1101
1102 // Remove the original image now that the import has completed.
1103 file_delete($original_path);
1104
1105 return $node;
1106 }
1107
1108 /**
1109 * Implementation of hook_views_api().
1110 */
1111 function image_views_api() {
1112 return array(
1113 'api' => 2,
1114 'path' => drupal_get_path('module', 'image') .'/views',
1115 );
1116 }
1117
1118 /**
1119 * Implementation of hook_content_extra_fields().
1120 *
1121 * Lets CCK expose the image weight in the node content.
1122 */
1123 function image_content_extra_fields($type_name) {
1124 if ($type_name == 'image') {
1125 $extra['image'] = array(
1126 'label' => t('Image'),
1127 'description' => t('Image display.'),
1128 'weight' => 0,
1129 );
1130 return