Projet

Général

Profil

Paste
Télécharger (47,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / image / image.module @ c7768a53

1
<?php
2

    
3
/**
4
 * @file
5
 * Exposes global functionality for creating image styles.
6
 */
7

    
8
/**
9
 * Image style constant for user presets in the database.
10
 */
11
define('IMAGE_STORAGE_NORMAL', 1);
12

    
13
/**
14
 * Image style constant for user presets that override module-defined presets.
15
 */
16
define('IMAGE_STORAGE_OVERRIDE', 2);
17

    
18
/**
19
 * Image style constant for module-defined presets in code.
20
 */
21
define('IMAGE_STORAGE_DEFAULT', 4);
22

    
23
/**
24
 * Image style constant to represent an editable preset.
25
 */
26
define('IMAGE_STORAGE_EDITABLE', IMAGE_STORAGE_NORMAL | IMAGE_STORAGE_OVERRIDE);
27

    
28
/**
29
 * Image style constant to represent any module-based preset.
30
 */
31
define('IMAGE_STORAGE_MODULE', IMAGE_STORAGE_OVERRIDE | IMAGE_STORAGE_DEFAULT);
32

    
33
/**
34
 * The name of the query parameter for image derivative tokens.
35
 */
36
define('IMAGE_DERIVATIVE_TOKEN', 'itok');
37

    
38
// Load all Field module hooks for Image.
39
require_once DRUPAL_ROOT . '/modules/image/image.field.inc';
40

    
41
/**
42
 * Implements hook_help().
43
 */
44
function image_help($path, $arg) {
45
  switch ($path) {
46
    case 'admin/help#image':
47
      $output = '';
48
      $output .= '<h3>' . t('About') . '</h3>';
49
      $output .= '<p>' . t('The Image module allows you to manipulate images on your website. It exposes a setting for using the <em>Image toolkit</em>, allows you to configure <em>Image styles</em> that can be used for resizing or adjusting images on display, and provides an <em>Image</em> field for attaching images to content. For more information, see the online handbook entry for <a href="@image">Image module</a>.', array('@image' => 'http://drupal.org/documentation/modules/image')) . '</p>';
50
      $output .= '<h3>' . t('Uses') . '</h3>';
51
      $output .= '<dl>';
52
      $output .= '<dt>' . t('Manipulating images') . '</dt>';
53
      $output .= '<dd>' . t('With the Image module you can scale, crop, resize, rotate and desaturate images without affecting the original image using <a href="@image">image styles</a>. When you change an image style, the module automatically refreshes all created images. Every image style must have a name, which will be used in the URL of the generated images. There are two common approaches to naming image styles (which you use will depend on how the image style is being applied):',array('@image' => url('admin/config/media/image-styles')));
54
      $output .= '<ul><li>' . t('Based on where it will be used: eg. <em>profile-picture</em>') . '</li>';
55
      $output .= '<li>' . t('Describing its appearance: eg. <em>square-85x85</em>') . '</li></ul>';
56
      $output .=  t('After you create an image style, you can add effects: crop, scale, resize, rotate, and desaturate (other contributed modules provide additional effects). For example, by combining effects as crop, scale, and desaturate, you can create square, grayscale thumbnails.') . '<dd>';
57
      $output .= '<dt>' . t('Attaching images to content as fields') . '</dt>';
58
      $output .= '<dd>' . t("Image module also allows you to attach images to content as fields. To add an image field to a <a href='@content-type'>content type</a>, go to the content type's <em>manage fields</em> page, and add a new field of type <em>Image</em>. Attaching images to content this way allows image styles to be applied and maintained, and also allows you more flexibility when theming.", array('@content-type' => url('admin/structure/types'))) . '</dd>';
59
      $output .= '</dl>';
60
      return $output;
61
    case 'admin/config/media/image-styles':
62
      return '<p>' . t('Image styles commonly provide thumbnail sizes by scaling and cropping images, but can also add various effects before an image is displayed. When an image is displayed with a style, a new file is created and the original image is left unchanged.') . '</p>';
63
    case 'admin/config/media/image-styles/edit/%/add/%':
64
      $effect = image_effect_definition_load($arg[7]);
65
      return isset($effect['help']) ? ('<p>' . $effect['help'] . '</p>') : NULL;
66
    case 'admin/config/media/image-styles/edit/%/effects/%':
67
      $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[7], $arg[5]);
68
      return isset($effect['help']) ? ('<p>' . $effect['help'] . '</p>') : NULL;
69
  }
70
}
71

    
72
/**
73
 * Implements hook_menu().
74
 */
