trying to translate the contact category throws a fatal error "Call to a member function getLangcode()".

I tried to fix it, but the whole CMI / Routing debugging is quite hard.

Comments

andypost’s picture

Gábor Hojtsy’s picture

I don't see how that is related @andypost?

Gábor Hojtsy’s picture

So the reason for this failing is that the access() method on Drupal\config_translation\Access\ConfigNameCheck is invoked 5(!) yes FIVE times before the page callback gets invoked. The first four times there is actual parameters in $request->attributes, the fifth and last time there is none. That makes the route entity lookup code ($entity = $request->attributes->get($mapper->getType());) that should load the 'contact_category' type of entity from the route fail with a NULL, because there are no request attributes anymore. Then this trickles down into no category entity loaded, so the fail as shown on the issue happens.

We use the same technique for all other config entities, views, block placement entities, etc. So I don't see why this would fail and those not. Anything special with contact categories?

The request attributes we get in access() for the first 4 times:

object(Symfony\Component\HttpFoundation\ParameterBag)#262 (1) {
  ["parameters":protected]=>
  array(6) {
    ["system_path"]=>
    string(49) "admin/structure/contact/manage/feedback/translate"
    ["_controller"]=>
    string(83) "\Drupal\config_translation\Controller\ConfigTranslationController::itemOverviewPage"
    ["mapper"]=>
    object(Drupal\config_translation\ConfigEntityMapper)#270 (8) {
      ["base_path":"Drupal\config_translation\ConfigEntityMapper":private]=>
      string(49) "admin/structure/contact/manage/{contact_category}"
      ["path_id_index":"Drupal\config_translation\ConfigEntityMapper":private]=>
      int(4)
      ["title":"Drupal\config_translation\ConfigEntityMapper":private]=>
      string(23) "@label contact category"
      ["entity_type":"Drupal\config_translation\ConfigEntityMapper":private]=>
      string(16) "contact_category"
      ["config_prefix":"Drupal\config_translation\ConfigEntityMapper":private]=>
      string(16) "contact.category"
      ["menu_type":"Drupal\config_translation\ConfigEntityMapper":private]=>
      int(132)
      ["add_edit_tab":"Drupal\config_translation\ConfigEntityMapper":private]=>
      bool(false)
      ["route_name":"Drupal\config_translation\ConfigEntityMapper":private]=>
      string(73) "config_translation.item.admin.structure.contact.manage._contact_category_"
    }
    ["contact_category"]=>
    object(Drupal\contact\Plugin\Core\Entity\Category)#268 (14) {
      ["id"]=>
      string(8) "feedback"
      ["uuid"]=>
      string(36) "b5ce38dd-855b-4445-ae00-adb88ee6d63e"
      ["label"]=>
      string(16) "Website feedback"
      ["recipients"]=>
      array(1) {
        [0]=>
        string(0) ""
      }
      ["reply"]=>
      string(0) ""
      ["weight"]=>
      string(1) "0"
      ["originalID":protected]=>
      string(8) "feedback"
      ["status"]=>
      string(1) "1"
      ["langcode"]=>
      string(2) "en"
      ["entityType":protected]=>
      string(16) "contact_category"
      ["enforceIsNew":protected]=>
      NULL
      ["newRevision":protected]=>
      bool(false)
      ["isDefaultRevision":protected]=>
      bool(true)
      ["rdf_mapping"]=>
      array(0) {
      }
    }
    ["_route"]=>
    string(73) "config_translation.item.admin.structure.contact.manage._contact_category_"
    ["_route_object"]=>
    object(Symfony\Component\Routing\Route)#269 (8) {
      ["path":"Symfony\Component\Routing\Route":private]=>
      string(60) "/admin/structure/contact/manage/{contact_category}/translate"
      ["host":"Symfony\Component\Routing\Route":private]=>
      string(0) ""
      ["schemes":"Symfony\Component\Routing\Route":private]=>
      array(0) {
      }
      ["methods":"Symfony\Component\Routing\Route":private]=>
      array(0) {
      }
      ["defaults":"Symfony\Component\Routing\Route":private]=>
      array(2) {
        ["_controller"]=>
        string(83) "\Drupal\config_translation\Controller\ConfigTranslationController::itemOverviewPage"
        ["mapper"]=>
        object(Drupal\config_translation\ConfigEntityMapper)#270 (8) {
          ["base_path":"Drupal\config_translation\ConfigEntityMapper":private]=>
          string(49) "admin/structure/contact/manage/{contact_category}"
          ["path_id_index":"Drupal\config_translation\ConfigEntityMapper":private]=>
          int(4)
          ["title":"Drupal\config_translation\ConfigEntityMapper":private]=>
          string(23) "@label contact category"
          ["entity_type":"Drupal\config_translation\ConfigEntityMapper":private]=>
          string(16) "contact_category"
          ["config_prefix":"Drupal\config_translation\ConfigEntityMapper":private]=>
          string(16) "contact.category"
          ["menu_type":"Drupal\config_translation\ConfigEntityMapper":private]=>
          int(132)
          ["add_edit_tab":"Drupal\config_translation\ConfigEntityMapper":private]=>
          bool(false)
          ["route_name":"Drupal\config_translation\ConfigEntityMapper":private]=>
          string(73) "config_translation.item.admin.structure.contact.manage._contact_category_"
        }
      }
      ["requirements":"Symfony\Component\Routing\Route":private]=>
      array(1) {
        ["_config_translation_config_name_access"]=>
        string(4) "TRUE"
      }
      ["options":"Symfony\Component\Routing\Route":private]=>
      array(2) {
        ["compiler_class"]=>
        string(34) "\Drupal\Core\Routing\RouteCompiler"
        ["_access_checks"]=>
        array(1) {
          [0]=>
          string(31) "config_translation.access_check"
        }
      }
      ["compiled":"Symfony\Component\Routing\Route":private]=>
      object(Drupal\Core\Routing\CompiledRoute)#272 (12) {
        ["fit":protected]=>
        int(61)
        ["patternOutline":protected]=>
        string(43) "/admin/structure/contact/manage/%/translate"
        ["numParts":protected]=>
        int(6)
        ["route":protected]=>
        *RECURSION*
        ["variables":"Symfony\Component\Routing\CompiledRoute":private]=>
        array(1) {
          [0]=>
          string(16) "contact_category"
        }
        ["tokens":"Symfony\Component\Routing\CompiledRoute":private]=>
        array(3) {
          [0]=>
          array(2) {
            [0]=>
            string(4) "text"
            [1]=>
            string(10) "/translate"
          }
          [1]=>
          array(4) {
            [0]=>
            string(8) "variable"
            [1]=>
            string(1) "/"
            [2]=>
            string(6) "[^/]++"
            [3]=>
            string(16) "contact_category"
          }
          [2]=>
          array(2) {
            [0]=>
            string(4) "text"
            [1]=>
            string(31) "/admin/structure/contact/manage"
          }
        }
        ["staticPrefix":"Symfony\Component\Routing\CompiledRoute":private]=>
        string(31) "/admin/structure/contact/manage"
        ["regex":"Symfony\Component\Routing\CompiledRoute":private]=>
        string(75) "#^/admin/structure/contact/manage/(?P<contact_category>[^/]++)/translate$#s"
        ["pathVariables":"Symfony\Component\Routing\CompiledRoute":private]=>
        array(1) {
          [0]=>
          string(16) "contact_category"
        }
        ["hostVariables":"Symfony\Component\Routing\CompiledRoute":private]=>
        array(0) {
        }
        ["hostRegex":"Symfony\Component\Routing\CompiledRoute":private]=>
        NULL
        ["hostTokens":"Symfony\Component\Routing\CompiledRoute":private]=>
        array(0) {
        }
      }
    }
  }
}

