image.test 57.6 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Tests for image.module.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 */

/**
 * TODO: Test the following functions.
 *
 * image.effects.inc:
 *   image_style_generate()
 *   image_style_create_derivative()
 *
 * image.module:
 *   image_style_load()
 *   image_style_save()
 *   image_style_delete()
 *   image_style_options()
 *   image_style_flush()
 *   image_effect_definition_load()
 *   image_effect_load()
 *   image_effect_save()
 *   image_effect_delete()
 *   image_filter_keyword()
 */

28 29 30 31 32 33 34
/**
 * This class provides methods specifically for testing Image's field handling.
 */
class ImageFieldTestCase extends DrupalWebTestCase {
  protected $admin_user;

  function setUp() {
35 36 37 38 39 40 41 42 43 44 45 46 47 48
    $modules = func_get_args();
    if (isset($modules[0]) && is_array($modules[0])) {
      $modules = $modules[0];
    }
    $modules[] = 'node';
    $modules[] = 'image';
    parent::setUp($modules);

    // Create Basic page and Article node types.
    if ($this->profile != 'standard') {
      $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
      $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
    }

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
    $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles'));
    $this->drupalLogin($this->admin_user);
  }

  /**
   * Create a new image field.
   *
   * @param $name
   *   The name of the new field (all lowercase), exclude the "field_" prefix.
   * @param $type_name
   *   The node type that this field will be added to.
   * @param $field_settings
   *   A list of field settings that will be added to the defaults.
   * @param $instance_settings
   *   A list of instance settings that will be added to the instance defaults.
   * @param $widget_settings
   *   A list of widget settings that will be added to the widget defaults.
   */
  function createImageField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) {
    $field = array(
      'field_name' => $name,
      'type' => 'image',
      'settings' => array(),
      'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1,
    );
    $field['settings'] = array_merge($field['settings'], $field_settings);
    field_create_field($field);

    $instance = array(
      'field_name' => $field['field_name'],
      'entity_type' => 'node',
      'label' => $name,
      'bundle' => $type_name,
      'required' => !empty($instance_settings['required']),
      'settings' => array(),
      'widget' => array(
        'type' => 'image_image',
        'settings' => array(),
      ),
    );
    $instance['settings'] = array_merge($instance['settings'], $instance_settings);
    $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);
    return field_create_instance($instance);
  }

  /**
   * Upload an image to a node.
   *
   * @param $image
   *   A file object representing the image to upload.
   * @param $field_name
   *   Name of the image field the image should be attached to.
   * @param $type
   *   The type of node to create.
   */
  function uploadNodeImage($image, $field_name, $type) {
    $edit = array(
      'title' => $this->randomName(),
    );
108
    $edit['files[' . $field_name . '_' . LANGUAGE_NOT_SPECIFIED . '_0]'] = drupal_realpath($image->uri);
109 110 111 112 113 114 115 116 117
    $this->drupalPost('node/add/' . $type, $edit, t('Save'));

    // Retrieve ID of the newly created node from the current URL.
    $matches = array();
    preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
    return isset($matches[1]) ? $matches[1] : FALSE;
  }
}

118 119 120 121 122
/**
 * Tests the functions for generating paths and URLs for image styles.
 */
class ImageStylesPathAndUrlUnitTest extends DrupalWebTestCase {
  protected $style_name;
123 124
  protected $image_info;
  protected $image_filepath;
125

126
  public static function getInfo() {
127
    return array(
128 129 130
      'name' => 'Image styles path and URL functions',
      'description' => 'Tests functions for generating paths and URLs to image styles.',
      'group' => 'Image',
131 132 133 134
    );
  }

  function setUp() {
135
    parent::setUp(array('image', 'image_module_test'));
136 137

    $this->style_name = 'style_foo';
138
    image_style_save(array('name' => $this->style_name));
139 140 141 142 143 144
  }

  /**
   * Test image_style_path().
   */
  function testImageStylePath() {
145 146 147
    $scheme = 'public';
    $actual = image_style_path($this->style_name, "$scheme://foo/bar.gif");
    $expected = "$scheme://styles/" . $this->style_name . "/$scheme/foo/bar.gif";
148
    $this->assertEqual($actual, $expected, t('Got the path for a file URI.'));
149 150

    $actual = image_style_path($this->style_name, 'foo/bar.gif');
151
    $expected = "$scheme://styles/" . $this->style_name . "/$scheme/foo/bar.gif";
152
    $this->assertEqual($actual, $expected, t('Got the path for a relative file path.'));
153 154 155 156 157 158 159 160 161 162 163 164 165 166
  }

  /**
   * Test image_style_url() with a file using the "public://" scheme.
   */
  function testImageStyleUrlAndPathPublic() {
    $this->_testImageStyleUrlAndPath('public');
  }

  /**
   * Test image_style_url() with a file using the "private://" scheme.
   */
  function testImageStyleUrlAndPathPrivate() {
    $this->_testImageStyleUrlAndPath('private');
167 168
  }

169 170 171 172 173 174 175 176 177 178 179 180 181 182
  /**
   * Test image_style_url() with the "public://" scheme and unclean URLs.
   */
   function testImageStylUrlAndPathPublicUnclean() {
     $this->_testImageStyleUrlAndPath('public', FALSE);
   }