75
function image_menu() {
76
  $items = array();
77

    
78
  // Generate image derivatives of publicly available files.
79
  // If clean URLs are disabled, image derivatives will always be served
80
  // through the menu system.
81
  // If clean URLs are enabled and the image derivative already exists,
82
  // PHP will be bypassed.
83
  $directory_path = file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath();
84
  $items[$directory_path . '/styles/%image_style'] = array(
85
    'title' => 'Generate image style',
86
    'page callback' => 'image_style_deliver',
87
    'page arguments' => array(count(explode('/', $directory_path)) + 1),
88
    'access callback' => TRUE,
89
    'type' => MENU_CALLBACK,
90
  );
91
  // Generate and deliver image derivatives of private files.
92
  // These image derivatives are always delivered through the menu system.
93
  $items['system/files/styles/%image_style'] = array(
94
    'title' => 'Generate image style',
95
    'page callback' => 'image_style_deliver',
96
    'page arguments' => array(3),
97
    'access callback' => TRUE,
98
    'type' => MENU_CALLBACK,
99
  );
100
  $items['admin/config/media/image-styles'] = array(
101
    'title' => 'Image styles',
102
    'description' => 'Configure styles that can be used for resizing or adjusting images on display.',
103
    'page callback' => 'image_style_list',
104
    'access arguments' => array('administer image styles'),
105
    'file' => 'image.admin.inc',
106
  );
107
  $items['admin/config/media/image-styles/list'] = array(
108
    'title' => 'List',
109
    'description' => 'List the current image styles on the site.',
110
    'page callback' => 'image_style_list',
111
    'access arguments' => array('administer image styles'),
112
    'type' => MENU_DEFAULT_LOCAL_TASK,
113
    'weight' => 1,
114
    'file' => 'image.admin.inc',
115
  );
116
  $items['admin/config/media/image-styles/add'] = array(
117
    'title' => 'Add style',
118
    'description' => 'Add a new image style.',
119
    'page callback' => 'drupal_get_form',
120
    'page arguments' => array('image_style_add_form'),
121
    'access arguments' => array('administer image styles'),
122
    'type' => MENU_LOCAL_ACTION,
123
    'weight' => 2,
124
    'file' => 'image.admin.inc',
125
  );
126
  $items['admin/config/media/image-styles/edit/%image_style'] = array(
127
    'title' => 'Edit style',
128
    'description' => 'Configure an image style.',
129
    'page callback' => 'drupal_get_form',
130
    'page arguments' => array('image_style_form', 5),
131
    'access arguments' => array('administer image styles'),
132
    'file' => 'image.admin.inc',
133
  );
134
  $items['admin/config/media/image-styles/delete/%image_style'] = array(
135
    'title' => 'Delete style',
136
    'description' => 'Delete an image style.',
137
    'load arguments' => array(NULL, (string) IMAGE_STORAGE_NORMAL),
138
    'page callback' => 'drupal_get_form',
139
    'page arguments' => array('image_style_delete_form', 5),
140
    'access arguments' => array('administer image styles'),
141
    'file' => 'image.admin.inc',
142
  );
143
  $items['admin/config/media/image-styles/revert/%image_style'] = array(
144
    'title' => 'Revert style',
145
    'description' => 'Revert an image style.',
146
    'load arguments' => array(NULL, (string) IMAGE_STORAGE_OVERRIDE),
147
    'page callback' => 'drupal_get_form',
148
    'page arguments' => array('image_style_revert_form', 5),
149
    'access arguments' => array('administer image styles'),
150
    'file' => 'image.admin.inc',
151
  );
152
  $items['admin/config/media/image-styles/edit/%image_style/effects/%image_effect'] = array(
153
    'title' => 'Edit image effect',
154
    'description' => 'Edit an existing effect within a style.',
155
    'load arguments' => array(5, (string) IMAGE_STORAGE_EDITABLE),
156
    'page callback' => 'drupal_get_form',
157
    'page arguments' => array('image_effect_form', 5, 7),
158
    'access arguments' => array('administer image styles'),
159
    'file' => 'image.admin.inc',
160
  );
161
  $items['admin/config/media/image-styles/edit/%image_style/effects/%image_effect/delete'] = array(
162
    'title' => 'Delete image effect',
163
    'description' => 'Delete an existing effect from a style.',
164
    'load arguments' => array(5, (string) IMAGE_STORAGE_EDITABLE),
165
    'page callback' => 'drupal_get_form',
166
    'page arguments' => array('image_effect_delete_form', 5, 7),
167
    'access arguments' => array('administer image styles'),
168
    'file' => 'image.admin.inc',
169
  );
170
  $items['admin/config/media/image-styles/edit/%image_style/add/%image_effect_definition'] = array(
171
    'title' => 'Add image effect',
172
    'description' => 'Add a new effect to a style.',
173
    'load arguments' => array(5),
174
    'page callback' => 'drupal_get_form',
175
    'page arguments' => array('image_effect_form', 5, 7),
176
    'access arguments' => array('administer image styles'),
177
    'file' => 'image.admin.inc',
178
  );
179

    
180
  return $items;
181
}
182

    
183
/**
184
 * Implements hook_theme().
185
 */
186
function image_theme() {
187
  return array(
188
    // Theme functions in image.module.
189
    'image_style' => array(
190
      'variables' => array(
191
        'style_name' => NULL,
192
        'path' => NULL,
193
        'width' => NULL,
194
        'height' => NULL,
195
        'alt' => '',
196
        'title' => NULL,
197
        'attributes' => array(),
198
      ),
199
    ),
200

    
201
    // Theme functions in image.admin.inc.
202
    'image_style_list' => array(
203
      'variables' => array('styles' => NULL),
204
    ),
205
    'image_style_effects' => array(
206
      'render element' => 'form',
207
    ),
208
    'image_style_preview' => array(
209
      'variables' => array('style' => NULL),
210
    ),
211
    'image_anchor' => array(
212
      'render element' => 'element',
213
    ),
214
    'image_resize_summary' => array(
215
      'variables' => array('data' => NULL),
216
    ),
217
    'image_scale_summary' => array(
218
      'variables' => array('data' => NULL),
219
    ),
220
    'image_crop_summary' => array(
221
      'variables' => array('data' => NULL),
222
    ),
223
    'image_rotate_summary' => array(
224
      'variables' => array('data' => NULL),
225
    ),
226

    
227
    // Theme functions in image.field.inc.
228
    'image_widget' => array(
229
      'render element' => 'element',
230
    ),
231
    'image_formatter' => array(
232
      'variables' => array('item' => NULL, 'path' => NULL, 'image_style' => NULL),
233
    ),
234
  );
235
}
236

    
237
/**
238
 * Implements hook_permission().
239
 */
240
function image_permission() {
241
  return array(
242
    'administer image styles' => array(
243
      'title' => t('Administer image styles'),
244
      'description' => t('Create and modify styles for generating image modifications such as thumbnails.'),
245
    ),
246
  );
247
}
248

    
249
/**
250
 * Implements hook_form_FORM_ID_alter().
251
 */
252
function image_form_system_file_system_settings_alter(&$form, &$form_state) {
253
  $form['#submit'][] = 'image_system_file_system_settings_submit';
254
}
255

    
256
/**
257
 * Form submission handler for system_file_system_settings().
258
 *
259
 * Adds a menu rebuild after the public file path has been changed, so that the
260
 * menu router item depending on that file path will be regenerated.
261
 */
262
function image_system_file_system_settings_submit($form, &$form_state) {
263
  if ($form['file_public_path']['#default_value'] !== $form_state['values']['file_public_path']) {
264
    variable_set('menu_rebuild_needed', TRUE);
265
  }
266
}
267

    
268
/**
269
 * Implements hook_flush_caches().
270
 */
271
function image_flush_caches() {
272
  return array('cache_image');
273
}
274

    
275
/**
276
 * Implements hook_file_download().
277
 *
278
 * Control the access to files underneath the styles directory.
279
 */
