[PP-1] Disallow removing a language when there is content in that language

Created on 9 February 2017, over 7 years ago
Updated 27 June 2023, 12 months ago

Problem/Motivation

After removing a language that has at least one content translation created, any subsequent attempt to load /admin/content results in the following error:

The website encountered an unexpected error. Please try again later.
Error: Call to a member function getId() on null in Drupal\Core\Entity\Entity\EntityViewDisplay->buildMultiple() (line 254 of core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php).

I don't feel qualified to offer fixes to Drupal core yet but I think the culprit is the statement
$view_langcode = $entity->language()->getId();
When you remove a language you get a warning that all translations to that language will be marked as undefined. Well, that statement apparently is trying to getId() of undefined...

Steps to reproduce

- Install Umami
- Add an additional language
- Add a translation (in the new language you added) to an existing recipe
- Delete the language you added
- Go to /admin/content

Proposed resolution

As title says
Disallow removing a language when there is content in that language.
- Build a function / method for ConfigurableLanguage to check if there is any content for given language (langcode)
Check for all Content entities ... and return TRUE for the first "encounter".
- Delete programmatically: Also on ConfigurableLanguage throws an error for deletion if we have content for the language to be deleted.
Similar with deleting default language
- Delete from UI: Update LanguageDeleteForm to disable deletion is there is content in that language.
- Add tests for those updates.
- Update/fix tests that deletes languages with content (usually Users)

Remaining tasks

User interface changes

- Disable delete language form submit - is there is content in that language.

API changes

Data model changes

Release notes snippet

📌 Task
Status

Postponed

Version

11.0 🔥

Component
Entity 

Last updated about 20 hours ago

Created by