  /**
   * Test image_style_url() with the "private://" schema and unclean URLs.
   */
  function testImageStyleUrlAndPathPrivateUnclean() {
    $this->_testImageStyleUrlAndPath('private', FALSE);
  }

183 184 185
  /**
   * Test image_style_url().
   */
186
  function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE) {
187 188 189
    $script_path_original = $GLOBALS['script_path'];
    $GLOBALS['script_path'] = $clean_url ? '' : 'index.php/';

190
    // Make the default scheme neither "public" nor "private" to verify the
191
    // functions work for other than the default scheme.
192 193 194
    variable_set('file_default_scheme', 'temporary');

    // Create the directories for the styles.
195 196
    $directory = $scheme . '://styles/' . $this->style_name;
    $status = file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
197
    $this->assertNotIdentical(FALSE, $status, t('Created the directory for the generated images for the test style.'));
198 199

    // Create a working copy of the file.
200 201
    $files = $this->drupalGetTestFiles('image');
    $file = reset($files);
202 203
    $image_info = image_get_info($file->uri);
    $original_uri = file_unmanaged_copy($file->uri, $scheme . '://', FILE_EXISTS_RENAME);
204 205 206
    // Let the image_module_test module know about this file, so it can claim
    // ownership in hook_file_download().
    variable_set('image_module_test_file_download', $original_uri);
207
    $this->assertNotIdentical(FALSE, $original_uri, t('Created the generated image file.'));
208

209
    // Get the URL of a file that has not been generated and try to create it.
210
    $generated_uri = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/'. drupal_basename($original_uri);
211
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
212
    $generate_url = image_style_url($this->style_name, $original_uri);
213

214 215
    if ($GLOBALS['script_path']) {
      $this->assertTrue(strpos($generate_url, $GLOBALS['script_path']) !== FALSE, 'When using non-clean URLS, the system path contains the script name.');
216 217
    }

218
    // Fetch the URL that generates the file.
219 220 221
    $this->drupalGet($generate_url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));
222
    $this->assertRaw(file_get_contents($generated_uri), t('URL returns expected file.'));
223
    $generated_image_info = image_get_info($generated_uri);
224 225
    $this->assertEqual($this->drupalGetHeader('Content-Type'), $generated_image_info['mime_type'], t('Expected Content-Type was reported.'));
    $this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], t('Expected Content-Length was reported.'));
226 227 228
    if ($scheme == 'private') {
      $this->assertEqual($this->drupalGetHeader('X-Image-Owned-By'), 'image_module_test', t('Expected custom header has been added.'));
    }
229 230

    $GLOBALS['script_path'] = $script_path_original;
231 232 233 234 235 236 237 238
  }
}

/**
 * Use the image_test.module's mock toolkit to ensure that the effects are
 * properly passing parameters to the image toolkit.
 */
class ImageEffectsUnitTest extends ImageToolkitTestCase {
239
  public static function getInfo() {
240
    return array(
241 242 243
      'name' => 'Image effects',
      'description' => 'Test that the image effects pass parameters to the toolkit correctly.',
      'group' => 'Image',
244 245 246 247 248 249 250 251 252 253 254 255
    );
  }

  function setUp() {
    parent::setUp('image_test');
    module_load_include('inc', 'image', 'image.effects');
  }

  /**
   * Test the image_resize_effect() function.
   */
  function testResizeEffect() {
256
    $this->assertTrue(image_resize_effect($this->image, array('width' => 1, 'height' => 2)), t('Function returned the expected value.'));
257 258 259 260
    $this->assertToolkitOperationsCalled(array('resize'));

    // Check the parameters.
    $calls = image_test_get_all_calls();
261 262
    $this->assertEqual($calls['resize'][0][1], 1, t('Width was passed correctly'));
    $this->assertEqual($calls['resize'][0][2], 2, t('Height was passed correctly'));
263 264 265 266 267 268 269
  }

  /**
   * Test the image_scale_effect() function.
   */
  function testScaleEffect() {
    // @todo: need to test upscaling.
270
    $this->assertTrue(image_scale_effect($this->image, array('width' => 10, 'height' => 10)), t('Function returned the expected value.'));
271 272 273 274
    $this->assertToolkitOperationsCalled(array('resize'));

    // Check the parameters.
    $calls = image_test_get_all_calls();
275 276
    $this->assertEqual($calls['resize'][0][1], 10, t('Width was passed correctly'));
    $this->assertEqual($calls['resize'][0][2], 5, t('Height was based off aspect ratio and passed correctly'));
277 278 279 280 281 282 283
  }

  /**
   * Test the image_crop_effect() function.
   */
  function testCropEffect() {
    // @todo should test the keyword offsets.
284
    $this->assertTrue(image_crop_effect($this->image, array('anchor' => 'top-1', 'width' => 3, 'height' => 4)), t('Function returned the expected value.'));
285 286 287 288
    $this->assertToolkitOperationsCalled(array('crop'));

    // Check the parameters.
    $calls = image_test_get_all_calls();
289 290 291 292
    $this->assertEqual($calls['crop'][0][1], 0, t('X was passed correctly'));
    $this->assertEqual($calls['crop'][0][2], 1, t('Y was passed correctly'));
    $this->assertEqual($calls['crop'][0][3], 3, t('Width was passed correctly'));
    $this->assertEqual($calls['crop'][0][4], 4, t('Height was passed correctly'));
293 294 295 296 297 298
  }

  /**
   * Test the image_scale_and_crop_effect() function.
   */
  function testScaleAndCropEffect() {
299
    $this->assertTrue(image_scale_and_crop_effect($this->image, array('width' => 5, 'height' => 10)), t('Function returned the expected value.'));
300 301 302 303
    $this->assertToolkitOperationsCalled(array('resize', 'crop'));

    // Check the parameters.
    $calls = image_test_get_all_calls();
304 305 306 307
    $this->assertEqual($calls['crop'][0][1], 7.5, t('X was computed and passed correctly'));
    $this->assertEqual($calls['crop'][0][2], 0, t('Y was computed and passed correctly'));
    $this->assertEqual($calls['crop'][0][3], 5, t('Width was computed and passed correctly'));
    $this->assertEqual($calls['crop'][0][4], 10, t('Height was computed and passed correctly'));
308 309 310 311 312 313
  }