280
function image_file_download($uri) {
281
  $path = file_uri_target($uri);
282

    
283
  // Private file access for image style derivatives.
284
  if (strpos($path, 'styles/') === 0) {
285
    $args = explode('/', $path);
286
    // Discard the first part of the path (styles).
287
    array_shift($args);
288
    // Get the style name from the second part.
289
    $style_name = array_shift($args);
290
    // Remove the scheme from the path.
291
    array_shift($args);
292

    
293
    // Then the remaining parts are the path to the image.
294
    $original_uri = file_uri_scheme($uri) . '://' . implode('/', $args);
295

    
296
    // Check that the file exists and is an image.
297
    if ($info = image_get_info($uri)) {
298
      // Check the permissions of the original to grant access to this image.
299
      $headers = module_invoke_all('file_download', $original_uri);
300
      // Confirm there's at least one module granting access and none denying access.
301
      if (!empty($headers) && !in_array(-1, $headers)) {
302
        return array(
303
          // Send headers describing the image's size, and MIME-type...
304
          'Content-Type' => $info['mime_type'],
305
          'Content-Length' => $info['file_size'],
306
          // By not explicitly setting them here, this uses normal Drupal
307
          // Expires, Cache-Control and ETag headers to prevent proxy or
308
          // browser caching of private images.
309
        );
310
      }
311
    }
312
    return -1;
313
  }
314

    
315
  // Private file access for the original files. Note that we only check access
316
  // for non-temporary images, since file.module will grant access for all
317
  // temporary files.
318
  $files = file_load_multiple(array(), array('uri' => $uri));
319
  if (count($files)) {
320
    $file = reset($files);
321
    if ($file->status) {
322
      return file_file_download($uri, 'image');
323
    }
324
  }
325
}
326

    
327
/**
328
 * Implements hook_file_move().
329
 */
330
function image_file_move($file, $source) {
331
  // Delete any image derivatives at the original image path.
332
  image_path_flush($source->uri);
333
}
334

    
335
/**
336
 * Implements hook_file_delete().
337
 */
338
function image_file_delete($file) {
339
  // Delete any image derivatives of this image.
340
  image_path_flush($file->uri);
341
}
342

    
343
/**
344
 * Implements hook_image_default_styles().
345
 */
346
function image_image_default_styles() {
347
  $styles = array();
348

    
349
  $styles['thumbnail'] = array(
350
    'label' => 'Thumbnail (100x100)',
351
    'effects' => array(
352
      array(
353
        'name' => 'image_scale',
354
        'data' => array('width' => 100, 'height' => 100, 'upscale' => 1),
355
        'weight' => 0,
356
      ),
357
    )
358
  );
359

    
360
  $styles['medium'] = array(
361
    'label' => 'Medium (220x220)',
362
    'effects' => array(
363
      array(
364
        'name' => 'image_scale',
365
        'data' => array('width' => 220, 'height' => 220, 'upscale' => 1),
366
        'weight' => 0,
367
      ),
368
    )
369
  );
370

    
371
  $styles['large'] = array(
372
    'label' => 'Large (480x480)',
373
    'effects' => array(
374
      array(
375
        'name' => 'image_scale',
376
        'data' => array('width' => 480, 'height' => 480, 'upscale' => 0),
377
        'weight' => 0,
378
      ),
379
    )
380
  );
381

    
382
  return $styles;
383
}
384

    
385
/**
386
 * Implements hook_image_style_save().
387
 */
388
function image_image_style_save($style) {
389
  if (isset($style['old_name']) && $style['old_name'] != $style['name']) {
390
    $instances = field_read_instances();
391
    // Loop through all fields searching for image fields.
392
    foreach ($instances as $instance) {
393
      if ($instance['widget']['module'] == 'image') {
394
        $instance_changed = FALSE;
395
        foreach ($instance['display'] as $view_mode => $display) {
396
          // Check if the formatter involves an image style.
397
          if ($display['type'] == 'image' && $display['settings']['image_style'] == $style['old_name']) {
398
            // Update display information for any instance using the image
399
            // style that was just deleted.
400
            $instance['display'][$view_mode]['settings']['image_style'] = $style['name'];
401
            $instance_changed = TRUE;
402
          }
403
        }
404
        if ($instance['widget']['settings']['preview_image_style'] == $style['old_name']) {
405
          $instance['widget']['settings']['preview_image_style'] = $style['name'];
406
          $instance_changed = TRUE;
407
        }
408
        if ($instance_changed) {
409
          field_update_instance($instance);
410
        }
411
      }
412
    }
413
  }
414
}
415

    
416
/**
417
 * Implements hook_image_style_delete().
418
 */
419
function image_image_style_delete($style) {
420
  image_image_style_save($style);
421
}
422

    
423
/**
424
 * Implements hook_field_delete_field().
425
 */
426
function image_field_delete_field($field) {
427
  if ($field['type'] != 'image') {
428
    return;
429
  }
430

    
431
  // The value of a managed_file element can be an array if #extended == TRUE.
432
  $fid = (is_array($field['settings']['default_image']) ? $field['settings']['default_image']['fid'] : $field['settings']['default_image']);
433
  if ($fid && ($file = file_load($fid))) {
434
    file_usage_delete($file, 'image', 'default_image', $field['id']);
435
  }
436
}
437

    
438
/**
439
 * Implements hook_field_update_field().
440
 */
441
function image_field_update_field($field, $prior_field, $has_data) {
442
  if ($field['type'] != 'image') {
443
    return;
444
  }
445

    
446
  // The value of a managed_file element can be an array if #extended == TRUE.
447
  $fid_new = (is_array($field['settings']['default_image']) ? $field['settings']['default_image']['fid'] : $field['settings']['default_image']);
448
  $fid_old = (is_array($prior_field['settings']['default_image']) ? $prior_field['settings']['default_image']['fid'] : $prior_field['settings']['default_image']);
449

    
450
  $file_new = $fid_new ? file_load($fid_new) : FALSE;
451

    
452
  if ($fid_new != $fid_old) {
453

    
454
    // Is there a new file?
455
    if ($file_new) {
456
      $file_new->status = FILE_STATUS_PERMANENT;
457
      file_save($file_new);
458
      file_usage_add($file_new, 'image', 'default_image', $field['id']);
459
    }
460

    
461
    // Is there an old file?
462
    if ($fid_old && ($file_old = file_load($fid_old))) {
463
      file_usage_delete($file_old, 'image', 'default_image', $field['id']);
464
    }
465
  }
466

    
467
  // If the upload destination changed, then move the file.
468
  if ($file_new && (file_uri_scheme($file_new->uri) != $field['settings']['uri_scheme'])) {
469
    $directory = $field['settings']['uri_scheme'] . '://default_images/';
470
    file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
471
    file_move($file_new, $directory . $file_new->filename);
472
  }
473
}
474

    
475
/**
476
 * Implements hook_field_delete_instance().
477
 */
478
function image_field_delete_instance($instance) {
479
  // Only act on image fields.
480
  $field = field_read_field($instance['field_name']);
481
  if ($field['type'] != 'image') {
482
    return;
483
  }
484

    
485
  // The value of a managed_file element can be an array if the #extended
486
  // property is set to TRUE.
487
  $fid = $instance['settings']['default_image'];
488
  if (is_array($fid)) {
489
    $fid = $fid['fid'];
490
  }
491

    
492
  // Remove the default image when the instance is deleted.
493
  if ($fid && ($file = file_load($fid))) {
494
    file_usage_delete($file, 'image', 'default_image', $instance['id']);
495
  }
496
}
497

    
498
/**
499
 * Implements hook_field_update_instance().
500
 */
