Conversation
Every role named in a menu link's show_role was stored correctly, but the
target routes denied most of them, so the links never rendered: global_admin
was dead on seven links, sme on five, and a plain authenticated user saw
nothing at all.
Grant the missing permissions instead of trimming the menu, so the approved
spec and the effective access finally agree:
global_admin administer languages, administer users, create country group
reviewer administer users, create media
se administer users
sme accept translation jobs, the 15 create-content permissions
editor and se already hold
translator the same 15 create-content permissions
Widen the two report views to match the links that point at them:
country_reports admits authenticated, users_reports admits se.
Give Manage Reports the authenticated and sme roles its children already
grant. menu_per_role hides a parent whose show_role omits a role, taking the
whole subtree with it, so a parent must always cover its children.
Drop the Copy keywords link: its route demands administer site configuration,
and handing that to global_admin and translator would open every admin config
form on the site.
Roles carry no per-site split override, so these files apply to all seven
sites. Menu links are content entities and do not move on config import --
each environment needs drush bebbo:menu-sync after cim.
Editorial Menu Fixes
…page
A user restricted to a set of languages could not open the translation
they were assigned. Clicking Edit on their own language returned Access
denied, while the same form opened fine once a language prefix was added
to the URL.
ContentTranslationAccessCheck read the {target} route parameter, which
only the add route carries. On the edit and delete routes {target} is
empty, so the check fell back to the current content language and tested
the language of the page instead of the language of the translation.
That cut both ways. Under /en it denied every language, including the
ones the user was assigned. Under an assigned language prefix it allowed
every language, including ones the user was never assigned, so a
translator browsing /ro-ro could open the Bulgarian or Slovak
translation.
Read {language} when the route supplies it and fall back to {target}
otherwise, so the add route keeps its existing behaviour and the edit and
delete routes finally test the translation they are acting on.
Verified on all seven sites with real accounts: editing an assigned
language is allowed, editing an unassigned one is forbidden, and adding a
translation is unchanged. On Pakistan no account is assigned an installed
language, so the fix shows there as en allowed and ur forbidden for an
en-assigned user, which the old code would have allowed.
Still open and not addressed here: core forbids updating the default
translation, so a per-language original still offers no Edit link without
node update access, and the sme role holds translate any entity but not
update content translations, which the overview controller requires
before it will render an Edit link at all.
fix(allowed_languages): check the language being translated, not the page
Every top-level entry that owns children becomes an unclickable, expanded group, so the menu reads as eleven sections rather than a flat list of links that happened to have children hanging off them. Eleven top-level entries, twenty-seven children, thirty-eight links in total. Dashboard and Redirect Management keep their paths because nothing sits under them; the other nine parents move to route:<nolink> with expanded set, and their old destinations survive on a child. Manage Media, View Taxonomies, User List and Add user were top-level and are now children of Media, Taxonomies and Users. The taxonomy feed links flatten into Taxonomies alongside View Taxonomies. Content overview is new. Manage Translation Jobs becomes Translation Overview and moves from /admin/tmgmt/job_items to /admin/tmgmt/jobs, which is a real route (view.tmgmt_job_overview.page_1) despite an earlier note claiming otherwise. Google Analytics is gone; the module is not installed and its path never resolved. Every parent carries the union of its own roles and its children's, because menu_per_role hides a parent whose show_role omits a role and takes the subtree with it. That adds administrator to Content, Translation and Taxonomies, reviewer to Users, and authenticated plus sme to Reports. Surviving links keep their UUIDs so bebbo:menu-sync updates in place instead of deleting and recreating; only the four genuinely new entries get fresh ones. Verified on bebbo: cim clean, menu-sync reports 4 created, 31 updated, 1 deleted, and a second run reports no changes at all. A per-role tree build finds no role named in a show_role that cannot reach its link. /admin/tmgmt is the one path that leaves the menu entirely, since the Translations parent no longer carries it and no child covers it.
…ruth Two mechanisms could write the editorial menu and only one was authoritative, which is an easy way to lose a menu: bebbo:menu-sync applies bebbo_custom_general.editorial_menu, while menu_export offered an admin screen that could export or import the same links from its own snapshot. menu_export.export_data was already deleted, so the module had nothing left to import from, but the module and its settings were still installed and still pointed at editorial-menu. Uninstall it and drop menu_export.settings so the admin path disappears with it. The package stays in composer.json for now, patch included, so the module remains on disk and can be reinstated by re-adding it to core.extension. Also drop update hooks 10002 and 10003 from bebbo_custom_general. Both read menu_export.export_data and neither can run again: every local site is at schema 10004 or 10005, and stage and prod both report no database updates required. There is no hook_install, so a fresh install starts above them too. Hook 10001 is unrelated and stays. Verified: cim uninstalls the module cleanly on all seven local sites and the editorial menu is untouched by its removal.
…orts Seven editorial report paths were readable without logging in. Confirmed against stage: /keyword-link-to-content, /keyword-notlink-to-content and /taxonomy-export all returned 200 to an anonymous request, as did /keyword-term-english, /keyword-link-to-content-group and /keyword-og, which are not in the menu at all. /taxonomy-data-export went further and started a batch export job for an unauthenticated caller. Both views granted access by the 'access content' permission, which the anonymous role holds, and every display inherits that default, so a handful of menu links dragged four more paths into public view with them. Switch both views to role access matching what the menu already claims: administrator, editor and se. The menu grants Taxonomy Report and the two keyword links to editor and se only, so global_admin and reviewer lose access here by design. Verified on all seven sites: every one of the 29 linked editorial menu paths is now denied to anonymous, the four unlinked paths with it, while editor and se keep access. The three country-site config_split patches for keyword_term_count touch filters only and never access, so the fix survives on every site.
Mailer and Email TFA settings were reachable only by full administrators. Delegating them meant handing out an admin account, and the TFA email step makes recovery circular: when Office365 mail dies, the only people who can fix the mailer are the ones locked out by the OTP it can no longer send. mfa_admin holds administer mailer plus administer email tfa and nothing else, and is listed in email_tfa ignore_role so its holders log in without the OTP step — a way back in when mail is down. Scoped to the mailer and TFA settings pages only. The config translation tab is not included: core's translate configuration permission is global and would grant translating every config object on the site.
Role tables and the Email TFA sections still described nine roles and an empty ignore_role, so the documented recovery path was "there isn't one".
The category vocabulary in the taxonomies endpoints returned field_type_of_article as the referenced term's localized label, so clients had to match on "Article for birth to 6 years" and again on every translation of it. Every sibling key in the same payload is already a machine name. The type_of_article vocabulary carries no fields at all, so there is no field_unique_name to read. The value is derived from the English label with the same slug rules the endpoints already apply to terms whose field_unique_name is empty, which is why those rules move to the shared trait rather than gaining a third and fourth copy. The label JOINs now prefer English over the requested language, since the machine name must not change with langcode. Breaking for clients reading the label. V1 and V2 change together to keep the two payloads identical.
The category term shape still showed a localized label, which reads as if
the value tracks {lang}.
Non-English /api/articles and /v2/api/articles cold renders took 3-5x longer than English and hit the request time limit on stage. Two core behaviours combined: - EntityRepository::getTranslationFromContext() runs the full language fallback negotiation whenever the loaded entity's active language differs from the requested one, even when that translation exists. Nodes load with their original language active, so every non-English field render made content_translation walk all ~24 translations with access checks. - The entity_reference_entity_id formatter loads every referenced entity (hundreds of nodes and terms per listing) just to print its ID, and runs the same fallback for each target. Row entities are now switched to the requested translation before Views renders them, and the ID-only reference fields use a formatter that reads target_id without loading the target. Output is unchanged; measured on the ru listing: CPU 41.5s -> 6.8s, peak memory 2.0GB -> 0.56GB.
The per-node fragment cache no longer earns its keep: Views renders every row of the result on the first field access, so one stale fragment cost a full cold render anyway, and the response-level tag stack (bebbo_api_tag views cache plugin + per-bundle, per-language listing tags fired from hook_node_update) together with the contrib warmer now cover both caching and invalidation. Rows on the article displays still render in an isolated render context so per-entity node tags never reach the response: acquia_purge turns response tags into CDN Surrogate-Keys and hundreds of them overflow the header. Removes RowFragmentCache, its kernel test, the bebbo_api_fragment bin and its database-backend pin, and the fragment-only language tags.
Articles cache fixes
The v1 API listings render in 13+ minutes cold on the default site, so
the caches have to be filled before the app asks for them. The contrib
warmer could not do it: its queue drained 60 seconds a day against a
list of nearly a thousand URLs, and its URL lists were 967 hand-written
config lines that went stale whenever a language changed.
Derive the list instead. Every v1 path that takes a langcode, crossed
with the languages each site serves, plus one update check per country
group. The paths are shared because all four v1 views live in
config/sync and are identical everywhere; the languages and group IDs
come from the site being warmed.
bebbo:warm-all is what a single Acquia scheduled job calls. It walks the
sites in turn, spawning one bootstrapped subprocess per site with that
site's public hostname as --uri: a Drush process can only bootstrap one
site, and the warm URLs resolve against --uri, so passing the site
directory would build http://bangladesh/api/... and resolve nowhere. A
lock refreshed at each site boundary keeps two passes from fighting over
the same PHP-FPM workers, and expires in an hour so a killed pass does
not block the next one for the length of a whole pass.
Pass and fail are judged on the JSON envelope, not the HTTP status: the
v1 API answers 200 OK with {"status": 403} when it refuses a request, so
an HTTP-only check would record a warm cache that never happened.
Runs land in bebbo_warmer_log on the site that was warmed, whether they
came from the scheduled job or the button on the form, with start and
finish times, duration, status and the URLs that failed.
bebbo:warm-all replaces it. Keeping both would mean two warmers racing for the same PHP-FPM workers against two different URL lists, one of them the 967 hand-written config lines this deletes. Only the configuration goes here. The modules have to be uninstalled by config import on each environment while their code is still on disk, so drupal/warmer stays in composer.json until this has been deployed everywhere; removing the package first would leave cim unable to uninstall anything. Deleted with it: the per-site URL lists in all six splits, their complete_list entries, and the ultimate_cron job that enqueued the warmer every two hours. That job was disabled anyway, and its queue drained sixty seconds a day, which is why the caches were never warm.
PHPCS reads .install through phpcs.xml.dist, which the ad-hoc --standard=Drupal,DrupalPractice invocation does not, so this slipped past the checks run before the commit.
Update API warmer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.