Chris@18: save(); Chris@18: ConfigurableLanguage::createFromLangcode('ca-fr')->save(); Chris@18: Chris@18: // In order to reflect the changes for a multilingual site in the container Chris@18: // we have to rebuild it. Chris@18: $this->rebuildContainer(); Chris@18: Chris@18: \Drupal::configFactory()->getEditable('language.negotiation') Chris@18: ->set('url.prefixes.ca', 'ca') Chris@18: ->set('url.prefixes.ca-fr', 'ca-fr') Chris@18: ->save(); Chris@18: Chris@18: $this->createDefaultContent(5, 5, TRUE, TRUE, static::IS_MULTILINGUAL, FALSE); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Tests reading multilingual content. Chris@18: */ Chris@18: public function testReadMultilingual() { Chris@18: // Different databases have different sort orders, so a sort is required so Chris@18: // test expectations do not need to vary per database. Chris@18: $default_sort = ['sort' => 'drupal_internal__nid']; Chris@18: Chris@18: // Test reading an individual entity translation. Chris@18: $output = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $this->nodes[0]->uuid(), ['query' => ['include' => 'field_tags,field_image'] + $default_sort])); Chris@18: $this->assertEquals($this->nodes[0]->getTranslation('ca')->getTitle(), $output['data']['attributes']['title']); Chris@18: $this->assertSame('ca', $output['data']['attributes']['langcode']); Chris@18: $included_tags = array_filter($output['included'], function ($entry) { Chris@18: return $entry['type'] === 'taxonomy_term--tags'; Chris@18: }); Chris@18: $tag_name = $this->nodes[0]->get('field_tags')->entity Chris@18: ->getTranslation('ca')->getName(); Chris@18: $this->assertEquals($tag_name, reset($included_tags)['attributes']['name']); Chris@18: $alt = $this->nodes[0]->getTranslation('ca')->get('field_image')->alt; Chris@18: $this->assertSame($alt, $output['data']['relationships']['field_image']['data']['meta']['alt']); Chris@18: Chris@18: // Test reading an individual entity fallback. Chris@18: $output = Json::decode($this->drupalGet('/ca-fr/jsonapi/node/article/' . $this->nodes[0]->uuid())); Chris@18: $this->assertEquals($this->nodes[0]->getTranslation('ca')->getTitle(), $output['data']['attributes']['title']); Chris@18: Chris@18: $output = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $this->nodes[0]->uuid(), ['query' => $default_sort])); Chris@18: $this->assertEquals($this->nodes[0]->getTranslation('ca')->getTitle(), $output['data']['attributes']['title']); Chris@18: Chris@18: // Test reading a collection of entities. Chris@18: $output = Json::decode($this->drupalGet('/ca/jsonapi/node/article', ['query' => $default_sort])); Chris@18: $this->assertEquals($this->nodes[0]->getTranslation('ca')->getTitle(), $output['data'][0]['attributes']['title']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Tests updating a translation. Chris@18: */ Chris@18: public function testPatchTranslation() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: $node = $this->nodes[0]; Chris@18: $uuid = $node->uuid(); Chris@18: Chris@18: // Assert the precondition: the 'ca' translation has a different title. Chris@18: $document = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid)); Chris@18: $document_ca = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $uuid)); Chris@18: $this->assertSame('en', $document['data']['attributes']['langcode']); Chris@18: $this->assertSame('ca', $document_ca['data']['attributes']['langcode']); Chris@18: $this->assertSame($node->getTitle(), $document['data']['attributes']['title']); Chris@18: $this->assertSame($node->getTitle() . ' (ca)', $document_ca['data']['attributes']['title']); Chris@18: Chris@18: // PATCH the 'ca' translation. Chris@18: $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), [ Chris@18: 'bypass node access', Chris@18: ]); Chris@18: $request_options = []; Chris@18: $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; Chris@18: $request_options[RequestOptions::BODY] = Json::encode([ Chris@18: 'data' => [ Chris@18: 'type' => 'node--article', Chris@18: 'id' => $uuid, Chris@18: 'attributes' => [ Chris@18: 'title' => $document_ca['data']['attributes']['title'] . ' UPDATED', Chris@18: ], Chris@18: ], Chris@18: ]); Chris@18: $response = $this->request('PATCH', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: Chris@18: // Assert the postcondition: only the 'ca' translation has an updated title. Chris@18: $document_updated = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid)); Chris@18: $document_ca_updated = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $uuid)); Chris@18: $this->assertSame('en', $document_updated['data']['attributes']['langcode']); Chris@18: $this->assertSame('ca', $document_ca_updated['data']['attributes']['langcode']); Chris@18: $this->assertSame($node->getTitle(), $document_updated['data']['attributes']['title']); Chris@18: $this->assertSame($node->getTitle() . ' (ca) UPDATED', $document_ca_updated['data']['attributes']['title']); Chris@18: Chris@18: // Specifying a langcode is not allowed by default. Chris@18: $request_options[RequestOptions::BODY] = Json::encode([ Chris@18: 'data' => [ Chris@18: 'type' => 'node--article', Chris@18: 'id' => $uuid, Chris@18: 'attributes' => [ Chris@18: 'langcode' => 'ca-fr', Chris@18: ], Chris@18: ], Chris@18: ]); Chris@18: $response = $this->request('PATCH', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $this->assertSame(403, $response->getStatusCode()); Chris@18: Chris@18: // Specifying a langcode is allowed once configured to be alterable. But Chris@18: // modifying the language of a non-default translation is still not allowed. Chris@18: ContentLanguageSettings::create([ Chris@18: 'target_entity_type_id' => 'node', Chris@18: 'target_bundle' => 'article', Chris@18: ])->setLanguageAlterable(TRUE) Chris@18: ->save(); Chris@18: $response = $this->request('PATCH', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $this->assertSame(500, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('The translation language cannot be changed (ca).', $document['errors'][0]['detail']); Chris@18: Chris@18: // Changing the langcode of the default ('en') translation is possible: Chris@18: // first verify that it currently is 'en', then change it to 'ca-fr', and Chris@18: // verify that the the title is unchanged, but the langcode is updated. Chris@18: $response = $this->request('GET', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame($node->getTitle(), $document['data']['attributes']['title']); Chris@18: $this->assertSame('en', $document['data']['attributes']['langcode']); Chris@18: $response = $this->request('PATCH', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame($node->getTitle(), $document['data']['attributes']['title']); Chris@18: $this->assertSame('ca-fr', $document['data']['attributes']['langcode']); Chris@18: Chris@18: // Finally: assert the postcondition of all installed languages. Chris@18: // - When GETting the 'en' translation, we get 'ca-fr', since the 'en' Chris@18: // translation doesn't exist anymore. Chris@18: $response = $this->request('GET', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('ca-fr', $document['data']['attributes']['langcode']); Chris@18: $this->assertSame($node->getTitle(), $document['data']['attributes']['title']); Chris@18: // - When GETting the 'ca' translation, we still get the 'ca' one. Chris@18: $response = $this->request('GET', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('ca', $document['data']['attributes']['langcode']); Chris@18: $this->assertSame($node->getTitle() . ' (ca) UPDATED', $document['data']['attributes']['title']); Chris@18: // - When GETting the 'ca-fr' translation, we now get the default Chris@18: // translation. Chris@18: $response = $this->request('GET', Url::fromUri('base:/ca-fr/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('ca-fr', $document['data']['attributes']['langcode']); Chris@18: $this->assertSame($node->getTitle(), $document['data']['attributes']['title']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Tests updating a translation fallback. Chris@18: */ Chris@18: public function testPatchTranslationFallback() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: $node = $this->nodes[0]; Chris@18: $uuid = $node->uuid(); Chris@18: Chris@18: // Assert the precondition: 'ca-fr' falls back to the 'ca' translation which Chris@18: // has a different title. Chris@18: $document = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid)); Chris@18: $document_ca = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $uuid)); Chris@18: $document_cafr = Json::decode($this->drupalGet('/ca-fr/jsonapi/node/article/' . $uuid)); Chris@18: $this->assertSame('en', $document['data']['attributes']['langcode']); Chris@18: $this->assertSame('ca', $document_ca['data']['attributes']['langcode']); Chris@18: $this->assertSame('ca', $document_cafr['data']['attributes']['langcode']); Chris@18: $this->assertSame($node->getTitle(), $document['data']['attributes']['title']); Chris@18: $this->assertSame($node->getTitle() . ' (ca)', $document_ca['data']['attributes']['title']); Chris@18: $this->assertSame($node->getTitle() . ' (ca)', $document_cafr['data']['attributes']['title']); Chris@18: Chris@18: // PATCH the 'ca-fr' translation. Chris@18: $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), [ Chris@18: 'bypass node access', Chris@18: ]); Chris@18: $request_options = []; Chris@18: $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; Chris@18: $request_options[RequestOptions::BODY] = Json::encode([ Chris@18: 'data' => [ Chris@18: 'type' => 'node--article', Chris@18: 'id' => $uuid, Chris@18: 'attributes' => [ Chris@18: 'title' => $document_cafr['data']['attributes']['title'] . ' UPDATED', Chris@18: ], Chris@18: ], Chris@18: ]); Chris@18: $response = $this->request('PATCH', Url::fromUri('base:/ca-fr/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); Chris@18: $this->assertSame(405, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('The requested translation of the resource object does not exist, instead modify one of the translations that do exist: ca, en.', $document['errors'][0]['detail']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Tests creating a translation. Chris@18: */ Chris@18: public function testPostTranslation() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), [ Chris@18: 'bypass node access', Chris@18: ]); Chris@18: Chris@18: $title = 'Llamas FTW (ca)'; Chris@18: $request_document = [ Chris@18: 'data' => [ Chris@18: 'type' => 'node--article', Chris@18: 'attributes' => [ Chris@18: 'title' => $title, Chris@18: 'langcode' => 'ca', Chris@18: ], Chris@18: ], Chris@18: ]; Chris@18: Chris@18: $request_options = []; Chris@18: $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; Chris@18: Chris@18: // Specifying a langcode is forbidden by language_entity_field_access(). Chris@18: $request_options[RequestOptions::BODY] = Json::encode($request_document); Chris@18: $response = $this->request('POST', Url::fromUri('base:/ca/jsonapi/node/article/'), $request_options); Chris@18: $this->assertSame(403, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('The current user is not allowed to POST the selected field (langcode).', $document['errors'][0]['detail']); Chris@18: Chris@18: // Omitting a langcode results in an entity in 'en': the default language of Chris@18: // the site. Chris@18: unset($request_document['data']['attributes']['langcode']); Chris@18: $request_options[RequestOptions::BODY] = Json::encode($request_document); Chris@18: $response = $this->request('POST', Url::fromUri('base:/ca/jsonapi/node/article/'), $request_options); Chris@18: $this->assertSame(201, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame($title, $document['data']['attributes']['title']); Chris@18: $this->assertSame('en', $document['data']['attributes']['langcode']); Chris@18: $this->assertSame(['en'], array_keys(Node::load($document['data']['attributes']['drupal_internal__nid'])->getTranslationLanguages())); Chris@18: Chris@18: // Specifying a langcode is allowed once configured to be alterable. Now an Chris@18: // entity can be created with the specified langcode. Chris@18: ContentLanguageSettings::create([ Chris@18: 'target_entity_type_id' => 'node', Chris@18: 'target_bundle' => 'article', Chris@18: ])->setLanguageAlterable(TRUE) Chris@18: ->save(); Chris@18: $request_document['data']['attributes']['langcode'] = 'ca'; Chris@18: $request_options[RequestOptions::BODY] = Json::encode($request_document); Chris@18: $response = $this->request('POST', Url::fromUri('base:/ca/jsonapi/node/article/'), $request_options); Chris@18: $this->assertSame(201, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame($title, $document['data']['attributes']['title']); Chris@18: $this->assertSame('ca', $document['data']['attributes']['langcode']); Chris@18: $this->assertSame(['ca'], array_keys(Node::load($document['data']['attributes']['drupal_internal__nid'])->getTranslationLanguages())); Chris@18: Chris@18: // Same request, but sent to the URL without the language prefix. Chris@18: $response = $this->request('POST', Url::fromUri('base:/jsonapi/node/article/'), $request_options); Chris@18: $this->assertSame(201, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame($title, $document['data']['attributes']['title']); Chris@18: $this->assertSame('ca', $document['data']['attributes']['langcode']); Chris@18: $this->assertSame(['ca'], array_keys(Node::load($document['data']['attributes']['drupal_internal__nid'])->getTranslationLanguages())); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Tests deleting multilingual content. Chris@18: */ Chris@18: public function testDeleteMultilingual() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), [ Chris@18: 'bypass node access', Chris@18: ]); Chris@18: Chris@18: $response = $this->request('DELETE', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), []); Chris@18: $this->assertSame(405, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('Deleting a resource object translation is not yet supported. See https://www.drupal.org/docs/8/modules/jsonapi/translations.', $document['errors'][0]['detail']); Chris@18: Chris@18: $response = $this->request('DELETE', Url::fromUri('base:/ca-fr/jsonapi/node/article/' . $this->nodes[0]->uuid()), []); Chris@18: $this->assertSame(405, $response->getStatusCode()); Chris@18: $document = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('Deleting a resource object translation is not yet supported. See https://www.drupal.org/docs/8/modules/jsonapi/translations.', $document['errors'][0]['detail']); Chris@18: Chris@18: $response = $this->request('DELETE', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), []); Chris@18: $this->assertSame(204, $response->getStatusCode()); Chris@18: $this->assertFalse(Node::load($this->nodes[0]->id())); Chris@18: } Chris@18: Chris@18: }