  /**
   * Test the image_desaturate_effect() function.
   */
  function testDesaturateEffect() {
314
    $this->assertTrue(image_desaturate_effect($this->image, array()), t('Function returned the expected value.'));
315 316 317 318
    $this->assertToolkitOperationsCalled(array('desaturate'));

    // Check the parameters.
    $calls = image_test_get_all_calls();
319
    $this->assertEqual(count($calls['desaturate'][0]), 1, t('Only the image was passed.'));
320 321 322 323 324 325 326
  }

  /**
   * Test the image_rotate_effect() function.
   */
  function testRotateEffect() {
    // @todo: need to test with 'random' => TRUE
327
    $this->assertTrue(image_rotate_effect($this->image, array('degrees' => 90, 'bgcolor' => '#fff')), t('Function returned the expected value.'));
328 329 330 331
    $this->assertToolkitOperationsCalled(array('rotate'));

    // Check the parameters.
    $calls = image_test_get_all_calls();
332 333
    $this->assertEqual($calls['rotate'][0][1], 90, t('Degrees were passed correctly'));
    $this->assertEqual($calls['rotate'][0][2], 0xffffff, t('Background color was passed correctly'));
334 335
  }
}
336 337 338 339

/**
 * Tests creation, deletion, and editing of image styles and effects.
 */
340
class ImageAdminStylesUnitTest extends ImageFieldTestCase {
341

342
  public static function getInfo() {
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
    return array(
      'name' => 'Image styles and effects UI configuration',
      'description' => 'Tests creation, deletion, and editing of image styles and effects at the UI level.',
      'group' => 'Image',
    );
  }

  /**
   * Given an image style, generate an image.
   */
  function createSampleImage($style) {
    static $file_path;

    // First, we need to make sure we have an image in our testing
    // file directory. Copy over an image on the first run.
    if (!isset($file_path)) {
359 360
      $files = $this->drupalGetTestFiles('image');
      $file = reset($files);
361
      $file_path = file_unmanaged_copy($file->uri);
362 363 364 365 366 367 368 369 370
    }

    return image_style_url($style['name'], $file_path) ? $file_path : FALSE;
  }

