Problem/Motivation

language_save() and language_delete() are remnants of the pre ConfigurableLanauge config entity days and should be removed.

#2334763: Tidy up of LanguageInterface - removal of setters and other unnecessary methods is postponed on this.

Proposed resolution

Remove the function and refactor code to use ConfigurableLanguage::save() and ConfigurableLanguage::delete()

language_save() and language_delete() where introduced in Drupal 8 so the change record (https://www.drupal.org/node/2336669) is for Drupal 8 to Drupal 8.

Remaining tasks

  • (done) review
  • (done) change record draft
  • Commit.

User interface changes

None

API changes

Removal of language_save() and language_delete().
Minor change in language add/edit form (name => label) to reflect the property naming of ConfigurableLanguage.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

alexpott’s picture

Status: Active » Needs review
FileSize
98.32 KB

Status: Needs review » Needs work

The last submitted patch, 1: 2336177.1.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
4.06 KB
98.48 KB

Fix tests and tidy a couple of config language creations in tests up a bit.

alexpott’s picture

The only two issues I think we need to decide is:

  • Where to put the logging of language creation and deletion - I moved it to the forms but the more I think about it I think ConfigurableLanguage::postSave() ConfigurableLanguage::postDelete() are the correct places.
  • The protection against deleting locked languages through the API should be removed - this protection exists in language_delete() in HEAD but not in ConfigurableLanguage::preDelete(). Moving it to ConfigurableLanguage::preDelete() makes the language module uninstallable. I think we should just remove it.
alexpott’s picture

Re:logging - actually maybe the form is best - that is where we do it for node types.

Gábor Hojtsy’s picture

Reviewing #3. This results in mostly much nicer code, yay :) Especially like the more standard entity form for language. As for where we log, I don't think we have a standard. I don't think we answered questions like "Do we want to log all the changes separately one by one when a config import happens?". We may want to ask that question sometime. Historically we have not been very consistent with our logging unfortunately.

  1. +++ b/core/modules/language/src/Entity/ConfigurableLanguage.php
    @@ -222,9 +228,29 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie
    +//    @todo Prevents language uninstall :) since locked language entities are
    +//    deleted on uninstall.
    +//      if ($entity->locked) {
    +//        throw new \Exception('Can not delete a locked language ' . $entity->id());
    +//      }
    

    Right, we can remove it here. Do we have tests / core to ensure locked languages cannot be deleted through the UI though? Yes they don't appear on the list/form but they still have a delete URL that should throw a 403 or 404.

  2. +++ b/core/modules/language/src/Entity/ConfigurableLanguage.php
    @@ -319,4 +345,31 @@ public function setNegotiationMethodId($method_id) {
    +      return \Drupal::entityManager()->getStorage('configurable_language')->create(array(
    ...
    +      return \Drupal::entityManager()->getStorage('configurable_language')->create(array(
    

    Why not use static create on the class here? Like everywhere else.

  3. +++ b/core/modules/language/src/Form/LanguageDeleteForm.php
    @@ -107,13 +107,11 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +    \Drupal::logger('language')->notice('The %language (%langcode) language has been removed.', $t_args);
    

    Forms have an injected logger, no? Your other changes to forms use $this->logger

alexpott’s picture

FileSize
3.04 KB
97.96 KB

Thanks @Gábor Hojtsy. Fixed everything from #6.

andypost’s picture

Awesome clean-up!
Suppose logging needs separate issue to unify (prefer forms because this is a place where visitors interacts by changing data).
Locking of config seems still tricky, the node-types is another example.
Maybe better to provide an actual locking of config with dependencies somehow

+++ b/core/modules/language/src/Entity/ConfigurableLanguage.php
@@ -228,11 +228,6 @@
-//    @todo Prevents language uninstall :) since locked language entities are
-//    deleted on uninstall.

is there other way to prevent delete or module uninstall?

alexpott’s picture

Re #8 well it's certainly not for this issue to address. Using LanguageDeleteForm to delete a locked language because it only allows deleting of languages with LanguageInterface::STATE_CONFIGURABLE - ie. not locked languages.

alexpott’s picture

Added #2336689: Test that a locked language can not be deleted through the UI to test deleting of locked languages through UI since this issue does not change anything in that area but we are lacking test coverage.

alexpott’s picture

Issue summary: View changes
Gábor Hojtsy’s picture

Title: Remove language_save() and language_delete() » ConfigurableLanguage cannot be created/removed directly, integrate language_save() and language_delete()
Category: Task » Bug report
Issue summary: View changes
Status: Needs review » Reviewed & tested by the community
Issue tags: +sprint

Yeah I tested admin/config/regional/language/delete/und manually too and it throws a 403 perfectly. I certainly don't have any more concerns with #7 and I reviewed the change notice draft as well on IRC and made suggestions for improvements. Looks good to me.

Also reclassifying and retitling as a bug, since the after-effects code in language_save and language_delete now non-standard wrapper functions makes it impossible to use ConfigurableLanguage to create and delete them without needing to take care of the after effects separately. Once we integrate them into the config entity, the after effects happen properly on eg. config import or any other use of the API as well.

alexpott’s picture

Priority: Normal » Major
Issue tags: +beta target

Yep I agree that actually this is a bug due to the after effects. Also this should be a major beta target since correct usage of CMI is important for D8's success.

alexpott’s picture

YesCT’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
98.55 KB
1.5 KB

I saw this was just a re-roll... so read through it hoping to just re-RTBC it. but... since I read through it:

0.
one thing to note is that so many 'name' to 'label' changes are because the object is changing from Language to ConfigurableLanguage and ConfigurableLanguage uses the 'label' property, not 'name'. ok.

  1. +++ b/core/modules/language/language.module
    @@ -679,10 +604,9 @@ function language_form_system_regional_settings_alter(&$form, FormStateInterface
    +  $default_language->set('default', TRUE);
    

    this is in here a few times. shouldn't we use a setDefault() ?
    Probably a separate issue.

  2. +++ b/core/modules/language/src/Entity/ConfigurableLanguage.php
    @@ -225,6 +231,21 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie
    +  public static function postDelete(EntityStorageInterface $storage, array $entities) {
    

    needs a doc block.
    nit. fixed.

  3. +++ b/core/modules/language/src/Entity/ConfigurableLanguage.php
    @@ -225,6 +231,21 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie
    +
    +
    

    extra newline.
    nit.
    fixed.

  4. +++ b/core/modules/language/src/Entity/ConfigurableLanguage.php
    @@ -319,4 +340,31 @@ public function setNegotiationMethodId($method_id) {
    +   * @param string $langcode
    +   *
    

    add the doc line.
    fixed.
    also added the @return for this new method.

  5. +++ b/core/modules/language/src/Form/LanguageAddForm.php
    @@ -148,4 +139,25 @@ public function validatePredefined($form, FormStateInterface $form_state) {
    +
    +
    

    extra space.
    nit.
    fixed.

  6. +++ b/core/modules/language/src/Tests/Condition/LanguageConditionTest.php
    @@ -44,17 +44,8 @@ protected function setUp() {
    -    $language = new Language(array(
    -      'id' => 'it',
    -      'name' => 'Italian',
    -      'direction' => Language::DIRECTION_LTR,
    -    ));
    -    language_save($language);
    +    ConfigurableLanguage::createFromLangcode('it')->save();
    

    just noting that in a bunch of places, this kind of simplification was done. Probably the test did not rely on the name or the direction. So now they are not customized. (create defaults to re-using the langcode as the label if label was not specified).

  7. +++ b/core/modules/language/src/Tests/LanguageConfigurationElementTest.php
    @@ -85,11 +84,10 @@ public function testDefaultLangcode() {
    -    $old_default->default = FALSE;
    -    language_save($old_default);
    -    $new_default = \Drupal::languageManager()->getLanguage('cc');
    -    $new_default->default = TRUE;
    -    language_save($new_default);
    +
    +    $new_default = ConfigurableLanguage::load('cc');
    +    $new_default->set('default', TRUE);
    +    $new_default->save();
    

    I was wondering about this set default. why dont we have to unset the old default?

  8. +++ b/core/modules/language/src/Tests/LanguageDependencyInjectionTest.php
    @@ -66,8 +65,9 @@ function testDependencyInjectedNewDefaultLanguage() {
    -    language_save($default_language);
    ...
    +    $default_language->set('default', TRUE);
    +    $default_language->save();
    

    was this broken before? why did you need to set the default to true?

  9. +++ b/core/modules/language/src/Tests/LanguageFallbackTest.php
    @@ -25,11 +25,9 @@ protected function setUp() {
    +      $language = ConfigurableLanguage::createFromLangcode($langcode);
    +      $language->set('weight', $i--);
    +      $language->save();
    

    are we going to eventually convert this to pass in the weight to the create method? or have a setWeight()?

  10. +++ b/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php
    @@ -88,15 +89,10 @@ function testUILanguageNegotiation() {
         $default_language = \Drupal::languageManager()->getDefaultLanguage();
    
    @@ -108,7 +104,9 @@ function testUILanguageNegotiation() {
    -    language_save($default_language);
    +    $default_language = ConfigurableLanguage::load($default_language->getId());
    +    $default_language->set('default', TRUE);
    +    $default_language->save();
    

    I'm not sure about this. why all this setting up of the default language to replace the language_save()? $default_language is set up above. maybe it doesn't need to be now?

    Or... hm.

  11. +++ b/core/modules/language/src/Tests/Views/LanguageTestBase.php
    @@ -26,11 +26,8 @@ protected function setUp() {
    -    // Create English and another language beside English.
    -    $language = new Language(array('id' => 'en'));
    -    language_save($language);
    -    $language = new Language(array('id' => 'xx-lolspeak', 'name' => 'Lolspeak'));
    -    language_save($language);
    +    // Create another language beside English.
    +    ConfigurableLanguage::create(array('id' => 'xx-lolspeak', 'label' => 'Lolspeak'))->save();
    

    ok. this is just taking out code creating english... which is silly, if the tests are a standard install, they will already have english.

  12. +++ b/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php
    @@ -31,15 +32,14 @@ public function setUp() {
    -    $new_language_default = new Language(array(
    +    ConfigurableLanguage::create(array(
           'id' => 'fr',
           'name' => 'French',
           'direction' => LANGUAGE::DIRECTION_LTR,
           'weight' => 0,
           'method_id' => 'language-default',
           'default' => TRUE,
    -    ));
    

    this does not match the same pattern as the other replacements. It is not using the set(weight). and it is not taking out the 'name' and using label, etc. hm. Maybe because this is followed by a save() and not by a drupalPostForm().

  13. +++ b/core/modules/locale/src/Tests/LocaleStringTest.php
    @@ -40,8 +40,7 @@ protected function setUp() {
    -      $language = new Language(array('id' => $langcode));
    -      $languages[$langcode] = language_save($language);
    +      ConfigurableLanguage::createFromLangcode($langcode)->save();
    

    ok. $languages was unused anyway.

  14. +++ b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php
    @@ -38,11 +38,10 @@ protected function setUp() {
    +      ConfigurableLanguage::create(array(
             'id' => $language_code,
    -        'name' => $this->randomMachineName(),
    -      ));
    ...
    +        'label' => $this->randomMachineName(),
    +      ))->save();
    

    hm. but this create is changing 'name' to 'label'...

  15. +++ b/core/modules/system/src/Tests/Entity/EntityTranslationFormTest.php
    @@ -36,12 +36,12 @@ protected function setUp() {
    -      $this->langcodes[$i] = $language->id;
    ...
    +      $this->langcodes[$i] = $language->id();
    

    this is good. but I'm not totally sure why it's necessary to change ->id to ->id() in this patch. but ok.

  16. +++ b/core/modules/system/src/Tests/Theme/TwigTransTest.php
    @@ -69,9 +70,9 @@ protected function setUp() {
    -    $language->default = TRUE;
    ...
    +    $language->set('default', TRUE);
    

    why? are we setting default to protected here? no. I dont understand this change.

  17. +++ b/core/modules/taxonomy/src/Tests/TermLanguageTest.php
    @@ -96,12 +95,9 @@ function testDefaultTermLanguage() {
    -    $old_default = \Drupal::languageManager()->getDefaultLanguage();
    -    $old_default->default = FALSE;
    

    why are we ok taking out unsetting the old default to false again?

  18. +++ b/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php
    @@ -56,12 +56,11 @@ public function testCreateView() {
    -      'name' => 'Italian',
    +      'label' => 'Italian',
    

    we are not taking out the setting of name here? we took it out in other places where the label/name did not matter.

alexpott’s picture

FileSize
1.6 KB
98.65 KB

1. I want to review which set methods belong on ConfigurableLanguage interface in #2334763: Tidy up of LanguageInterface - removal of setters and other unnecessary methods. Doing it this way means we can protect the properties and change the methods as we like.
6. Using the createFromLangcode method uses the predefined language information to populate the name/label
7. Because a language's defaultness is not actually saved to the language :) it saved on the container and there can be only one default (this is a massive simplification compared to previous Drupals)
8. Because in the original test the original default language was not reloaded. Now it is - meaning it is no longer the default language so we have to set it back
9. See answer to #1
10. See answer to #8
12. Fixed. In a way that removes as much unnecessary stuff as possible.
14. Yes label is the ConfigurableLanguage property that we want to set.
15. Because $language is now a config entity and this is how we access their IDs. Yes it is public for the ConfigurableLanguage but accessing it publicly is wrong.
16. Because $language is now a config entity and I don't want to access their properties publicly and this way does not and we can sort out the interface in #2334763: Tidy up of LanguageInterface - removal of setters and other unnecessary methods
17. See answer to #7
18. Because we are also setting the default property and this is not using createFromLangcode(). However changed to match how I resolve #12

Also I really don't think we should care too much about about configurable languages are created and their properties set in tests. This usage should not define their public APIs - if a method is only being used in tests it probably should not be there.

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

@YesCT: re point 0, yes the ConfigurableLanguage has a label instead of name so the API is a bit different. But @alexpott also fixed the form to reflect the ConfigurableLanguage properties, which is why $edit keys needed to change as appropriate also.

The minor cleanups in #15 are great, the further cleanups in #16 as well. I also agree with @alexpott's reasoning which is why there was so little in the end to correct from #7. So AFAIS back to RTBC.

YesCT’s picture

@alexpott Thanks a lot for the response.
Looks good to me also.

YesCT’s picture

hm berdir pointed out that this draft change record
https://www.drupal.org/node/2336669
got tweeted
https://twitter.com/drupal8changes/status/510226011301371904

I'm not sure where to report that.
#2292469: [meta] Improve change records doesn't mention the twitter bot.

[EDIT]
@tvn gave me the issue via irc: #2337375: Feed showing draft change records

YesCT’s picture

re #17

+++ b/core/modules/language/src/Form/LanguageFormBase.php
@@ -72,11 +73,11 @@ public function commonForm(array &$form) {
-    $form['name'] = array(
+    $form['label'] = array(
       '#type' => 'textfield',
       '#title' => $this->t('Language name in English'),
       '#maxlength' => 64,
-      '#default_value' => $language->label,
+      '#default_value' => $language->label(),
       '#required' => TRUE,

ah, I see here in the LanguageFormBase. :)

Gábor Hojtsy’s picture

YesCT’s picture

Issue summary: View changes
YesCT’s picture

YesCT’s picture

[edit: oops. posted to wrong issue.]

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.0.x, thanks!

  • catch committed 21a11cf on 8.0.x
    Issue #2336177 by alexpott, YesCT: Fixed ConfigurableLanguage cannot be...
Gábor Hojtsy’s picture

Issue tags: -sprint

Yay, thanks a lot! Published the change record.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.