🇷🇺Russia marassa Moscow

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

    Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

  • First commit to issue fork.
  • last update about 1 year ago
    Custom Commands Failed
  • @vasike opened merge request.
  • last update about 1 year ago
    Custom Commands Failed
  • last update about 1 year ago
    Custom Commands Failed
  • last update about 1 year ago
    29,411 pass, 17 fail
  • last update about 1 year ago
    29,411 pass, 17 fail
  • last update about 1 year ago
    29,418 pass, 7 fail
  • last update about 1 year ago
    29,440 pass, 4 fail
  • last update about 1 year ago
    29,444 pass
  • last update about 1 year ago
    Custom Commands Failed
  • last update about 1 year ago
    29,444 pass
  • Status changed to Needs review about 1 year ago
  • 🇷🇴Romania vasike Ramnicu Valcea

    new MR available - continuing the patches efforts

    - Create helper method languageUsedByContent($langcode) for ConfigurableLanguage entity class
    Used the code from patch ... but "stop searching" on the first "usage"
    - Use this method to "block" Language deletion if there is a content for the the language
    - Use this method to disable the LanguageDeleteForm if there is a content for the the language
    - Fix the tests - by updating the content language (user) before deletion of "default english language"
    - Complete the tests - core/modules/language/tests/src/Functional/LanguageConfigurationTest.php
    To tests what happens if the content still has language in use.

    Questions:
    Are there other tests needed?
    Is "change record" required for this?

  • Status changed to Needs work about 1 year ago
  • 🇺🇸United States smustgrave

    Change record I think so.

    Question bunch of tests had to be updated with

        foreach (User::loadMultiple() as $account) {
          $account->set('langcode', LanguageInterface::LANGCODE_NOT_SPECIFIED)->save();
        }
    

    Is that because it broke? Could this cause a regression for existing sites if so.

  • 🇷🇴Romania vasike Ramnicu Valcea

    Question bunch of tests had to be updated with

        foreach (User::loadMultiple() as $account) {
          $account->set('langcode', LanguageInterface::LANGCODE_NOT_SPECIFIED)->save();
        }

    Is that because it broke? Could this cause a regression for existing sites if so.

    This is about, actually, what happens ... we could delete the language ... but then we'll have issues with the content - in the language - left behind.
    No regression here - we just make sure the solution works properly - please @see LanguageConfigurationTest
    https://git.drupalcode.org/project/drupal/-/blob/ca516d9b7e1275bce90d1bd....
    We cover what happens without those deletions ...

    About change record ... this more like functional update (fix) - not API update ... not sure what to specify there ...
    any suggestion/example

  • 🇺🇸United States smustgrave

    Think it could be as simple as just mentioning that you can't delete a language now. Maybe a screenshot of the error

  • Status changed to Needs review about 1 year ago
  • 🇷🇴Romania vasike Ramnicu Valcea

    Unfortunately i can't get error ... maybe it's not there anymore ...

    So, please, anyone could replicate the error from summary ... or confirm the error is ... no more.

    however i noticed something like there will be dirty content ... and if want to clean up ... first will delete FIRST actually the content in the new language.
    then i could delete the "source"

    i'll try to upload some images that should explain the "dirty content"

    i'll put on "Needs review" ... just to get some feedback

    then we'll see about the change record

  • Status changed to Needs work about 1 year ago
  • 🇺🇸United States smustgrave

    Following the steps from the issue summary I get the error.

    Applying the MR And trying to repeat the steps I get

    Started the CR https://www.drupal.org/node/3367094

    Sorry I didn't notice before but the proposed solution is TBD, that will have to be updated before being RTBC.

  • Status changed to Needs review about 1 year ago
  • 🇷🇴Romania vasike Ramnicu Valcea

    updated the summary ... i hope we're close ...

  • Status changed to RTBC about 1 year ago
  • 🇺🇸United States smustgrave

    Lets get this in committers eyes

    Good job!

  • last update about 1 year ago
    29,486 pass
  • last update 12 months ago
    29,499 pass
  • last update 12 months ago
    29,499 pass
  • Status changed to Needs work 12 months ago
  • 🇬🇧United Kingdom catch

    I would normally agree with preventing deletion of things that leaves the site in an inconsistent state but am less sure here.

    If I have one node, where the original language is English and French, and I want to remove French from my site entirely, then I get this message - how do I go about removing the French translation from the node, not only from the most recent version, but also every previous revision?

  • 🇺🇸United States smustgrave

    @catch would imagine you would need to do some bulk deletion for the language you are trying to delete. See as like nodes or paragraphs. You can't delete the bundles until the content is gone.

  • Status changed to RTBC 12 months ago
  • 🇷🇴Romania vasike Ramnicu Valcea

    @catch do you mean like point the user to content that needs to be deleted?

    if this is the case:
    What if there are like thousands of content entities ... and not only node, from users to custom content entities?

    Or do you mean, like a "button" that does this?
    Here i would say it could a follow-up

    So please, details a little "your desires".

    For now put back to RTBC ... as there is no clear request of what's needed to be worked on ... imho

  • last update 12 months ago
    29,559 pass
  • Status changed to Needs work 12 months ago
  • 🇬🇧United Kingdom catch

    @vasike I mean that as far as I know, there's no way to remove old translation content.

    For example:

    1. Install Umami
    2. Edit the spanish translation of node/9 a couple of times, your node_revision__field_summary table should look something like this:

    MariaDB [db]> SELECT * FROM node_revision__field_summary WHERE entity_id = 9;
    +--------+---------+-----------+-------------+----------+-------+--------------------------------------------------------------------------------------------------------------+----------------------+
    | bundle | deleted | entity_id | revision_id | langcode | delta | field_summary_value                                                                                          | field_summary_format |
    +--------+---------+-----------+-------------+----------+-------+--------------------------------------------------------------------------------------------------------------+----------------------+
    | recipe |       0 |         9 |          17 | en       |     0 | A rich and fiery chili sauce. Take care when handling chili peppers. And serve sparingly!                    | basic_html           |
    | recipe |       0 |         9 |          18 | en       |     0 | A rich and fiery chili sauce. Take care when handling chili peppers. And serve sparingly!                    | basic_html           |
    | recipe |       0 |         9 |          18 | es       |     0 | Una rica y ardiente salsa de chile. Tenga cuidado al manejar chiles. ¡Y servir con moderación!               | basic_html           |
    | recipe |       0 |         9 |          39 | en       |     0 | A rich and fiery chili sauce. Take care when handling chili peppers. And serve sparingly!                    | basic_html           |
    | recipe |       0 |         9 |          39 | es       |     0 | Una rica y ardiente salsa de chile. Tenga cuidado al manejar chiles. ¡Y servir con moderación!               | basic_html           |
    | recipe |       0 |         9 |          40 | en       |     0 | A rich and fiery chili sauce. Take care when handling chili peppers. And serve sparingly!                    | basic_html           |
    | recipe |       0 |         9 |          40 | es       |     0 | <p>Una rica y ardiente salsa de chile. Tenga cuidado al manejar chiles. ¡Y servir con moderación! aaaa</p>   | basic_html           |
    | recipe |       0 |         9 |          41 | en       |     0 | A rich and fiery chili sauce. Take care when handling chili peppers. And serve sparingly!                    | basic_html           |
    +--------+---------+-----------+-------------+----------+-------+--------------------------------------------------------------------------------------------------------------+----------------------+
    8 rows in set (0.001 sec)
    
    

    And node_field_data should look something like this:

    MariaDB [db]> SELECT * FROM node_field_data WHERE nid = 9;
    +-----+-----+--------+----------+--------+-----+---------------------------+------------+------------+---------+--------+------------------+-------------------------------+----------------------------+------------------------------+
    | nid | vid | type   | langcode | status | uid | title                     | created    | changed    | promote | sticky | default_langcode | revision_translation_affected | content_translation_source | content_translation_outdated |
    +-----+-----+--------+----------+--------+-----+---------------------------+------------+------------+---------+--------+------------------+-------------------------------+----------------------------+------------------------------+
    |   9 |  39 | recipe | en       |      1 |   4 | Fiery chili sauce         | 1686645158 | 1686645158 |       1 |      0 |                1 |                          NULL | und                        |                            0 |
    |   9 |  39 | recipe | es       |      1 |   4 | Salsa de chile ardiente 2 | 1686645158 | 1687865049 |       1 |      0 |                0 |                             1 | und                        |                            0 |
    +-----+-----+--------+----------+--------+-----+---------------------------+------------+------------+---------+--------+------------------+-------------------------------+----------------------------+------------------------------+
    

    3. now delete the Spanish translation on https://drupal-dev.ddev.site/en/node/9/translations

    4. Now query the table again - for me I get the same result for node_revision__field_summary - the Spanish language revision data is still there. Although node_field_data now just has one row for English as expected.

    If we don't remove old translation content when we delete translations, how can we prevent people from removing languages until it's removed??

  • 🇷🇴Romania vasike Ramnicu Valcea

    @catch: for me, this sounds for me a different issue

    there was mentioned an issue about deleting revisions https://www.drupal.org/project/drupal/issues/2925532

    which also has a related issue about Deleting a translation issue
    https://www.drupal.org/project/drupal/issues/2815779 🐛 Deleting a translation leaves behind orphaned revisions Needs work

    is this the issue you're talking about?

    thanks

  • Status changed to Postponed 12 months ago
  • 🇬🇧United Kingdom catch

    The MR does this:

    public static function languageUsedByContent($langcode) {
        $entity_type_manager = \Drupal::entityTypeManager();
        foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) {
          if ($entity_type instanceof ContentEntityType && $entity_type->isTranslatable()) {
            $query = $entity_type_manager->getStorage($entity_type_id)->getQuery()
              ->condition($entity_type->getKey('langcode'), $langcode)->accessCheck(FALSE);
            if ($entity_type->isRevisionable()) {
              $query->allRevisions();
            }
            $results = $query->range(0, 1)->execute();
    
            if ($results) {
              return TRUE;
            }
          }
        }
        return FALSE;
    

    That query checks all revisions. 🐛 Deleting a translation leaves behind orphaned revisions Needs work means deleting a translation leaves revisions in the a language - so it will be impossible to get to a point where the language can be removed.

    I think that means this is postponed on 🐛 Deleting a translation leaves behind orphaned revisions Needs work , or if not, then we should add test coverage showing how the site admin is able to remove the necessary content in order to be able to delete the language.

  • 🇷🇴Romania vasike Ramnicu Valcea

    added https://www.drupal.org/project/drupal/issues/2815779 🐛 Deleting a translation leaves behind orphaned revisions Needs work as related issue

Production build 0.69.0 2024