  /**
   * Count the number of images currently create for a style.
   */
  function getImageCount($style) {
371
    return count(file_scan_directory('public://styles/' . $style['name'], '/.*/'));
372 373
  }

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
  /**
   * Test creating an image style with a numeric name and ensuring it can be
   * applied to an image.
   */
  function testNumericStyleName() {
    $style_name = rand();
    $edit = array(
      'name' => $style_name,
    );
    $this->drupalPost('admin/config/media/image-styles/add', $edit, t('Create new style'));
    $this->assertRaw(t('Style %name was created.', array('%name' => $style_name)), t('Image style successfully created.'));
    $options = image_style_options();
    $this->assertTrue(array_key_exists($style_name, $options), t('Array key %key exists.', array('%key' => $style_name)));
  }

389 390 391 392 393 394
  /**
   * General test to add a style, add/remove/edit effects to it, then delete it.
   */
  function testStyle() {
    // Setup a style to be created and effects to add to it.
    $style_name = strtolower($this->randomName(10));
395
    $style_path = 'admin/config/media/image-styles/edit/' . $style_name;
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
    $effect_edits = array(
      'image_resize' => array(
        'data[width]' => 100,
        'data[height]' => 101,
      ),
      'image_scale' => array(
        'data[width]' => 110,
        'data[height]' => 111,
        'data[upscale]' => 1,
      ),
      'image_scale_and_crop' => array(
        'data[width]' => 120,
        'data[height]' => 121,
      ),
      'image_crop' => array(
        'data[width]' => 130,
        'data[height]' => 131,
        'data[anchor]' => 'center-center',
      ),
      'image_desaturate' => array(
        // No options for desaturate.
      ),
      'image_rotate' => array(
        'data[degrees]' => 5,
        'data[random]' => 1,
        'data[bgcolor]' => '#FFFF00',
      ),
    );

    // Add style form.

    $edit = array(
      'name' => $style_name,
    );
430
    $this->drupalPost('admin/config/media/image-styles/add', $edit, t('Create new style'));
431
    $this->assertRaw(t('Style %name was created.', array('%name' => $style_name)), t('Image style successfully created.'));
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446

    // Add effect form.

    // Add each sample effect to the style.
    foreach ($effect_edits as $effect => $edit) {
      // Add the effect.
      $this->drupalPost($style_path, array('new' => $effect), t('Add'));
      if (!empty($edit)) {
        $this->drupalPost(NULL, $edit, t('Add effect'));
      }
    }

    // Edit effect form.

    // Revisit each form to make sure the effect was saved.
447
    drupal_static_reset('image_styles');
448
    $style = image_style_load($style_name);
gdd's avatar
gdd committed
449

450 451 452
    foreach ($style['effects'] as $ieid => $effect) {
      $this->drupalGet($style_path . '/effects/' . $ieid);
      foreach ($effect_edits[$effect['name']] as $field => $value) {
453
        $this->assertFieldByName($field, $value, t('The %field field in the %effect effect has the correct value of %value.', array('%field' => $field, '%effect' => $effect['name'], '%value' => $value)));
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
      }
    }

    // Image style overview form (ordering and renaming).

    // Confirm the order of effects is maintained according to the order we
    // added the fields.
    $effect_edits_order = array_keys($effect_edits);
    $effects_order = array_values($style['effects']);
    $order_correct = TRUE;
    foreach ($effects_order as $index => $effect) {
      if ($effect_edits_order[$index] != $effect['name']) {
        $order_correct = FALSE;
      }
    }
469
    $this->assertTrue($order_correct, t('The order of the effects is correctly set by default.'));
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484

    // Test the style overview form.
    // Change the name of the style and adjust the weights of effects.
    $style_name = strtolower($this->randomName(10));
    $weight = count($effect_edits);
    $edit = array(
      'name' => $style_name,
    );
    foreach ($style['effects'] as $ieid => $effect) {
      $edit['effects[' . $ieid . '][weight]'] = $weight;
      $weight--;
    }

    // Create an image to make sure it gets flushed after saving.
    $image_path = $this->createSampleImage($style);
485
    $this->assertEqual($this->getImageCount($style), 1, t('Image style %style image %file successfully generated.', array('%style' => $style['name'], '%file' => $image_path)));
486 487 488 489

    $this->drupalPost($style_path, $edit, t('Update style'));

    // Note that after changing the style name, the style path is changed.
490
    $style_path = 'admin/config/media/image-styles/edit/' . $style_name;
491 492 493

    // Check that the URL was updated.
    $this->drupalGet($style_path);
494
    $this->assertResponse(200, t('Image style %original renamed to %new', array('%original' => $style['name'], '%new' => $style_name)));
495 496 497 498

    // Check that the image was flushed after updating the style.
    // This is especially important when renaming the style. Make sure that
    // the old image directory has been deleted.
499
    $this->assertEqual($this->getImageCount($style), 0, t('Image style %style was flushed after renaming the style and updating the order of effects.', array('%style' => $style['name'])));
500 501 502

    // Load the style by the new name with the new weights.
    drupal_static_reset('image_styles');
503
    $style = image_style_load($style_name);
504 505 506 507 508 509 510 511 512 513

    // Confirm the new style order was saved.
    $effect_edits_order = array_reverse($effect_edits_order);
    $effects_order = array_values($style['effects']);
    $order_correct = TRUE;
    foreach ($effects_order as $index => $effect) {
      if ($effect_edits_order[$index] != $effect['name']) {
        $order_correct = FALSE;
      }
    }
514
    $this->assertTrue($order_correct, t('The order of the effects is correctly set by default.'));
515 516 517 518 519

    // Image effect deletion form.

    // Create an image to make sure it gets flushed after deleting an effect.
    $image_path = $this->createSampleImage($style);
520
    $this->assertEqual($this->getImageCount($style), 1, t('Image style %style image %file successfully generated.', array('%style' => $style['name'], '%file' => $image_path)));
521 522 523 524

    // Test effect deletion form.
    $effect = array_pop($style['effects']);
    $this->drupalPost($style_path . '/effects/' . $effect['ieid'] . '/delete', array(), t('Delete'));
525
    $this->assertRaw(t('The image effect %name has been deleted.', array('%name' => $effect['label'])), t('Image effect deleted.'));
526 527 528 529

    // Style deletion form.

    // Delete the style.
530
    $this->drupalPost('admin/config/media/image-styles/delete/' . $style_name, array(), t('Delete'));
531 532

    // Confirm the style directory has been removed.
533
    $directory = file_default_scheme() . '://styles/' . $style_name;
534
    $this->assertFalse(is_dir($directory), t('Image style %style directory removed on style deletion.', array('%style' => $style['name'])));
535 536

    drupal_static_reset('image_styles');
537
    $this->assertFalse(image_style_load($style_name), t('Image style %style successfully deleted.', array('%style' => $style['name'])));
538 539

  }
540

541
  /**
542
   * Test deleting a style and choosing a replacement style.
543
   */
544 545 546 547 548
  function testStyleReplacement() {
    // Create a new style.
    $style_name = strtolower($this->randomName(10));
    image_style_save(array('name' => $style_name));
    $style_path = 'admin/config/media/image-styles/edit/' . $style_name;
549

550 551
    // Create an image field that uses the new style.
    $field_name = strtolower($this->randomName(10));
552 553
    $this->createImageField($field_name, 'article');
    $instance = field_info_instance('node', $field_name, 'article');
554 555
    $instance['display']['default']['type'] = 'image';
    $instance['display']['default']['settings']['image_style'] = $style_name;
556 557 558 559 560 561 562 563 564
    field_update_instance($instance);

    // Create a new node with an image attached.
    $test_image = current($this->drupalGetTestFiles('image'));
    $nid = $this->uploadNodeImage($test_image, $field_name, 'article');
    $node = node_load($nid);

    // Test that image is displayed using newly created style.
    $this->drupalGet('node/' . $nid);
565
    $this->assertRaw(image_style_url($style_name, $node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['uri']), t('Image displayed using style @style.', array('@style' => $style_name)));
566 567 568 569 570

    // Rename the style and make sure the image field is updated.
    $new_style_name = strtolower($this->randomName(10));
    $edit = array(
      'name' => $new_style_name,
571
    );
572
    $this->drupalPost('admin/config/media/image-styles/edit/' . $style_name, $edit, t('Update style'));
573
    $this->assertText(t('Changes to the style have been saved.'), t('Style %name was renamed to %new_name.', array('%name' => $style_name, '%new_name' => $new_style_name)));
574
    $this->drupalGet('node/' . $nid);
575
    $this->assertRaw(image_style_url($new_style_name, $node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['uri']), t('Image displayed using style replacement style.'));
576

577
    // Delete the style and choose a replacement style.
578
    $edit = array(
579
      'replacement' => 'thumbnail',
580
    );
581 582 583
    $this->drupalPost('admin/config/media/image-styles/delete/' . $new_style_name, $edit, t('Delete'));
    $message = t('Style %name was deleted.', array('%name' => $new_style_name));
    $this->assertRaw($message, $message);
584

585
    $this->drupalGet('node/' . $nid);
586
    $this->assertRaw(image_style_url('thumbnail', $node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['uri']), t('Image displayed using style replacement style.'));
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
  }
}

