Skip to content

Commit 45e720a

Browse files
authored
Fix translation registration for admin interface (#304)
* use bash instead of sh for bin/setup * make add-hosts.sh work better in a non-Mac developemnt environment * Update apache2-custom.sh to improve file ownership handling * change default for SKIP_PLUGIN_CHOWN * fix setup script * fix bug in translations registration and test it
1 parent da29d7b commit 45e720a

6 files changed

Lines changed: 249 additions & 19 deletions

File tree

bin/setup

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/sh
1+
#!/bin/bash
22

33
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
44
source $DIR/../.env
@@ -7,9 +7,9 @@ source $DIR/.resolve-container.bash
77

88
if [ ! -f .env.local ]; then
99
echo "Missing local env file: .env.local"
10-
echo "\tIt must exist and set env var WP_ADMIN_EMAIL to some real email address."
11-
echo "\tThis is the address WordPress will use for its admin email."
12-
echo "\tIt's not checked in to git. It's .gitignore'd."
10+
echo " It must exist and set env var WP_ADMIN_EMAIL to some real email address."
11+
echo " This is the address WordPress will use for its admin email."
12+
echo " It's not checked in to git. It's .gitignore'd."
1313
exit 1
1414
else
1515
. .env.local
@@ -21,7 +21,37 @@ if [ "$?" != "0" ]; then
2121
echo "WARNING: your docker container will not resolve the wp.test hostname"
2222
fi
2323

24+
# The install request below (and, later, your browser) reach WordPress by the
25+
# WP_DOMAIN host name. add-hosts.sh above only maps it *inside* the container;
26+
# it does nothing for this host. curl works around that with --resolve below, so
27+
# setup succeeds regardless. Your browser has no such shortcut, though, so warn
28+
# when WP_DOMAIN isn't mapped on this host to avoid a silent "can't reach the
29+
# site" foot gun. See DEVELOPMENT.md, "Add the wp.test host name".
30+
WP_HOST="${WP_DOMAIN%%:*}"
31+
32+
host_resolves() {
33+
if command -v getent >/dev/null 2>&1; then
34+
getent hosts "$1" >/dev/null 2>&1 && return 0
35+
fi
36+
grep -qiE "[[:space:]]$1([[:space:]]|\$)" /etc/hosts 2>/dev/null && return 0
37+
ping -c 1 "$1" >/dev/null 2>&1 && return 0
38+
return 1
39+
}
40+
41+
if ! host_resolves "$WP_HOST"; then
42+
echo ""
43+
echo "WARNING: '$WP_HOST' does not resolve on this host."
44+
echo " Setup will still initialize WordPress, but you won't be able to reach"
45+
echo " http://$WP_DOMAIN in your browser until you map '$WP_HOST' to localhost."
46+
echo " Add this line to your /etc/hosts (see DEVELOPMENT.md,"
47+
echo " \"Add the wp.test host name\"):"
48+
echo ""
49+
echo " 127.0.0.1 $WP_HOST"
50+
echo ""
51+
fi
52+
2453
rc=`curl \
54+
--resolve "$WP_DOMAIN:127.0.0.1" \
2555
--write-out '%{http_code}' \
2656
--silent \
2757
--output /dev/null \
@@ -34,7 +64,9 @@ rc=`curl \
3464
--data-urlencode "pw_weak=1"`
3565

3666
if [ "$rc" == "200" ]; then
37-
echo "SUCCESS initializing WordPress\n\tAdmin dashboard here:\n\thttp://$WP_DOMAIN/wp-admin"
67+
echo "SUCCESS initializing WordPress"
68+
echo " Admin dashboard here:"
69+
echo " http://$WP_DOMAIN/wp-admin"
3870
else
3971
echo "FAIL with HTTP $rc"
4072
exit 1

docker-compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ x-wordpress-defaults: &wordpress_defaults
118118
- "8765:80"
119119
extra_hosts:
120120
- "dockerhost:169.254.254.254"
121+
# Makes host.docker.internal resolve to the docker host on Docker Engine for
122+
# Linux (20.10+), where it isn't provided automatically as it is on Docker
123+
# Desktop for Mac/Windows. Harmless on Docker Desktop.
124+
- "host.docker.internal:host-gateway"
121125
command:
122126
- apache2-custom.sh
123127

docker/add-hosts.sh

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,39 @@
11
#!/bin/bash
22

3-
INTERNAL_IP=$( php -r 'print gethostbyname("host.docker.internal");' )
3+
# Resolve the docker host's IP address so we can point wp.test at it.
4+
#
5+
# host.docker.internal resolves automatically inside containers on Docker
6+
# Desktop (Mac/Windows), but not on Docker Engine for Linux (e.g. an Ubuntu EC2
7+
# host) unless the compose file maps it via `extra_hosts: host.docker.internal:
8+
# host-gateway`. Where it can't be resolved, fall back to the container's
9+
# default gateway, which is the docker host on a Linux bridge network.
410

5-
if [ "xhost.docker.internal" == x${INTERNAL_IP} ]; then
6-
echo "FAILED resolving host.docker.internal in the docker container"
7-
exit 1
11+
# Parse the default route's gateway from /proc/net/route. The gateway is stored
12+
# little-endian hex, and this needs no iproute2 (`ip`) dependency.
13+
default_gateway() {
14+
local iface dest gateway rest
15+
while read -r iface dest gateway rest; do
16+
[ "$dest" = "00000000" ] || continue
17+
printf '%d.%d.%d.%d\n' \
18+
"0x${gateway:6:2}" "0x${gateway:4:2}" "0x${gateway:2:2}" "0x${gateway:0:2}"
19+
return 0
20+
done < /proc/net/route
21+
return 1
22+
}
23+
24+
INTERNAL_IP=$( getent hosts host.docker.internal | awk '{ print $1 }' | head -n1 )
25+
26+
if [ -z "$INTERNAL_IP" ]; then
27+
INTERNAL_IP=$( default_gateway )
828
fi
929

10-
echo "$INTERNAL_IP wp.test" >> /etc/hosts
30+
if [ -z "$INTERNAL_IP" ]; then
31+
echo "FAILED resolving the docker host IP address for wp.test"
32+
exit 1
33+
fi
1134

12-
if [ "$?" == "0" ]; then
13-
echo "SUCCESS adding wp.test to container /etc/hosts"
35+
if echo "$INTERNAL_IP wp.test" >> /etc/hosts; then
36+
echo "SUCCESS adding wp.test ($INTERNAL_IP) to container /etc/hosts"
1437
else
1538
echo "FAILED attempting to add wp.test to container /etc/hosts"
1639
exit 1

docker/apache2-custom.sh

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,57 @@
11
#!/bin/bash
22

33
# See https://github.com/docker-library/wordpress/issues/205
4+
#
5+
# WordPress/Apache needs /var/www/html to be writable by www-data, which we
6+
# used to guarantee with a blanket recursive chown of the whole tree:
7+
#
8+
# chown -Rf www-data:www-data /var/www/html/
9+
#
10+
# The catch: this repo and the integration themes/plugins are bind-mounted into
11+
# /var/www/html from the host, so a recursive chown walks into those bind mounts
12+
# and reowns the host's working copy to www-data (uid 33). On the host that
13+
# trips git's "dubious ownership" guard (CVE-2022-24765) and causes other
14+
# permission friction.
15+
#
16+
# By default (SKIP_PLUGIN_CHOWN unset or any value other than "false") we chown
17+
# everything under /var/www/html EXCEPT the bind-mounted host paths, so the host
18+
# working copy keeps its ownership. The excluded paths default to this plugin
19+
# plus the integration mounts wired up in docker-compose.yml; override with
20+
# CHOWN_EXCLUDE_PATHS (a colon-separated list) if you add or remove bind mounts
21+
# there. Set SKIP_PLUGIN_CHOWN=false to restore the original blanket behavior.
422

5-
chown -Rf www-data:www-data /var/www/html/
23+
CHOWN_ROOT="/var/www/html"
24+
25+
default_excludes=(
26+
"$CHOWN_ROOT/wp-content/plugins"
27+
"$CHOWN_ROOT/wp-content/js"
28+
"$CHOWN_ROOT/wp-content/themes"
29+
)
30+
31+
if [ "$SKIP_PLUGIN_CHOWN" != "false" ]; then
32+
if [ -n "$CHOWN_EXCLUDE_PATHS" ]; then
33+
IFS=':' read -ra excludes <<< "$CHOWN_EXCLUDE_PATHS"
34+
else
35+
excludes=("${default_excludes[@]}")
36+
fi
37+
38+
# Build a find(1) prune expression: ( -path A -o -path B -o ... )
39+
prune=()
40+
for path in "${excludes[@]}"; do
41+
[ -n "$path" ] || continue
42+
[ ${#prune[@]} -eq 0 ] || prune+=( -o )
43+
prune+=( -path "$path" )
44+
done
45+
46+
if [ ${#prune[@]} -gt 0 ]; then
47+
# Skip (don't descend into) the excluded bind-mount paths, chown the rest.
48+
find "$CHOWN_ROOT" \( "${prune[@]}" \) -prune -o -exec chown -f www-data:www-data {} +
49+
else
50+
chown -Rf www-data:www-data "$CHOWN_ROOT/"
51+
fi
52+
else
53+
chown -Rf www-data:www-data "$CHOWN_ROOT/"
54+
fi
655

756
DEFINES=""
857

includes/class-fontawesome.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -435,11 +435,6 @@ function () {
435435

436436
$this->maybe_enqueue_admin_assets();
437437

438-
// Setup JavaScript internationalization if we're on WordPress 5.0+.
439-
if ( function_exists( 'wp_set_script_translations' ) ) {
440-
wp_set_script_translations( self::ADMIN_RESOURCE_HANDLE, 'font-awesome' );
441-
}
442-
443438
if ( $this->using_kit() ) {
444439
if ( $this->skip_enqueue_kit() ) {
445440
// Normally, conflict detection is built into a kit.
@@ -1703,6 +1698,14 @@ function ( $hook ) {
17031698
try {
17041699
if ( $this->detecting_conflicts() || $hook === $this->screen_id || $should_enable_icon_chooser ) {
17051700
$this->enqueue_admin_js_assets( $should_enable_icon_chooser );
1701+
1702+
// Setup JavaScript internationalization if we're on WordPress 5.0+.
1703+
// This must happen after the admin script is registered/enqueued
1704+
// (in enqueue_admin_js_assets), so that the textdomain is
1705+
// associated with the script object before it is printed.
1706+
if ( function_exists( 'wp_set_script_translations' ) ) {
1707+
wp_set_script_translations( self::ADMIN_RESOURCE_HANDLE, 'font-awesome' );
1708+
}
17061709
}
17071710

17081711
if ( $hook === $this->screen_id ) {

tests/test-enqueue.php

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,10 +572,129 @@ public function test_conflict_detector_enqueued_when_enabled_webfont() {
572572
/**
573573
* We do not test the v4 font face shim inline style has the detection ignore
574574
* attr, since we can't filter the inline style tag.
575-
*
575+
*
576576
* (Probably we'll have a separate mechanism for ignoring that, if the
577577
* conflict detector reports it.)
578578
*/
579579
}
580+
581+
/**
582+
* Regression test for JavaScript translations on the admin settings page.
583+
*
584+
* The admin bundle (font-awesome-official-admin) is not registered until the
585+
* admin_enqueue_scripts hook fires (inside maybe_enqueue_admin_assets ->
586+
* enqueue_admin_js_assets). wp_set_script_translations() must therefore run
587+
* *after* that registration, or WP_Scripts::print_translations() bails early
588+
* (empty textdomain) and never emits the inline setLocaleData script, leaving
589+
* the settings page in English regardless of the site locale.
590+
*
591+
* This drives the real admin_enqueue_scripts flow and asserts that the admin
592+
* script ends up with its textdomain associated, and that the setLocaleData
593+
* inline script is produced.
594+
*/
595+
public function test_admin_script_translations_are_set() {
596+
// Enable conflict detection so the admin JS bundle is enqueued on
597+
// admin_enqueue_scripts regardless of the current screen. This lets us
598+
// exercise the real enqueue flow without standing up a settings-page
599+
// screen_id.
600+
$later = time() + ( 10 * 60 );
601+
update_option(
602+
FontAwesome::CONFLICT_DETECTION_OPTIONS_KEY,
603+
array_merge(
604+
FontAwesome::DEFAULT_CONFLICT_DETECTION_OPTIONS,
605+
array( 'detectConflictsUntil' => $later )
606+
)
607+
);
608+
609+
// set_up() pre-registers a stub for the admin handle so unrelated tests
610+
// can satisfy the conflict detector's dependency. Deregister it here so
611+
// this test reproduces the real runtime, where the admin bundle is not
612+
// registered until admin_enqueue_scripts fires. (Otherwise the premature,
613+
// buggy wp_set_script_translations() call would find the stub already
614+
// registered and the bug would be masked.)
615+
wp_deregister_script( FontAwesome::ADMIN_RESOURCE_HANDLE );
616+
617+
// There is no real .json translation file installed in the test
618+
// environment, so short-circuit the file lookup with fake translation
619+
// data. print_translations() still bails on an empty textdomain, so this
620+
// filter does not paper over the bug being tested.
621+
$fake_translations = wp_json_encode(
622+
array(
623+
'domain' => 'messages',
624+
'locale_data' => array(
625+
'messages' => array(
626+
'' => array(
627+
'domain' => 'messages',
628+
'lang' => 'ja',
629+
),
630+
'Settings' => array( '設定' ),
631+
),
632+
),
633+
)
634+
);
635+
636+
add_filter(
637+
'pre_load_script_translations',
638+
function ( $translations, $file, $handle, $domain ) use ( $fake_translations ) {
639+
if ( FontAwesome::ADMIN_RESOURCE_HANDLE === $handle && 'font-awesome' === $domain ) {
640+
return $fake_translations;
641+
}
642+
return $translations;
643+
},
644+
10,
645+
4
646+
);
647+
648+
// Run the plugin's init. This registers the admin_enqueue_scripts
649+
// callback. On the buggy code, this is also where wp_set_script_translations()
650+
// was (incorrectly) called -- too early, before the admin bundle exists.
651+
fa()->init();
652+
653+
$this->assertFalse(
654+
wp_script_is( FontAwesome::ADMIN_RESOURCE_HANDLE, 'registered' ),
655+
'Precondition: the admin bundle must not be registered until admin_enqueue_scripts runs.'
656+
);
657+
658+
// A current screen must be set, since WordPress core callbacks on
659+
// admin_enqueue_scripts (e.g. wp_auth_check_load) read
660+
// get_current_screen()->id.
661+
set_current_screen( 'settings_page_font-awesome' );
662+
663+
// Fire the admin_enqueue_scripts hook. This registers/enqueues the admin
664+
// bundle and -- with the fix -- associates the textdomain with it.
665+
do_action( 'admin_enqueue_scripts', 'settings_page_font-awesome' );
666+
667+
$wp_scripts = wp_scripts();
668+
669+
$this->assertArrayHasKey(
670+
FontAwesome::ADMIN_RESOURCE_HANDLE,
671+
$wp_scripts->registered,
672+
'The admin bundle should be registered after admin_enqueue_scripts.'
673+
);
674+
675+
// Root cause from the bug report: the script object's textdomain property
676+
// was never set, so print_translations() returned early.
677+
$this->assertEquals(
678+
'font-awesome',
679+
$wp_scripts->registered[ FontAwesome::ADMIN_RESOURCE_HANDLE ]->textdomain,
680+
'The admin script must have its textdomain set to font-awesome.'
681+
);
682+
683+
// With the textdomain set, print_translations() should emit the inline
684+
// setLocaleData script -- the <script id="font-awesome-official-admin-js-translations">
685+
// element that was reported as absent from the DOM.
686+
$translations_output = $wp_scripts->print_translations( FontAwesome::ADMIN_RESOURCE_HANDLE, false );
687+
688+
$this->assertNotFalse(
689+
$translations_output,
690+
'print_translations() must not return false; otherwise no setLocaleData script is output.'
691+
);
692+
693+
$this->assertStringContainsString(
694+
'wp.i18n.setLocaleData',
695+
$translations_output,
696+
'The translations output should call wp.i18n.setLocaleData.'
697+
);
698+
}
580699
}
581700

0 commit comments

Comments
 (0)