However, the 5th:

object(Symfony\Component\HttpFoundation\ParameterBag)#263 (1) {
  ["parameters":protected]=>
  array(0) {
  }
}

I don't understand either why it is invoked 5 times, that may or may not be normal.

Crell’s picture

It should only be invoked once by the routing system. However, it may be invoked N times by menu blocks. (This is a known *cry*.) Why the 5th time it has no request I don't know. Perhaps check the backtraces to see where it's coming from? (Callstack in an IDE would be very helpful here.)

Ryan Weal’s picture

Title:Contact Category translation fails with "Call to a member function getLangcode()"» Contact Category and Block config translation fails

This also applies to blocks, which give the error when trying to create a translation for the config entity.

Fatal error: Call to a member function getLanguageWithFallback() on a non-object in /var/www/drupal/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigNameCheck.php on line 34

Ryan Weal’s picture

Same error with defining custom block types... go to Structure > Custom block types > Edit (note: translation option missing) > Translate.

Fatal error: Call to a member function getLanguageWithFallback() on a non-object in /var/www/drupal/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigNameCheck.php on line 34

Ryan Weal’s picture

Filter formats > Basic HTML > Translate: Access denied

Filter formats > Full HTML > Translate: Fatal error: Call to a member function getLanguageWithFallback() on a non-object in /var/www/drupal/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigNameCheck.php on line 34

