diff -u b/core/modules/image/src/Controller/QuickEditImageController.php b/core/modules/image/src/Controller/QuickEditImageController.php --- b/core/modules/image/src/Controller/QuickEditImageController.php +++ b/core/modules/image/src/Controller/QuickEditImageController.php @@ -195,9 +195,23 @@ throw new BadRequestHttpException('Requested Entity is not a Content Entity.'); } - // Get the first item (we only support single-cardinality image fields). - $field = $entity->getTranslation($langcode)->$field_name->get(0); + // Check that this field exists. + /** @var \Drupal\Core\Field\FieldItemListInterface $field_list */ + $field_list = $entity->getTranslation($langcode)->$field_name; + if (!$field_list) { + throw new BadRequestHttpException('Requested Field does not exist.'); + } + + // If the list is empty, append an empty item to use. + if ($field_list->isEmpty()) { + $field = $field_list->appendItem(); + } + // Otherwise, use the first item. + else { + $field = $entity->getTranslation($langcode)->$field_name->get(0); + } + // Ensure that the field is the type we expect. if (!($field instanceof ImageItem)) { throw new BadRequestHttpException('Requested Field is not of type "image".'); } only in patch2: unchanged: --- /dev/null +++ b/core/modules/image/src/Tests/QuickEditImageTest.php @@ -0,0 +1,138 @@ +createRole(['access in-place editing']); + $this->adminUser->addRole($rid); + $this->adminUser->save(); + + // Create a field with basic resolution validators. + $field_settings = [ + 'max_resolution' => '100x', + 'min_resolution' => '50x', + ]; + $this->createImageField('image_test', 'article', [], $field_settings); + } + + /** + * Tests that the field info route returns expected data. + */ + function testFieldInfo() { + // Create a test Node. + $node = $this->drupalCreateNode([ + 'type' => 'article', + 'title' => t('Test Node'), + ]); + $info = $this->drupalGetJSON('quickedit/image/info/node/' . $node->id() . '/image_test/' . $node->language()->getId() . '/default'); + // Assert that the default settings for our field are respected by our JSON + // endpoint. + $this->assertTrue($info['alt_field']); + $this->assertFalse($info['title_field']); + } + + /** + * Tests that uploading a valid image works. + */ + function testValidImageUpload() { + // Create a test Node. + $node = $this->drupalCreateNode([ + 'type' => 'article', + 'title' => t('Test Node'), + ]); + + // We want a test image that is a valid size. + $valid_image = FALSE; + $image_factory = $this->container->get('image.factory'); + foreach ($this->drupalGetTestFiles('image') as $image) { + $image_file = $image_factory->get($image->uri); + if ($image_file->getWidth() > 50 && $image_file->getWidth() < 100) { + $valid_image = $image; + break; + } + } + $this->uploadImage($valid_image, $node->id(), 'image_test', $node->language()->getId()); + $this->assertText('fid', t('Valid upload completed successfully.')); + } + + /** + * Tests that uploading a invalid image does not work. + */ + function testInvalidUpload() { + // Create a test Node. + $node = $this->drupalCreateNode([ + 'type' => 'article', + 'title' => t('Test Node'), + ]); + + // We want a test image that will fail validation. + $invalid_image = FALSE; + /** @var \Drupal\Core\Image\ImageFactory $image_factory */ + $image_factory = $this->container->get('image.factory'); + foreach ($this->drupalGetTestFiles('image') as $image) { + /** @var \Drupal\Core\Image\ImageInterface $image_file */ + $image_file = $image_factory->get($image->uri); + if ($image_file->getWidth() < 50 || $image_file->getWidth() > 100 ) { + $invalid_image = $image; + break; + } + } + $this->uploadImage($invalid_image, $node->id(), 'image_test', $node->language()->getId()); + $this->assertText('main_error', t('Invalid upload returned errors.')); + } + + /** + * Uploads an image using the image module's Quick Edit route. + * + * @param object $image + * The image to upload. + * @param integer $nid + * The target node ID. + * @param string $field_name + * The target field machine name. + * @param string $langcode + * The langcode to use when setting the field's value. + * + * @return mixed + * The content returned from the call to $this->curlExec(). + */ + function uploadImage($image, $nid, $field_name, $langcode) { + $filepath = $this->container->get('file_system')->realpath($image->uri); + $data = [ + 'files[image]' => curl_file_create($filepath), + ]; + $path = 'quickedit/image/upload/node/' . $nid . '/' . $field_name . '/' . $langcode . '/default'; + // We assemble the curl request ourselves as drupalPost cannot process file + // uploads, and drupalPostForm only works with typical Drupal forms. + return $this->curlExec([ + CURLOPT_URL => $this->buildUrl($path, []), + CURLOPT_POST => TRUE, + CURLOPT_POSTFIELDS => $data, + CURLOPT_HTTPHEADER => [ + 'Accept: application/json', + 'Content-Type: multipart/form-data', + ], + ]); + } + +}