501
function image_field_update_instance($instance, $prior_instance) {
502
  // Only act on image fields.
503
  $field = field_read_field($instance['field_name']);
504
  if ($field['type'] != 'image') {
505
    return;
506
  }
507

    
508
  // The value of a managed_file element can be an array if the #extended
509
  // property is set to TRUE.
510
  $fid_new = $instance['settings']['default_image'];
511
  if (is_array($fid_new)) {
512
    $fid_new = $fid_new['fid'];
513
  }
514
  $fid_old = $prior_instance['settings']['default_image'];
515
  if (is_array($fid_old)) {
516
    $fid_old = $fid_old['fid'];
517
  }
518

    
519
  // If the old and new files do not match, update the default accordingly.
520
  $file_new = $fid_new ? file_load($fid_new) : FALSE;
521
  if ($fid_new != $fid_old) {
522
    // Save the new file, if present.
523
    if ($file_new) {
524
      $file_new->status = FILE_STATUS_PERMANENT;
525
      file_save($file_new);
526
      file_usage_add($file_new, 'image', 'default_image', $instance['id']);
527
    }
528
    // Delete the old file, if present.
529
    if ($fid_old && ($file_old = file_load($fid_old))) {
530
      file_usage_delete($file_old, 'image', 'default_image', $instance['id']);
531
    }
532
  }
533

    
534
  // If the upload destination changed, then move the file.
535
  if ($file_new && (file_uri_scheme($file_new->uri) != $field['settings']['uri_scheme'])) {
536
    $directory = $field['settings']['uri_scheme'] . '://default_images/';
537
    file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
538
    file_move($file_new, $directory . $file_new->filename);
539
  }
540
}
541

    
542
/**
543
 * Clears cached versions of a specific file in all styles.
544
 *
545
 * @param $path
546
 *   The Drupal file path to the original image.
547
 */
548
function image_path_flush($path) {
549
  $styles = image_styles();
550
  foreach ($styles as $style) {
551
    $image_path = image_style_path($style['name'], $path);
552
    if (file_exists($image_path)) {
553
      file_unmanaged_delete($image_path);
554
    }
555
  }
556
}
557

    
558
/**
559
 * Gets an array of all styles and their settings.
560
 *
561
 * @return
562
 *   An array of styles keyed by the image style ID (isid).
563
 * @see image_style_load()
564
 */
565
function image_styles() {
566
  $styles = &drupal_static(__FUNCTION__);
567

    
568
  // Grab from cache or build the array.
569
  if (!isset($styles)) {
570
    if ($cache = cache_get('image_styles', 'cache')) {
571
      $styles = $cache->data;
572
    }
573
    else {
574
      $styles = array();
575

    
576
      // Select the module-defined styles.
577
      foreach (module_implements('image_default_styles') as $module) {
578
        $module_styles = module_invoke($module, 'image_default_styles');
579
        foreach ($module_styles as $style_name => $style) {
580
          $style['name'] = $style_name;
581
          $style['label'] = empty($style['label']) ? $style_name : $style['label'];
582
          $style['module'] = $module;
583
          $style['storage'] = IMAGE_STORAGE_DEFAULT;
584
          foreach ($style['effects'] as $key => $effect) {
585
            $definition = image_effect_definition_load($effect['name']);
586
            $effect = array_merge($definition, $effect);
587
            $style['effects'][$key] = $effect;
588
          }
589
          $styles[$style_name] = $style;
590
        }
591
      }
592

    
593
      // Select all the user-defined styles.
594
      $user_styles = db_select('image_styles', NULL, array('fetch' => PDO::FETCH_ASSOC))
595
        ->fields('image_styles')
596
        ->orderBy('name')
597
        ->execute()
598
        ->fetchAllAssoc('name', PDO::FETCH_ASSOC);
599

    
600
      // Allow the user styles to override the module styles.
601
      foreach ($user_styles as $style_name => $style) {
602
        $style['module'] = NULL;
603
        $style['storage'] = IMAGE_STORAGE_NORMAL;
604
        $style['effects'] = image_style_effects($style);
605
        if (isset($styles[$style_name]['module'])) {
606
          $style['module'] = $styles[$style_name]['module'];
607
          $style['storage'] = IMAGE_STORAGE_OVERRIDE;
608
        }
609
        $styles[$style_name] = $style;
610
      }
611

    
612
      drupal_alter('image_styles', $styles);
613
      cache_set('image_styles', $styles);
614
    }
615
  }
616

    
617
  return $styles;
618
}
619

    
620
/**
621
 * Loads a style by style name or ID.
622
 *
623
 * May be used as a loader for menu items.
624
 *
625
 * @param $name
626
 *   The name of the style.
627
 * @param $isid
628
 *   Optional. The numeric id of a style if the name is not known.
629
 * @param $include
630
 *   If set, this loader will restrict to a specific type of image style, may be
631
 *   one of the defined Image style storage constants.
632
 *
633
 * @return
634
 *   An image style array containing the following keys:
635
 *   - "isid": The unique image style ID.
636
 *   - "name": The unique image style name.
637
 *   - "effects": An array of image effects within this image style.
638
 *   If the image style name or ID is not valid, an empty array is returned.
639
 * @see image_effect_load()
640
 */
641
function image_style_load($name = NULL, $isid = NULL, $include = NULL) {
642
  $styles = image_styles();
643

    
644
  // If retrieving by name.
645
  if (isset($name) && isset($styles[$name])) {
646
    $style = $styles[$name];
647
  }
648

    
649
  // If retrieving by image style id.
650
  if (!isset($name) && isset($isid)) {
651
    foreach ($styles as $name => $database_style) {
652
      if (isset($database_style['isid']) && $database_style['isid'] == $isid) {
653
        $style = $database_style;
654
        break;
655
      }
656
    }
657
  }
658

    
659
  // Restrict to the specific type of flag. This bitwise operation basically
660
  // states "if the storage is X, then allow".
661
  if (isset($style) && (!isset($include) || ($style['storage'] & (int) $include))) {
662
    return $style;
663
  }
664

    
665
  // Otherwise the style was not found.
666
  return FALSE;
667
}
668

    
669
/**
670
 * Saves an image style.
671
 *
672
 * @param array $style
673
 *   An image style array containing:
674
 *   - name: A unique name for the style.
675
 *   - isid: (optional) An image style ID.
676
 *
677
 * @return array
678
 *   An image style array containing:
679
 *   - name: An unique name for the style.
680
 *   - old_name: The original name for the style.
681
 *   - isid: An image style ID.
682
 *   - is_new: TRUE if this is a new style, and FALSE if it is an existing
683
 *     style.
684
 */