Ryan Weal’s picture

Config > Shortcuts > List links:
Fatal error: Call to a member function getLanguageWithFallback() on a non-object in /var/www/drupal/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigNameCheck.php on line 34

Ryan Weal’s picture

"System maintenance" is translatable in the string listing but does not have a tab.

Gábor Hojtsy’s picture

So the getLanguageWithFallback() problem seems to be down to breadcrumbs. It comes from menu_set_active_trail() from menu.inc doing:

<?php
         
if (strpos($link['href'], '%') !== FALSE) {
           
_menu_link_translate($link, TRUE);
          }
?>

which results in the following call:

<?php
     
if ($route = $item->getRoute()) {
       
$request = Request::create('/' . $item['path']);
       
$item['access'] = drupal_container()->get('access_manager')->check($route, $request);
      }
?>

so *this* request will not have the arguments (in _menu_link_translate()), whatever $request object this code creates does not have the attributes. Seems like this is invoked for admin/help, admin/structure/custom-blocks and admin/structure/views and admin/structure/contact/manage/%/translate on this path. The last results in our access checker being invoked. The $request->attributes here are:

object(Symfony\Component\HttpFoundation\ParameterBag)#264 (1) {
  ["parameters":protected]=>
  array(0) {
  }
}

So looks like /%/ in the path lost the entity name (I assume due to the menu table storage based on my pre-D8 experience?) and then the request creation will not find the entity in there. I also tried however making it create a Request for admin/structure/contact/manage/%contact_category/translate here, and that did not work either. So not sure % with the missing entity type name is the problem.

Gábor Hojtsy’s picture

Status:Active» Postponed

All right, the problem is in core in #2004780: Unify menu item access checking with mock menu items. Got some advice from timplunkett and Crell on that.

Ryan Weal’s picture

The patch in #11 fixes the original issue (contact), in addition to #5, #6, and #7b (seems #7a might be something else).

Going to continue testing the other cases listed in config_translation.module...

Edit: #8 is NOT fixed but probably same/similar issue.

Ryan Weal’s picture

Ryan Weal’s picture

The latest version of the patch I referenced in #13 gives access denied on block config translation.

Gábor Hojtsy’s picture

Status:Postponed» Active

#2004802: Basic HTML and plain HTML should ship as English configuration got committed. It would be a good idea to check these again. #2004710: Add tests for block, menu, vocabulary and views listings adds actual test coverage, so we'll not need to check manually later. But for now...

Gábor Hojtsy’s picture

Status:Active» Postponed (maintainer needs more info)

Waiting on info :)

Ryan Weal’s picture

Assigned:Unassigned» Ryan Weal
Status:Postponed (maintainer needs more info)» Needs review

I'll take a look at this. If you don't hear back from me within a week, unassign!

Gábor Hojtsy’s picture

vijaycs85’s picture

Title:Contact Category and Block config translation fails» Block config translation fails
Assigned:Ryan Weal» Unassigned
Status:Fixed» Needs review

I have verified in clean installation and found tat contact category translation working without any problem. However block translation page throwing access defined. After few debug steps, found the problem is because of $group->hasTranslatable() is returning false in configNameCheck.php.

However none of them being wrong there (i.e. schema and config data etc).

Changing title according to current defect.

Gábor Hojtsy’s picture

Maybe langcode is not 'en' on it in config?

vijaycs85’s picture

Status:Needs review» Closed (works as designed)
StatusFileSize
new74.86 KB
new57.02 KB

Looks like the problem is the schema itself :) More details at #2035839: Align block config schema with block configuration and patch at #2035839-1: Align block config schema with block configuration fixes the problem as per attached screenshots. So no code fix in config_translation.