/**
 * Test class to check that formatters and display settings are working.
 */
class ImageFieldDisplayTestCase extends ImageFieldTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Image field display tests',
      'description' => 'Test the display of image fields.',
      'group' => 'Image',
    );
  }

602 603 604 605
  function setUp() {
    parent::setUp(array('field_ui'));
  }

606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
  /**
   * Test image formatters on node display for public files.
   */
  function testImageFieldFormattersPublic() {
    $this->_testImageFieldFormatters('public');
  }

  /**
   * Test image formatters on node display for private files.
   */
  function testImageFieldFormattersPrivate() {
    // Remove access content permission from anonymous users.
    user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array('access content' => FALSE));
    $this->_testImageFieldFormatters('private');
  }

622 623 624
  /**
   * Test image formatters on node display.
   */
625
  function _testImageFieldFormatters($scheme) {
626
    $field_name = strtolower($this->randomName());
627
    $this->createImageField($field_name, 'article', array('uri_scheme' => $scheme));
628 629 630 631 632 633
    // Create a new node with an image attached.
    $test_image = current($this->drupalGetTestFiles('image'));
    $nid = $this->uploadNodeImage($test_image, $field_name, 'article');
    $node = node_load($nid, NULL, TRUE);

    // Test that the default formatter is being used.
634
    $image_uri = $node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['uri'];
635
    $image_info = array(
636
      'uri' => $image_uri,
637 638
      'width' => 40,
      'height' => 20,
639 640
    );
    $default_output = theme('image', $image_info);
641
    $this->assertRaw($default_output, t('Default formatter displaying correctly on full node view.'));
642 643 644

    // Test the image linked to file formatter.
    $instance = field_info_instance('node', $field_name, 'article');
645 646
    $instance['display']['default']['type'] = 'image';
    $instance['display']['default']['settings']['image_link'] = 'file';
647 648 649
    field_update_instance($instance);
    $default_output = l(theme('image', $image_info), file_create_url($image_uri), array('html' => TRUE));
    $this->drupalGet('node/' . $nid);
650
    $this->assertRaw($default_output, t('Image linked to file formatter displaying correctly on full node view.'));
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
    // Verify that the image can be downloaded.
    $this->assertEqual(file_get_contents($test_image->uri), $this->drupalGet(file_create_url($image_uri)), t('File was downloaded successfully.'));
    if ($scheme == 'private') {
      // Only verify HTTP headers when using private scheme and the headers are
      // sent by Drupal.
      $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png; name="' . $test_image->filename . '"', t('Content-Type header was sent.'));
      $this->assertEqual($this->drupalGetHeader('Content-Disposition'), 'inline; filename="' . $test_image->filename . '"', t('Content-Disposition header was sent.'));
      $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'private', t('Cache-Control header was sent.'));

      // Log out and try to access the file.
      $this->drupalLogout();
      $this->drupalGet(file_create_url($image_uri));
      $this->assertResponse('403', t('Access denied to original image as anonymous user.'));

      // Log in again.
      $this->drupalLogin($this->admin_user);
    }
668 669

    // Test the image linked to content formatter.
670
    $instance['display']['default']['settings']['image_link'] = 'content';
671 672 673
    field_update_instance($instance);
    $default_output = l(theme('image', $image_info), 'node/' . $nid, array('html' => TRUE, 'attributes' => array('class' => 'active')));
    $this->drupalGet('node/' . $nid);
674
    $this->assertRaw($default_output, t('Image linked to content formatter displaying correctly on full node view.'));
675 676

    // Test the image style 'thumbnail' formatter.
677 678
    $instance['display']['default']['settings']['image_link'] = '';
    $instance['display']['default']['settings']['image_style'] = 'thumbnail';
679
    field_update_instance($instance);
680
    // Ensure the derivative image is generated so we do not have to deal with
681 682
    // image style callback paths.
    $this->drupalGet(image_style_url('thumbnail', $image_uri));
683
    $image_info['uri'] = image_style_path('thumbnail', $image_uri);
684 685
    $image_info['width'] = 100;
    $image_info['height'] = 50;
686 687
    $default_output = theme('image', $image_info);
    $this->drupalGet('node/' . $nid);
688
    $this->assertRaw($default_output, t('Image style thumbnail formatter displaying correctly on full node view.'));
689 690 691 692 693 694 695

    if ($scheme == 'private') {
      // Log out and try to access the file.
      $this->drupalLogout();
      $this->drupalGet(image_style_url('thumbnail', $image_uri));
      $this->assertResponse('403', t('Access denied to image style thumbnail as anonymous user.'));
    }
696
  }
697