685
function image_style_save($style) {
686
  if (isset($style['isid']) && is_numeric($style['isid'])) {
687
    // Load the existing style to make sure we account for renamed styles.
688
    $old_style = image_style_load(NULL, $style['isid']);
689
    image_style_flush($old_style);
690
    drupal_write_record('image_styles', $style, 'isid');
691
    if ($old_style['name'] != $style['name']) {
692
      $style['old_name'] = $old_style['name'];
693
    }
694
  }
695
  else {
696
    // Add a default label when not given.
697
    if (empty($style['label'])) {
698
      $style['label'] = $style['name'];
699
    }
700
    drupal_write_record('image_styles', $style);
701
    $style['is_new'] = TRUE;
702
  }
703

    
704
  // Let other modules update as necessary on save.
705
  module_invoke_all('image_style_save', $style);
706

    
707
  // Clear all caches and flush.
708
  image_style_flush($style);
709

    
710
  return $style;
711
}
712

    
713
/**
714
 * Deletes an image style.
715
 *
716
 * @param $style
717
 *   An image style array.
718
 * @param $replacement_style_name
719
 *   (optional) When deleting a style, specify a replacement style name so
720
 *   that existing settings (if any) may be converted to a new style.
721
 *
722
 * @return
723
 *   TRUE on success.
724
 */
725
function image_style_delete($style, $replacement_style_name = '') {
726
  image_style_flush($style);
727

    
728
  db_delete('image_effects')->condition('isid', $style['isid'])->execute();
729
  db_delete('image_styles')->condition('isid', $style['isid'])->execute();
730

    
731
  // Let other modules update as necessary on save.
732
  $style['old_name'] = $style['name'];
733
  $style['name'] = $replacement_style_name;
734
  module_invoke_all('image_style_delete', $style);
735

    
736
  return TRUE;
737
}
738

    
739
/**
740
 * Loads all the effects for an image style.
741
 *
742
 * @param array $style
743
 *   An image style array containing:
744
 *   - isid: The unique image style ID that contains this image effect.
745
 *
746
 * @return array
747
 *   An array of image effects associated with specified image style in the
748
 *   format array('isid' => array()), or an empty array if the specified style
749
 *   has no effects.
750
 * @see image_effects()
751
 */
752
function image_style_effects($style) {
753
  $effects = image_effects();
754
  $style_effects = array();
755
  foreach ($effects as $effect) {
756
    if ($style['isid'] == $effect['isid']) {
757
      $style_effects[$effect['ieid']] = $effect;
758
    }
759
  }
760

    
761
  return $style_effects;
762
}
763

    
764
/**
765
 * Gets an array of image styles suitable for using as select list options.
766
 *
767
 * @param $include_empty
768
 *   If TRUE a <none> option will be inserted in the options array.
769
 * @param $output
770
 *   Optional flag determining how the options will be sanitized on output.
771
 *   Leave this at the default (CHECK_PLAIN) if you are using the output of
772
 *   this function directly in an HTML context, such as for checkbox or radio
773
 *   button labels, and do not plan to sanitize it on your own. If using the
774
 *   output of this function as select list options (its primary use case), you
775
 *   should instead set this flag to PASS_THROUGH to avoid double-escaping of
776
 *   the output (the form API sanitizes select list options by default).
777
 *
778
 * @return
779
 *   Array of image styles with the machine name as key and the label as value.
780
 */
781
function image_style_options($include_empty = TRUE, $output = CHECK_PLAIN) {
782
  $styles = image_styles();
783
  $options = array();
784
  if ($include_empty && !empty($styles)) {
785
    $options[''] = t('<none>');
786
  }
787
  foreach ($styles as $name => $style) {
788
    $options[$name] = ($output == PASS_THROUGH) ? $style['label'] : check_plain($style['label']);
789
  }
790

    
791
  if (empty($options)) {
792
    $options[''] = t('No defined styles');
793
  }
794
  return $options;
795
}
796

    
797
/**
798
 * Page callback: Generates a derivative, given a style and image path.
799
 *
800
 * After generating an image, transfer it to the requesting agent.
801
 *
802
 * @param $style
803
 *   The image style
804
 * @param $scheme
805
 *   The file scheme, for example 'public' for public files.
806
 */
807
function image_style_deliver($style, $scheme) {
808
  $args = func_get_args();
809
  array_shift($args);
810
  array_shift($args);
811
  $target = implode('/', $args);
812

    
813
  // Check that the style is defined, the scheme is valid, and the image
814
  // derivative token is valid. (Sites which require image derivatives to be
815
  // generated without a token can set the 'image_allow_insecure_derivatives'
816
  // variable to TRUE to bypass the latter check, but this will increase the
817
  // site's vulnerability to denial-of-service attacks. To prevent this
818
  // variable from leaving the site vulnerable to the most serious attacks, a
819
  // token is always required when a derivative of a derivative is requested.)
820
  $valid = !empty($style) && file_stream_wrapper_valid_scheme($scheme);
821
  if (!variable_get('image_allow_insecure_derivatives', FALSE) || strpos(ltrim($target, '\/'), 'styles/') === 0) {
822
    $valid = $valid && isset($_GET[IMAGE_DERIVATIVE_TOKEN]) && $_GET[IMAGE_DERIVATIVE_TOKEN] === image_style_path_token($style['name'], $scheme . '://' . $target);
823
  }
824
  if (!$valid) {
825
    return MENU_ACCESS_DENIED;
826
  }
827

    
828
  $image_uri = $scheme . '://' . $target;
829
  $derivative_uri = image_style_path($style['name'], $image_uri);
830

    
831
  // If using the private scheme, let other modules provide headers and
832
  // control access to the file.
833
  if ($scheme == 'private') {
834
    if (file_exists($derivative_uri)) {
835
      file_download($scheme, file_uri_target($derivative_uri));
836
    }
837
    else {
838
      $headers = file_download_headers($image_uri);
839
      if (empty($headers)) {
840
        return MENU_ACCESS_DENIED;
841
      }
842
      if (count($headers)) {
843
        foreach ($headers as $name => $value) {
844
          drupal_add_http_header($name, $value);
845
        }
846
      }
847
    }
848
  }
849

    
850
  // Confirm that the original source image exists before trying to process it.
851
  if (!is_file($image_uri)) {
852
    watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.',  array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri));
853
    return MENU_NOT_FOUND;
854
  }
855

    
856
  // Don't start generating the image if the derivative already exists or if
857
  // generation is in progress in another thread.
858
  $lock_name = 'image_style_deliver:' . $style['name'] . ':' . drupal_hash_base64($image_uri);
859
  if (!file_exists($derivative_uri)) {
860
    $lock_acquired = lock_acquire($lock_name);
861
    if (!$lock_acquired) {
862
      // Tell client to retry again in 3 seconds. Currently no browsers are known
863
      // to support Retry-After.
864
      drupal_add_http_header('Status', '503 Service Unavailable');
865
      drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
866
      drupal_add_http_header('Retry-After', 3);
867
      print t('Image generation in progress. Try again shortly.');
868
      drupal_exit();
869
    }
870
  }
871

    
872
  // Try to generate the image, unless another thread just did it while we were
873
  // acquiring the lock.
874
  $success = file_exists($derivative_uri) || image_style_create_derivative($style, $image_uri, $derivative_uri);
875

    
876
  if (!empty($lock_acquired)) {
877
    lock_release($lock_name);
878
  }
879

    
880
  if ($success) {
881
    $image = image_load($derivative_uri);
882
    file_transfer($image->source, array('Content-Type' => $image->info['mime_type'], 'Content-Length' => $image->info['file_size']));
883
  }
884
  else {
885
    watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri));
