Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/lai-team/bv_admin_widgets/archive/refs/heads/main.zipAdds a WordPress dashboard widget letting each user pick up to three preferred
languages, stored in the bv_user_languages user meta.
Requirements
- WordPress (multisite in this install, but nothing here depends on it)
- TranslatePress —
TRP_Languages::get_wp_languages()supplies the language list. Guarded: without it the widget shows a notice, hides the save button, and the save handler refuses to write.
What it does
| Hook | wp_dashboard_setup → bv_add_dashboard_widgets() |
| Widget slug | bv_dashboard_widget |
| Stores | bv_user_languages user meta, an array of native language names |
| Limit | BV_LANGUAGE_LIMIT, index.php:23, currently 3 |
| Save hook | admin_init → bv_edit_user_languages_be() |
Layout
index.php Plugin header, constant, widget registration and rendering
inc/handle_requests.php Save handler
Nothing else. This plugin has no external consumers — no file outside it references any of its functions, its constant or its meta key.
Why the stored value is a language name, not a code
bv_user_languages holds native language names ("Deutsch"), not codes
(de_DE). That is pre-existing: the <option> tags originally carried no
value attribute at all, so the browser submitted the option's text content.
The value is now explicit and is the same string, so existing saved meta keeps
matching.
It is still the wrong thing to store — it breaks if a language's native name
changes, and it is display text in a data field. Switching to codes needs a
migration over existing bv_user_languages meta, so it is deliberately not
done here.
Changed in this pass
Deleted 545 lines of dead vendored code. Everything under inc/ except
handle_requests.php, plus assets/js/trp-sortable-languages.js, was
unreachable — index.php only ever required handle_requests.php. The files
were stale copies of TranslatePress and its commercial Extra Languages add-on,
both of which are installed and active in their own right:
class-extra-languages.php,tp-extra-languages.php,language-selector-pro.php— byte-identical totp-add-on-extra-languagesmain-settings-page.php,main-settings-language-selector.php— copies of TranslatePress partials from an older release, predating upstream's Formality and custom-language support. Checked for local modifications: there were none.assets/js/trp-sortable-languages.js— also ships in two other plugins
Deleting them also removes a latent three-way fatal redeclare: the copies
defined trp_el_run() and TRP_EL_PLUGIN_DIR, which
bv_translatepress_menu and tp-add-on-extra-languages also define. It was
only ever dormant because nothing loaded them. inc/main-settings.php, which
did not even parse — TranslatePress class methods pasted at file scope, so
public function and $this sat outside any class — went with them.
Secured the save path. It previously inspected $_POST at file scope on
every request, declared a function inside that if, and wrote
$_POST['bv_languages_select'] straight into user meta with no nonce, no
capability check and no validation. Now:
- the hook is registered unconditionally and checks inside, on
admin_init check_admin_referer()against awp_nonce_field()in the formis_user_logged_in()(the bar is deliberately low — it only ever writes the acting user's own meta; the nonce is what stops a third-party page driving it)- every submitted value is
sanitize_text_field()'d and must appear in the real language list, so unknown or injected strings are dropped - the array is truncated to
BV_LANGUAGE_LIMIT - with TranslatePress absent there is nothing to validate against, so it bails rather than storing unvalidated input or wiping what is saved
Removed the var_dump( get_current_user_id() ) that printed into the admin
page on every save.
Fixed three PHP warnings per dashboard load. get_user_meta( …, true )
returns '' when unset and the old code took a string offset of it —
Uninitialized string offset 0/1/2, for every user who had never saved.
Confirmed by running the old file: three warnings, gone in the new one.
Closed the <select>. index.php:39 echoed "<select>" where it meant
</select>, so the widget emitted six opening tags and no closing ones.
Other: options now carry explicit value attributes; native_name is
escaped on output; BV_LANGUAGE_LIMIT is defined()-guarded; TRP_Languages
is instantiated once per render instead of once per dropdown; and the plugin
header no longer claims to be a map plugin credited to another author with a
Plugin URI pointing at digfish/geotagged-media.
The widget title was the boilerplate "Example Dashboard Widget" under the
wporg text domain. It is now "My Languages" under bv-admin-widgets.
This is a user-visible string change, unlike everything else here.
Known issues
- The stored value is display text rather than a language code — see above.
- Nothing reads
bv_user_languages. The widget records a preference that no other code in the install acts on; whatever was meant to consume it was never written, or lives outside this install.
License
GPL-2.0-or-later. See LICENSE.
Credits
Lai Consulting Team; original scaffolding by Gentrit Biba.
Previously hosted at gitlab.com/beauvoyage/bv_admin_widgets.