698 699 700 701
  /**
   * Tests for image field settings.
   */
  function testImageFieldSettings() {
702 703
    $test_image = current($this->drupalGetTestFiles('image'));
    list(, $test_image_extension) = explode('.', $test_image->filename);
704
    $field_name = strtolower($this->randomName());
705 706
    $instance_settings = array(
      'alt_field' => 1,
707
      'file_extensions' => $test_image_extension,
708 709 710 711 712 713 714 715
      'max_filesize' => '50 KB',
      'max_resolution' => '100x100',
      'min_resolution' => '10x10',
      'title_field' => 1,
    );
    $widget_settings = array(
      'preview_image_style' => 'medium',
    );
716 717 718 719
    $field = $this->createImageField($field_name, 'article', array(), $instance_settings, $widget_settings);
    $field['deleted'] = 0;
    $table = _field_sql_storage_tablename($field);
    $schema = drupal_get_schema($table, TRUE);
720 721 722
    $instance = field_info_instance('node', $field_name, 'article');

    $this->drupalGet('node/add/article');
723 724 725
    $this->assertText(t('Files must be less than 50 KB.'), t('Image widget max file size is displayed on article form.'));
    $this->assertText(t('Allowed file types: ' . $test_image_extension . '.'), t('Image widget allowed file types displayed on article form.'));
    $this->assertText(t('Images must be between 10x10 and 100x100 pixels.'), t('Image widget allowed resolution displayed on article form.'));
726 727 728 729 730

    // We have to create the article first and then edit it because the alt
    // and title fields do not display until the image has been attached.
    $nid = $this->uploadNodeImage($test_image, $field_name, 'article');
    $this->drupalGet('node/' . $nid . '/edit');
731 732
    $this->assertFieldByName($field_name . '[' . LANGUAGE_NOT_SPECIFIED . '][0][alt]', '', t('Alt field displayed on article form.'));
    $this->assertFieldByName($field_name . '[' . LANGUAGE_NOT_SPECIFIED . '][0][title]', '', t('Title field displayed on article form.'));
733 734 735 736
    // Verify that the attached image is being previewed using the 'medium'
    // style.
    $node = node_load($nid, NULL, TRUE);
    $image_info = array(
737
      'uri' => image_style_url('medium', $node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['uri']),
738 739
      'width' => 220,
      'height' => 110,
740 741
    );
    $default_output = theme('image', $image_info);
742
    $this->assertRaw($default_output, t("Preview image is displayed using 'medium' style."));
743 744 745

    // Add alt/title fields to the image and verify that they are displayed.
    $image_info = array(
746
      'uri' => $node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['uri'],
747 748
      'alt' => $this->randomName(),
      'title' => $this->randomName(),
749 750
      'width' => 40,
      'height' => 20,
751 752
    );
    $edit = array(
753 754
      $field_name . '[' . LANGUAGE_NOT_SPECIFIED . '][0][alt]' => $image_info['alt'],
      $field_name . '[' . LANGUAGE_NOT_SPECIFIED . '][0][title]' => $image_info['title'],
755 756 757
    );
    $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));
    $default_output = theme('image', $image_info);
758
    $this->assertRaw($default_output, t('Image displayed using user supplied alt and title attributes.'));
759 760 761 762

    // Verify that alt/title longer than allowed results in a validation error.
    $test_size = 2000;
    $edit = array(
763 764
      $field_name . '[' . LANGUAGE_NOT_SPECIFIED . '][0][alt]' => $this->randomName($test_size),
      $field_name . '[' . LANGUAGE_NOT_SPECIFIED . '][0][title]' => $this->randomName($test_size),
765 766 767
    );
    $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));
    $this->assertRaw(t('Alternate text cannot be longer than %max characters but is currently %length characters long.', array(
768
      '%max' => $schema['fields'][$field_name .'_alt']['length'],
769 770 771
      '%length' => $test_size,
    )));
    $this->assertRaw(t('Title cannot be longer than %max characters but is currently %length characters long.', array(
772
      '%max' => $schema['fields'][$field_name .'_title']['length'],
773 774
      '%length' => $test_size,
    )));
775 776 777 778 779 780 781
  }

  /**
   * Test use of a default image with an image field.
   */
  function testImageFieldDefaultImage() {
    // Create a new image field.
782
    $field_name = strtolower($this->randomName());
783 784 785 786 787 788 789 790
    $this->createImageField($field_name, 'article');

    // Create a new node, with no images and verify that no images are
    // displayed.
    $node = $this->drupalCreateNode(array('type' => 'article'));
    $this->drupalGet('node/' . $node->nid);
    // Verify that no image is displayed on the page by checking for the class
    // that would be used on the image field.
791
    $this->assertNoPattern('<div class="(.*?)field-name-' . strtr($field_name, '_', '-') . '(.*?)">', t('No image displayed when no image is attached and no default image specified.'));
792

793
    // Add a default image to the public imagefield instance.
794 795
    $images = $this->drupalGetTestFiles('image');
    $edit = array(
796
      'files[field_settings_default_image]' => drupal_realpath($images[0]->uri),
797 798 799 800 801 802
    );
    $this->drupalPost('admin/structure/types/manage/article/fields/' . $field_name, $edit, t('Save settings'));
    // Clear field info cache so the new default image is detected.
    field_info_cache_clear();
    $field = field_info_field($field_name);
    $image = file_load($field['settings']['default_image']);
803
    $this->assertTrue($image->status == FILE_STATUS_PERMANENT, t('The default image status is permanent.'));
804
    $default_output = theme('image', array('uri' => $image->uri));
805
    $this->drupalGet('node/' . $node->nid);
806
    $this->assertRaw($default_output, t('Default image displayed when no user supplied image is present.'));
807 808 809 810 811 812

    // Create a node with an image attached and ensure that the default image
    // is not displayed.
    $nid = $this->uploadNodeImage($images[1], $field_name, 'article');
    $node = node_load($nid, NULL, TRUE);
    $image_info = array(
813
      'uri' => $node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['uri'],
814 815
      'width' => 40,
      'height' => 20,
816 817 818
    );
    $image_output = theme('image', $image_info);
    $this->drupalGet('node/' . $nid);
819 820
    $this->assertNoRaw($default_output, t('Default image is not displayed when user supplied image is present.'));
    $this->assertRaw($image_output, t('User supplied image is displayed.'));
821 822 823 824 825 826 827 828 829

    // Remove default image from the field and make sure it is no longer used.
    $edit = array(
      'field[settings][default_image][fid]' => 0,
    );
    $this->drupalPost('admin/structure/types/manage/article/fields/' . $field_name, $edit, t('Save settings'));
    // Clear field info cache so the new default image is detected.
    field_info_cache_clear();
    $field = field_info_field($field_name);
830
    $this->assertFalse($field['settings']['default_image'], t('Default image removed from field.'));
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
    // Create an image field that uses the private:// scheme and test that the
    // default image works as expected.
    $private_field_name = strtolower($this->randomName());
    $this->createImageField($private_field_name, 'article', array('uri_scheme' => 'private'));
    // Add a default image to the new field.
    $edit = array(
      'files[field_settings_default_image]' => drupal_realpath($images[1]->uri),
    );
    $this->drupalPost('admin/structure/types/manage/article/fields/' . $private_field_name, $edit, t('Save settings'));
    $private_field = field_info_field($private_field_name);
    $image = file_load($private_field['settings']['default_image']);
    $this->assertEqual('private', file_uri_scheme($image->uri), t('Default image uses private:// scheme.'));
    $this->assertTrue($image->status == FILE_STATUS_PERMANENT, t('The default image status is permanent.'));
    // Create a new node with no image attached and ensure that default private
    // image is displayed.
    $node = $this->drupalCreateNode(array('type' => 'article'));
847
    $default_output = theme('image', array('uri' => $image->uri));
848 849
    $this->drupalGet('node/' . $node->nid);
    $this->assertRaw($default_output, t('Default private image displayed when no user supplied image is present.'));
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868
  }
}