886
    drupal_add_http_header('Status', '500 Internal Server Error');
887
    drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
888
    print t('Error generating image.');
889
    drupal_exit();
890
  }
891
}
892

    
893
/**
894
 * Creates a new image derivative based on an image style.
895
 *
896
 * Generates an image derivative by creating the destination folder (if it does
897
 * not already exist), applying all image effects defined in $style['effects'],
898
 * and saving a cached version of the resulting image.
899
 *
900
 * @param $style
901
 *   An image style array.
902
 * @param $source
903
 *   Path of the source file.
904
 * @param $destination
905
 *   Path or URI of the destination file.
906
 *
907
 * @return
908
 *   TRUE if an image derivative was generated, or FALSE if the image derivative
909
 *   could not be generated.
910
 *
911
 * @see image_style_load()
912
 */
913
function image_style_create_derivative($style, $source, $destination) {
914
  // If the source file doesn't exist, return FALSE without creating folders.
915
  if (!$image = image_load($source)) {
916
    return FALSE;
917
  }
918

    
919
  // Get the folder for the final location of this style.
920
  $directory = drupal_dirname($destination);
921

    
922
  // Build the destination folder tree if it doesn't already exist.
923
  if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
924
    watchdog('image', 'Failed to create style directory: %directory', array('%directory' => $directory), WATCHDOG_ERROR);
925
    return FALSE;
926
  }
927

    
928
  foreach ($style['effects'] as $effect) {
929
    image_effect_apply($image, $effect);
930
  }
931

    
932
  if (!image_save($image, $destination)) {
933
    if (file_exists($destination)) {
934
      watchdog('image', 'Cached image file %destination already exists. There may be an issue with your rewrite configuration.', array('%destination' => $destination), WATCHDOG_ERROR);
935
    }
936
    return FALSE;
937
  }
938

    
939
  return TRUE;
940
}
941

    
942
/**
943
 * Determines the dimensions of the styled image.
944
 *
945
 * Applies all of an image style's effects to $dimensions.
946
 *
947
 * @param $style_name
948
 *   The name of the style to be applied.
949
 * @param $dimensions
950
 *   Dimensions to be modified - an array with components width and height, in
951
 *   pixels.
952
 */
953
function image_style_transform_dimensions($style_name, array &$dimensions) {
954
  module_load_include('inc', 'image', 'image.effects');
955
  $style = image_style_load($style_name);
956

    
957
  if (!is_array($style)) {
958
    return;
959
  }
960

    
961
  foreach ($style['effects'] as $effect) {
962
    if (isset($effect['dimensions passthrough'])) {
963
      continue;
964
    }
965

    
966
    if (isset($effect['dimensions callback'])) {
967
      $effect['dimensions callback']($dimensions, $effect['data']);
968
    }
969
    else {
970
      $dimensions['width'] = $dimensions['height'] = NULL;
971
    }
972
  }
973
}
974

    
975
/**
976
 * Flushes cached media for a style.
977
 *
978
 * @param $style
979
 *   An image style array.
980
 */
981
function image_style_flush($style) {
982
  // Delete the style directory in each registered wrapper.
983
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
984
  foreach ($wrappers as $wrapper => $wrapper_data) {
985
    if (file_exists($directory = $wrapper . '://styles/' . $style['name'])) {
986
      file_unmanaged_delete_recursive($directory);
987
    }
988
  }
989

    
990
  // Let other modules update as necessary on flush.
991
  module_invoke_all('image_style_flush', $style);
992

    
993
  // Clear image style and effect caches.
994
  cache_clear_all('image_styles', 'cache');
995
  cache_clear_all('image_effects:', 'cache', TRUE);
996
  drupal_static_reset('image_styles');
997
  drupal_static_reset('image_effects');
998

    
999
  // Clear field caches so that formatters may be added for this style.
1000
  field_info_cache_clear();
1001
  drupal_theme_rebuild();
1002

    
1003
  // Clear page caches when flushing.
1004
  if (module_exists('block')) {
1005
    cache_clear_all('*', 'cache_block', TRUE);
1006
  }
1007
  cache_clear_all('*', 'cache_page', TRUE);
1008
}
1009

    
1010
/**
1011
 * Returns the URL for an image derivative given a style and image path.
1012
 *
1013
 * @param $style_name
1014
 *   The name of the style to be used with this image.
1015
 * @param $path
1016
 *   The path to the image.
1017
 *
1018
 * @return
1019
 *   The absolute URL where a style image can be downloaded, suitable for use
1020
 *   in an <img> tag. Requesting the URL will cause the image to be created.
1021
 * @see image_style_deliver()
1022
 */
1023
function image_style_url($style_name, $path) {
1024
  $uri = image_style_path($style_name, $path);
1025

    
1026
  // The passed-in $path variable can be either a relative path or a full URI.
1027
  $original_uri = file_uri_scheme($path) ? file_stream_wrapper_uri_normalize($path) : file_build_uri($path);
1028

    
1029
  // The token query is added even if the 'image_allow_insecure_derivatives'
1030
  // variable is TRUE, so that the emitted links remain valid if it is changed
1031
  // back to the default FALSE.
1032
  // However, sites which need to prevent the token query from being emitted at
1033
  // all can additionally set the 'image_suppress_itok_output' variable to TRUE
1034
  // to achieve that (if both are set, the security token will neither be
1035
  // emitted in the image derivative URL nor checked for in
1036
  // image_style_deliver()).
1037
  $token_query = array();
1038
  if (!variable_get('image_suppress_itok_output', FALSE)) {
1039
    $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $original_uri));
1040
  }
1041

    
1042
  // If not using clean URLs, the image derivative callback is only available
1043
  // with the query string. If the file does not exist, use url() to ensure
1044
  // that it is included. Once the file exists it's fine to fall back to the
1045
  // actual file path, this avoids bootstrapping PHP once the files are built.
1046
  if (!variable_get('clean_url') && file_uri_scheme($uri) == 'public' && !file_exists($uri)) {
1047
    $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath();
1048
    return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query));
1049
  }
1050

    
1051
  $file_url = file_create_url($uri);
1052
  // Append the query string with the token, if necessary.
1053
  if ($token_query) {
1054
    $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
1055
  }
1056

    
1057
  return $file_url;
1058
}
1059

    
1060
/**
1061
 * Generates a token to protect an image style derivative.
1062
 *
1063
 * This prevents unauthorized generation of an image style derivative,
1064
 * which can be costly both in CPU time and disk space.
1065
 *
1066
 * @param $style_name
1067
 *   The name of the image style.
1068
 * @param $uri
1069
 *   The URI of the image for this style, for example as returned by
1070
 *   image_style_path().
1071
 *
1072
 * @return
1073
 *   An eight-character token which can be used to protect image style
1074
 *   derivatives against denial-of-service attacks.
1075
 */
1076
function image_style_path_token($style_name, $uri) {
1077
  // Return the first eight characters.
1078
  return substr(drupal_hmac_base64($style_name . ':' . $uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
1079
}
1080

    
1081
/**
1082
 * Returns the URI of an image when using a style.
1083
 *
1084
 * The path returned by this function may not exist. The default generation
1085
 * method only creates images when they are requested by a user's browser.
1086
 *
1087
 * @param $style_name
1088
 *   The name of the style to be used with this image.
1089
 * @param $uri
1090
 *   The URI or path to the image.
1091
 *
1092
 * @return
1093
 *   The URI to an image style image.
1094
 * @see image_style_url()
1095
 */
1096
function image_style_path($style_name, $uri) {
1097
  $scheme = file_uri_scheme($uri);
1098
  if ($scheme) {
1099
    $path = file_uri_target($uri);
1100
  }
1101
  else {
1102
    $path = $uri;
1103
    $scheme = file_default_scheme();
1104
  }
1105
  return $scheme . '://styles/' . $style_name . '/' . $scheme . '/' . $path;
1106
}
1107

    
1108
/**
1109
 * Saves a default image style to the database.
1110
 *
1111
 * @param style
1112
 *   An image style array provided by a module.
1113
 *
1114
 * @return
1115
 *   An image style array. The returned style array will include the new 'isid'
1116
 *   assigned to the style.
1117
 */
1118
function image_default_style_save($style) {
1119
  $style = image_style_save($style);
1120
  $effects = array();
1121
  foreach ($style['effects'] as $effect) {
1122
    $effect['isid'] = $style['isid'];
1123
    $effect = image_effect_save($effect);
1124
    $effects[$effect['ieid']] = $effect;
1125
  }
1126
  $style['effects'] = $effects;
1127
  return $style;
1128
}
1129

    
1130
/**
1131
 * Reverts the changes made by users to a default image style.
1132
 *
1133
 * @param style
1134
 *   An image style array.
1135
 * @return
1136
 *   Boolean TRUE if the operation succeeded.
1137
 */
1138
function image_default_style_revert($style) {
1139
  image_style_flush($style);
1140

    
1141
  db_delete('image_effects')->condition('isid', $style['isid'])->execute();
1142
  db_delete('image_styles')->condition('isid', $style['isid'])->execute();
1143

    
1144
  return TRUE;
1145
}
1146

    
1147
/**
1148
 * Returns a set of image effects.
1149
 *
1150
 * These image effects are exposed by modules implementing
1151
 * hook_image_effect_info().
1152
 *
1153
 * @return
1154
 *   An array of image effects to be used when transforming images.
1155
 * @see hook_image_effect_info()
1156
 * @see image_effect_definition_load()
1157
 */
1158
function image_effect_definitions() {
1159
  global $language;
1160

    
1161
  // hook_image_effect_info() includes translated strings, so each language is
1162
  // cached separately.
1163
  $langcode = $language->language;
1164

    
1165
  $effects = &drupal_static(__FUNCTION__);
1166

    
1167
  if (!isset($effects)) {
1168
    if ($cache = cache_get("image_effects:$langcode")) {
1169
      $effects = $cache->data;
1170
    }
1171
    else {
1172
      $effects = array();
1173
      include_once DRUPAL_ROOT . '/modules/image/image.effects.inc';
1174
      foreach (module_implements('image_effect_info') as $module) {
1175
        foreach (module_invoke($module, 'image_effect_info') as $name => $effect) {
1176
          // Ensure the current toolkit supports the effect.
1177
          $effect['module'] = $module;
1178
          $effect['name'] = $name;
1179
          $effect['data'] = isset($effect['data']) ? $effect['data'] : array();
1180
          $effects[$name] = $effect;
1181
        }
1182
      }
1183
      uasort($effects, '_image_effect_definitions_sort');
1184
      drupal_alter('image_effect_info', $effects);
1185
      cache_set("image_effects:$langcode", $effects);
1186
    }
1187
  }
1188

    
1189
  return $effects;
1190
}
1191

    
1192
/**
1193
 * Loads the definition for an image effect.
1194
 *
1195
 * The effect definition is a set of core properties for an image effect, not
1196
 * containing any user-settings. The definition defines various functions to
1197
 * call when configuring or executing an image effect. This loader is mostly for
1198
 * internal use within image.module. Use image_effect_load() or
1199
 * image_style_load() to get image effects that contain configuration.
1200
 *
1201
 * @param $effect
1202
 *   The name of the effect definition to load.
1203
 * @param $style
1204
 *   An image style array to which this effect will be added.
1205
 *
1206
 * @return
1207
 *   An array containing the image effect definition with the following keys:
1208
 *   - "effect": The unique name for the effect being performed. Usually prefixed
1209
 *     with the name of the module providing the effect.
1210
 *   - "module": The module providing the effect.
1211
 *   - "help": A description of the effect.
1212
 *   - "function": The name of the function that will execute the effect.
1213
 *   - "form": (optional) The name of a function to configure the effect.
1214
 *   - "summary": (optional) The name of a theme function that will display a
1215
 *     one-line summary of the effect. Does not include the "theme_" prefix.
1216
 */
1217
function image_effect_definition_load($effect, $style_name = NULL) {
1218
  $definitions = image_effect_definitions();
1219

    
1220
  // If a style is specified, do not allow loading of default style
1221
  // effects.
1222
  if (isset($style_name)) {
1223
    $style = image_style_load($style_name, NULL);
1224
    if ($style['storage'] == IMAGE_STORAGE_DEFAULT) {
1225
      return FALSE;
1226
    }
1227
  }
1228

    
1229
  return isset($definitions[$effect]) ? $definitions[$effect] : FALSE;
1230
}
1231

    
1232
/**
1233
 * Loads all image effects from the database.
1234
 *
1235
 * @return
1236
 *   An array of all image effects.
1237
 * @see image_effect_load()
1238
 */
1239
function image_effects() {
1240
  $effects = &drupal_static(__FUNCTION__);
1241

    
1242
  if (!isset($effects)) {
1243
    $effects = array();
1244

    
1245
    // Add database image effects.
1246
    $result = db_select('image_effects', NULL, array('fetch' => PDO::FETCH_ASSOC))
1247
      ->fields('image_effects')
1248
      ->orderBy('image_effects.weight', 'ASC')
1249
      ->execute();
1250
    foreach ($result as $effect) {
1251
      $effect['data'] = unserialize($effect['data']);
1252
      $definition = image_effect_definition_load($effect['name']);
1253
      // Do not load image effects whose definition cannot be found.
1254
      if ($definition) {
1255
        $effect = array_merge($definition, $effect);
1256
        $effects[$effect['ieid']] = $effect;
1257
      }
1258
    }
1259
  }
1260

    
1261
  return $effects;
1262
}
1263

    
1264
/**
1265
 * Loads a single image effect.
1266
 *
1267
 * @param $ieid
1268
 *   The image effect ID.
1269
 * @param $style_name
1270
 *   The image style name.
1271
 * @param $include
1272
 *   If set, this loader will restrict to a specific type of image style, may be
1273
 *   one of the defined Image style storage constants.
1274
 *
1275
 * @return
1276
 *   An image effect array, consisting of the following keys:
1277
 *   - "ieid": The unique image effect ID.
1278
 *   - "isid": The unique image style ID that contains this image effect.
1279
 *   - "weight": The weight of this image effect within the image style.
1280
 *   - "name": The name of the effect definition that powers this image effect.
1281
 *   - "data": An array of configuration options for this image effect.
1282
 *   Besides these keys, the entirety of the image definition is merged into
1283
 *   the image effect array. Returns FALSE if the specified effect cannot be
1284
 *   found.
1285
 * @see image_style_load()
1286
 * @see image_effect_definition_load()
1287
 */
1288
function image_effect_load($ieid, $style_name, $include = NULL) {
1289
  if (($style = image_style_load($style_name, NULL, $include)) && isset($style['effects'][$ieid])) {
1290
    return $style['effects'][$ieid];
1291
  }
1292
  return FALSE;
1293
}
1294

    
1295
/**
1296
 * Saves an image effect.
1297
 *
1298
 * @param $effect
1299
 *   An image effect array.
1300
 *
1301
 * @return
1302
 *   An image effect array. In the case of a new effect, 'ieid' will be set.
1303
 */
1304
function image_effect_save($effect) {
1305
  if (!empty($effect['ieid'])) {
1306
    drupal_write_record('image_effects', $effect, 'ieid');
1307
  }
1308
  else {
1309
    drupal_write_record('image_effects', $effect);
1310
  }
1311
  $style = image_style_load(NULL, $effect['isid']);
1312
  image_style_flush($style);
1313
  return $effect;
1314
}
1315

    
1316
/**
1317
 * Deletes an image effect.
1318
 *
1319
 * @param $effect
1320
 *   An image effect array.
1321
 */
1322
function image_effect_delete($effect) {
1323
  db_delete('image_effects')->condition('ieid', $effect['ieid'])->execute();
1324
  $style = image_style_load(NULL, $effect['isid']);
1325
  image_style_flush($style);
1326
}
1327

    
1328
/**
1329
 * Applies an image effect to the image object.
1330
 *
1331
 * @param $image
1332
 *   An image object returned by image_load().
1333
 * @param $effect
1334
 *   An image effect array.
1335
 *
1336
 * @return
1337
 *   TRUE on success. FALSE if unable to perform the image effect on the image.
1338
 */
1339
function image_effect_apply($image, $effect) {
1340
  module_load_include('inc', 'image', 'image.effects');
1341
  $function = $effect['effect callback'];
1342
  if (function_exists($function)) {
1343
    return $function($image, $effect['data']);
1344
  }
1345
  return FALSE;
1346
}
1347

    
1348
/**
1349
 * Returns HTML for an image using a specific image style.
1350
 *
1351
 * @param $variables
1352
 *   An associative array containing:
1353
 *   - style_name: The name of the style to be used to alter the original image.
1354
 *   - path: The path of the image file relative to the Drupal files directory.
1355
 *     This function does not work with images outside the files directory nor
1356
 *     with remotely hosted images. This should be in a format such as
1357
 *     'images/image.jpg', or using a stream wrapper such as
1358
 *     'public://images/image.jpg'.
1359
 *   - width: The width of the source image (if known).
1360
 *   - height: The height of the source image (if known).
1361
 *   - alt: The alternative text for text-based browsers.
1362
 *   - title: The title text is displayed when the image is hovered in some
1363
 *     popular browsers.
1364
 *   - attributes: Associative array of attributes to be placed in the img tag.
1365
 *
1366
 * @ingroup themeable
1367
 */
1368
function theme_image_style($variables) {
1369
  // Determine the dimensions of the styled image.
1370
  $dimensions = array(
1371
    'width' => $variables['width'],
1372
    'height' => $variables['height'],
1373
  );
1374

    
1375
  image_style_transform_dimensions($variables['style_name'], $dimensions);
1376

    
1377
  $variables['width'] = $dimensions['width'];
1378
  $variables['height'] = $dimensions['height'];
1379

    
1380
  // Determine the URL for the styled image.
1381
  $variables['path'] = image_style_url($variables['style_name'], $variables['path']);
1382
  return theme('image', $variables);
1383
}
1384

    
1385
/**
1386
 * Accepts a keyword (center, top, left, etc) and returns it as a pixel offset.
1387
 *
1388
 * @param $value
1389
 * @param $current_pixels
1390
 * @param $new_pixels
1391
 */
1392
function image_filter_keyword($value, $current_pixels, $new_pixels) {
1393
  switch ($value) {
1394
    case 'top':
1395
    case 'left':
1396
      return 0;
1397

    
1398
    case 'bottom':
1399
    case 'right':
1400
      return $current_pixels - $new_pixels;
1401

    
1402
    case 'center':
1403
      return $current_pixels / 2 - $new_pixels / 2;
1404
  }
1405
  return $value;
1406
}
1407

    
1408
/**
1409
 * Internal function for sorting image effect definitions through uasort().
1410
 *
1411
 * @see image_effect_definitions()
1412
 */
1413
function _image_effect_definitions_sort($a, $b) {
1414
  return strcasecmp($a['name'], $b['name']);
1415
}