/**
 * Test class to check for various validations.
 */
class ImageFieldValidateTestCase extends ImageFieldTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Image field validation tests',
      'description' => 'Tests validation functions such as min/max resolution.',
      'group' => 'Image',
    );
  }

  /**
   * Test min/max resolution settings.
   */
  function testResolution() {
869
    $field_name = strtolower($this->randomName());
870 871
    $min_resolution = 50;
    $max_resolution = 100;
872
    $instance_settings = array(
873 874
      'max_resolution' => $max_resolution . 'x' . $max_resolution,
      'min_resolution' => $min_resolution . 'x' . $min_resolution,
875 876 877
    );
    $this->createImageField($field_name, 'article', array(), $instance_settings);

878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
    // We want a test image that is too small, and a test image that is too
    // big, so cycle through test image files until we have what we need.
    $image_that_is_too_big = FALSE;
    $image_that_is_too_small = FALSE;
    foreach ($this->drupalGetTestFiles('image') as $image) {
      $info = image_get_info($image->uri);
      if ($info['width'] > $max_resolution) {
        $image_that_is_too_big = $image;
      }
      if ($info['width'] < $min_resolution) {
        $image_that_is_too_small = $image;
      }
      if ($image_that_is_too_small && $image_that_is_too_big) {
        break;
      }
    }
    $nid = $this->uploadNodeImage($image_that_is_too_small, $field_name, 'article');
895
    $this->assertText(t('The specified file ' . $image_that_is_too_small->filename . ' could not be uploaded. The image is too small; the minimum dimensions are 50x50 pixels.'), t('Node save failed when minimum image resolution was not met.'));
896
    $nid = $this->uploadNodeImage($image_that_is_too_big, $field_name, 'article');
897
    $this->assertText(t('The image was resized to fit within the maximum allowed dimensions of 100x100 pixels.'), t('Image exceeding max resolution was properly resized.'));
898
  }
899
}
900 901 902 903 904

/**
 * Tests that images have correct dimensions when styled.
 */
class ImageDimensionsUnitTest extends DrupalWebTestCase {
905
  protected $profile = 'testing';
906 907 908 909 910 911 912 913 914 915

  public static function getInfo() {
    return array(
      'name' => 'Image dimensions',
      'description' => 'Tests that images have correct dimensions when styled.',
      'group' => 'Image',
    );
  }

  function setUp() {
916
    parent::setUp(array('image', 'image_module_test'));
917 918 919 920 921 922 923 924 925 926 927 928 929
  }

  /**
   * Test styled image dimensions cumulatively.
   */
  function testImageDimensions() {
    // Create a working copy of the file.
    $files = $this->drupalGetTestFiles('image');
    $file = reset($files);
    $original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);

    // Create a style.
    $style = image_style_save(array('name' => 'test'));
930
    $generated_uri = 'public://styles/test/public/'. drupal_basename($original_uri);
931 932 933 934
    $url = image_style_url('test', $original_uri);

    $variables = array(
      'style_name' => 'test',
935
      'uri' => $original_uri,
936 937 938
      'width' => 40,
      'height' => 20,
    );
939 940 941 942
    // Verify that the original image matches the hard-coded values.
    $image_info = image_get_info($original_uri);
    $this->assertEqual($image_info['width'], $variables['width']);
    $this->assertEqual($image_info['height'], $variables['height']);
943 944 945 946 947 948 949 950 951

    // Scale an image that is wider than it is high.
    $effect = array(
      'name' => 'image_scale',
      'data' => array(
        'width' => 120,
        'height' => 90,
        'upscale' => TRUE,
      ),
952
      'weight' => 0,
953 954
    );

955
    image_effect_save('test', $effect);
956
    $img_tag = theme_image_style($variables);
957
    $this->assertEqual($img_tag, '<img src="' . $url . '" width="120" height="60" alt="" />');
958 959 960 961 962
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
    $this->drupalGet($url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));
    $image_info = image_get_info($generated_uri);
963 964
    $this->assertEqual($image_info['width'], 120);
    $this->assertEqual($image_info['height'], 60);
965 966 967 968 969 970 971 972

    // Rotate 90 degrees anticlockwise.
    $effect = array(
      'name' => 'image_rotate',
      'data' => array(
        'degrees' => -90,
        'random' => FALSE,
      ),
973
      'weight' => 1,
974 975
    );

976
    image_effect_save('test', $effect);
977
    $img_tag = theme_image_style($variables);
978
    $this->assertEqual($img_tag, '<img src="' . $url . '" width="60" height="120" alt="" />');
979 980 981 982 983
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
    $this->drupalGet($url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));
    $image_info = image_get_info($generated_uri);
984 985
    $this->assertEqual($image_info['width'], 60);
    $this->assertEqual($image_info['height'], 120);
986 987 988 989 990

    // Scale an image that is higher than it is wide (rotated by previous effect).
    $effect = array(
      'name' => 'image_scale',
      'data' => array(
991
        'width' => 120,
992 993 994
        'height' => 90,
        'upscale' => TRUE,
      ),
995
      'weight' => 2,
996 997
    );

998
    image_effect_save('test', $effect);
999
    $img_tag = theme_image_style($variables);
1000
    $this->assertEqual($img_tag, '<img src="' . $url . '" width="45" height="90" alt="" />');
1001 1002 1003 1004 1005
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
    $this->drupalGet($url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));
    $image_info = image_get_info($generated_uri);
1006 1007
    $this->assertEqual($image_info['width'], 45);
    $this->assertEqual($image_info['height'], 90);
1008 1009 1010 1011 1012 1013 1014 1015 1016

    // Test upscale disabled.
    $effect = array(
      'name' => 'image_scale',
      'data' => array(
        'width' => 400,
        'height' => 200,
        'upscale' => FALSE,
      ),
1017
      'weight' => 3,
1018 1019
    );

1020
    image_effect_save('test', $effect);
1021
    $img_tag = theme_image_style($variables);
1022
    $this->assertEqual($img_tag, '<img src="' . $url . '" width="45" height="90" alt="" />');
1023 1024 1025 1026 1027
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
    $this->drupalGet($url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));
    $image_info = image_get_info($generated_uri);
1028 1029
    $this->assertEqual($image_info['width'], 45);
    $this->assertEqual($image_info['height'], 90);
1030 1031 1032 1033 1034

    // Add a desaturate effect.
    $effect = array(
      'name' => 'image_desaturate',
      'data' => array(),
1035
      'weight' => 4,
1036 1037
    );

1038
    image_effect_save('test', $effect);
1039
    $img_tag = theme_image_style($variables);
1040
    $this->assertEqual($img_tag, '<img src="' . $url . '" width="45" height="90" alt="" />');
1041 1042 1043 1044 1045
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
    $this->drupalGet($url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));
    $image_info = image_get_info($generated_uri);
1046 1047
    $this->assertEqual($image_info['width'], 45);
    $this->assertEqual($image_info['height'], 90);
1048 1049 1050 1051 1052 1053 1054 1055

    // Add a random rotate effect.
    $effect = array(
      'name' => 'image_rotate',
      'data' => array(
        'degrees' => 180,
        'random' => TRUE,
      ),
1056
      'weight' => 5,
1057 1058
    );

1059
    image_effect_save('test', $effect);
1060
    $img_tag = theme_image_style($variables);
1061
    $this->assertEqual($img_tag, '<img src="' . $url . '" alt="" />');
1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
    $this->drupalGet($url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));


    // Add a crop effect.
    $effect = array(
      'name' => 'image_crop',
      'data' => array(
        'width' => 30,
        'height' => 30,
        'anchor' => 'center-center',
      ),
1076
      'weight' => 6,
1077 1078
    );

1079
    image_effect_save('test', $effect);
1080
    $img_tag = theme_image_style($variables);
1081
    $this->assertEqual($img_tag, '<img src="' . $url . '" width="30" height="30" alt="" />');
1082 1083 1084 1085 1086
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
    $this->drupalGet($url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));
    $image_info = image_get_info($generated_uri);
1087 1088
    $this->assertEqual($image_info['width'], 30);
    $this->assertEqual($image_info['height'], 30);
1089 1090 1091 1092 1093 1094 1095 1096

    // Rotate to a non-multiple of 90 degrees.
    $effect = array(
      'name' => 'image_rotate',
      'data' => array(
        'degrees' => 57,
        'random' => FALSE,
      ),
1097
      'weight' => 7,
1098 1099
    );

1100
    $effect = image_effect_save('test', $effect);
1101
    $img_tag = theme_image_style($variables);
1102
    $this->assertEqual($img_tag, '<img src="' . $url . '" alt="" />');
1103 1104 1105 1106 1107
    $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
    $this->drupalGet($url);
    $this->assertResponse(200, t('Image was generated at the URL.'));
    $this->assertTrue(file_exists($generated_uri), t('Generated file does exist after we accessed it.'));

1108
    image_effect_delete('test', $effect);
1109 1110 1111 1112 1113 1114

    // Ensure that an effect with no dimensions callback unsets the dimensions.
    // This ensures compatibility with 7.0 contrib modules.
    $effect = array(
      'name' => 'image_module_test_null',
      'data' => array(),
1115
      'weight' => 8,
1116 1117
    );

1118
    image_effect_save('test', $effect);
1119
    $img_tag = theme_image_style($variables);
1120
    $this->assertEqual($img_tag,