Skip to content

Commit 1b8c459

Browse files
authored
Handle restricted filesystem permissions and allow for disabling block editor support (#243)
* update theme-alpha to derive from twenty-twenty-five * add try / catch / notify in activator * add more custom exceptions * Enable fallback checking for svg stylesheet using HEAD request * enable differentiating admin warnings from fatal errors and allow for multiline exception messages. * check for svg styles with fallback warning notice when in WP admin * use the warning version of fetch_svg_styles from the config controller too * make the block editor support actions disableable * rename function as ensure_ * on activation, initialize svg styles conditionally * pre-release 5.0.2-1 * re-work the classic editor support - handle null results when querying for editorId element - use jQuery to handle binding click events to editors to avoid loss of click event bindings due to interaction with other plugin * rebuild classic-editor bundle * comment out special filters in theme-alpha/functions.php * fix translators comment for phpcs * make jquery dependence explicit for classic-editor * move maybe_refresh_releases into initialize_admin() * add default SVG styles * maybe_refresh_releases only on the plugin's settings page * reduce admin notices to warnings * bump version * wp_die with descriptive message on failed activation * Fixes for phpcs * update actions/cache version * Update Dockerfile-latest to add uopz extension * migrate SVG styles manager away from being a singleton - make it more testable with uopz * update multi-site activation tests to use non-singleton SVG styles manager * add uopz extension to CI * add test coverage for svg styles on activation * cleanup and auto-formatting * cleanup and focus ActivationTest * move svg-styles-manager tests into a separate test module * add test coverage for filesystem permissions error on activation * add coverage for throwing a specific exception when lacking filesystem permissions * add test coverage for stylesheet presence with and without filesystem permissions * add more test coverage for loading including wp-admin * enable uopz extension only for phpunit runs
1 parent 2629a30 commit 1b8c459

23 files changed

+828
-161
lines changed

.github/workflows/jest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121

2222
- name: Cache node_module
2323
id: node-modules-cache
24-
uses: actions/cache@v2
24+
uses: actions/cache@v4
2525
with:
2626
path: admin/node_modules
2727
key: ${{ runner.os }}-node-modules-${{ hashFiles('admin/package-lock.json') }}

.github/workflows/php.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ jobs:
4646
- uses: shivammathur/setup-php@v2
4747
with:
4848
php-version: ${{ matrix.php }}
49+
extensions: uopz
4950

5051
- name: Validate composer.json and composer.lock
5152
id: composer-lock

bin/phpunit

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
44
source $DIR/.common.bash
55
source $DIR/.resolve-container.bash
66

7-
$WP_SUDO $DOCKER exec -it -w /var/www/html/wp-content/plugins/font-awesome $WP_CONTAINER composer exec phpunit -- $@
7+
# We need to add the uopz extension to the phpunit command, to enable that extension
8+
# that is used in the tests for mocking.
9+
$WP_SUDO $DOCKER exec -it -w /var/www/html/wp-content/plugins/font-awesome $WP_CONTAINER php -d extension=uopz /var/tmp/composer/vendor/bin/phpunit $@

classic-editor/build/index.asset.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-i18n'), 'version' => '16532cdc2e9643455c8b');
1+
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-i18n'), 'version' => '7d5db1cbc6bf460346ff');

classic-editor/build/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

classic-editor/src/index.js

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { __ } from '@wordpress/i18n'
88

99
const { IconChooserModal } = get(window, [GLOBAL_KEY, 'iconChooser'], {})
1010

11+
const ICON_CHOOSER_OPEN_MODAL_BY_EDITOR_ID = {}
12+
1113
// Creates a new icon chooser with its own open event and submit handler.
1214
function newIconChooser(editorId, container, editorInsert) {
1315
const modalOpenEvent = createCustomEvent()
@@ -72,31 +74,36 @@ function insertContentIntoEditor(editorId, content) {
7274
}
7375
}
7476

75-
function initialize() {
76-
const editorIds = get(window, '__FontAwesomeOfficialPlugin_tinymce__.editors', [])
77-
78-
for (const editorId of editorIds) {
79-
const button = document.querySelector(`#fawp-tinymce-${editorId}`)
80-
const editor = document.querySelector(`#${editorId}`)
81-
const editorContainer = editor.parentElement
82-
83-
if (!editor || !button || !editorContainer) {
84-
console.error(__('Font Awesome Plugin: could not attach to editor id:', 'font-awesome'), editorId)
85-
continue
86-
}
77+
function getIconChooserOpenModal(editorId) {
78+
if (ICON_CHOOSER_OPEN_MODAL_BY_EDITOR_ID[editorId]) {
79+
return ICON_CHOOSER_OPEN_MODAL_BY_EDITOR_ID[editorId]
80+
}
8781

88-
const openIconChooser = newIconChooser(editorId, editorContainer, (editorId, content) => {
89-
insertContentIntoEditor(editorId, content)
90-
})
82+
const editor = document.querySelector(`#${editorId}`)
83+
const editorContainer = editor?.parentElement
9184

92-
button.addEventListener('click', openIconChooser)
85+
if (!editorContainer) {
86+
// The editor might be hidden. Bail early.
87+
return
9388
}
94-
}
9589

96-
if (document.readyState === 'complete') {
97-
initialize()
98-
} else {
99-
window.addEventListener('DOMContentLoaded', () => {
100-
initialize()
90+
const openIconChooser = newIconChooser(editorId, editorContainer, (editorId, content) => {
91+
insertContentIntoEditor(editorId, content)
10192
})
93+
94+
ICON_CHOOSER_OPEN_MODAL_BY_EDITOR_ID[editorId] = openIconChooser
95+
96+
return openIconChooser
10297
}
98+
99+
// Using jQuery seems to be the more idiomatic way to bind click events to the media button
100+
// in the WordPress editor. Doing it this way seems to resolve a conflict with the ACF plugin,
101+
// where our click event binding on the media button seemed to be removed on us.
102+
jQuery(document).on('click', '.font-awesome-icon-chooser-media-button', function(e) {
103+
const editorId = e.target.getAttribute('data-fa-editor-id')
104+
const iconChooserOpenModal = getIconChooserOpenModal(editorId)
105+
// This setTimeout allow for the React component that is the Icon Chooser to be mounted
106+
// and ready to receive the event. After the timeout expires, then the "open" event is dispatched.
107+
// Without this, the event is dispatched before the React component is mounted and ready to receive it.
108+
setTimeout(iconChooserOpenModal, 0)
109+
});

docker/Dockerfile-latest

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ FROM wordpress:latest
44
# See: https://deb.nodesource.com/
55
RUN apt-get update && apt-get install -y ca-certificates curl gnupg
66
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /tmp/nodesource.gpg
7-
ENV NODE_MAJOR=20
7+
ENV NODE_MAJOR=22
88
RUN echo "deb [signed-by=/tmp/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
99

1010
# Install packages
@@ -18,7 +18,12 @@ RUN curl -L -s https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-
1818
RUN groupadd -r user && useradd --no-log-init -r -g user user
1919

2020
# Install composer
21-
RUN curl https://raw.githubusercontent.com/composer/getcomposer.org/76a7060ccb93902cd7576b67264ad91c8a2700e2/web/installer | php -- --quiet && chmod +x composer.phar && mv composer.phar /usr/local/bin/composer
21+
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
22+
php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'.PHP_EOL; } else { echo 'Installer corrupt'.PHP_EOL; unlink('composer-setup.php'); exit(1); }" && \
23+
php composer-setup.php && \
24+
php -r "unlink('composer-setup.php');" && \
25+
mv composer.phar /usr/local/bin/composer && \
26+
chmod +x /usr/local/bin/composer
2227

2328
COPY ./install-wp-tests-docker.sh /tmp
2429

@@ -39,4 +44,6 @@ COPY ./setup-owasp.sh /tmp
3944

4045
RUN /tmp/setup-owasp.sh
4146

47+
RUN pecl install uopz
48+
4249
CMD ["docker-entrypoint.sh"]

font-awesome.php

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,14 @@ public function init_plugin() {
209209
add_action(
210210
'admin_notices',
211211
function() use ( $e ) {
212-
self::emit_admin_error_output( $e );
212+
self::emit_admin_error_output( $e, 'error' );
213213
}
214214
);
215215
} catch ( Error $e ) {
216216
add_action(
217217
'admin_notices',
218218
function() use ( $e ) {
219-
self::emit_admin_error_output( $e );
219+
self::emit_admin_error_output( $e, 'error' );
220220
}
221221
);
222222
}
@@ -249,10 +249,10 @@ public function activate_plugin() {
249249
require_once self::$loaded['path'] . 'includes/class-fontawesome-activator.php';
250250
FontAwesome_Activator::activate();
251251
} catch ( Exception $e ) {
252-
self::emit_admin_error_output( $e, $activation_failed_message );
252+
self::emit_admin_error_output( $e, 'error', $activation_failed_message );
253253
exit;
254254
} catch ( Error $e ) {
255-
self::emit_admin_error_output( $e, $activation_failed_message );
255+
self::emit_admin_error_output( $e, 'error', $activation_failed_message );
256256
exit;
257257
}
258258
}
@@ -263,28 +263,41 @@ public function activate_plugin() {
263263
* @internal
264264
* @ignore
265265
*/
266-
public static function emit_admin_error_output( $e, $context_message = '' ) {
266+
public static function emit_admin_error_output( $e, $level, $context_message = '' ) {
267+
$message_level_class = $level === 'warning' ? 'notice notice-warning is-dismissible' : 'notice notice-error is-dismissible';
268+
$header_messsage_error = esc_html__( 'The Font Awesome plugin caught a fatal error', 'font-awesome' );
269+
$header_messsage_warning = esc_html__( 'The Font Awesome plugin has a warning', 'font-awesome' );
270+
$header_message = $level === 'warning' ? $header_messsage_warning : $header_messsage_error;
271+
267272
if ( is_admin() && current_user_can( 'manage_options' ) ) {
268-
echo '<div class="error">';
269-
echo '<p>' . esc_html__( 'The Font Awesome plugin caught a fatal error', 'font-awesome' );
273+
echo '<div class="' . esc_html( $message_level_class ) . '">';
274+
echo '<p>' . $header_message;
270275
if ( is_string( $context_message ) && '' !== $context_message ) {
271276
echo ': ' . esc_html( $context_message );
272277
} else {
273278
echo '.';
274279
}
275-
echo '</p><p>';
280+
echo '</p>';
276281

277282
if ( ! is_a( $e, 'Exception' ) && ! is_a( $e, 'Error' ) ) {
283+
echo '<p>';
278284
esc_html_e( 'No error message available.', 'font-awesome' );
285+
echo '</p>';
279286
} else {
280287
self::emit_error_output_to_console( $e );
281288

282289
if ( boolval( $e->getMessage() ) ) {
283-
echo esc_html( $e->getMessage() );
290+
$lines = explode("\n", $e->getMessage());
291+
292+
foreach ($lines as $line) {
293+
echo '<p>';
294+
echo esc_html( $line );
295+
echo '</p>';
296+
}
284297
}
285298
}
286299

287-
echo '</p></div>';
300+
echo '</div>';
288301
}
289302
}
290303

@@ -408,10 +421,10 @@ public static function initialize() {
408421
require_once self::$loaded['path'] . 'includes/class-fontawesome-activator.php';
409422
FontAwesome_Activator::initialize();
410423
} catch ( Exception $e ) {
411-
self::emit_admin_error_output( $e, $initialization_failed_msg );
424+
self::emit_admin_error_output( $e, 'error', $initialization_failed_msg );
412425
exit;
413426
} catch ( Error $e ) {
414-
self::emit_admin_error_output( $e, $initialization_failed_msg );
427+
self::emit_admin_error_output( $e, 'error', $initialization_failed_msg );
415428
exit;
416429
}
417430
}

includes/class-fontawesome-activator.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require_once trailingslashit( __DIR__ ) . 'class-fontawesome.php';
66
require_once trailingslashit( __DIR__ ) . 'class-fontawesome-release-provider.php';
77
require_once trailingslashit( __DIR__ ) . 'class-fontawesome-svg-styles-manager.php';
8+
require_once trailingslashit( FONTAWESOME_DIR_PATH ) . 'includes/error-util.php';
89

910
/**
1011
* Plugin activation logic.
@@ -24,7 +25,21 @@ class FontAwesome_Activator {
2425
* @throws ReleaseProviderStorageException
2526
*/
2627
public static function activate() {
27-
self::initialize();
28+
try {
29+
self::initialize();
30+
} catch ( \Exception $e ) {
31+
wp_die(
32+
esc_html( $e->getMessage() ),
33+
esc_html( __( 'Font Awesome Activation Error', 'font-awesome' ) ),
34+
array( 'back_link' => true )
35+
);
36+
} catch ( \Error $e ) {
37+
wp_die(
38+
esc_html( $e->getMessage() ),
39+
esc_html( __( 'Font Awesome Activation Error', 'font-awesome' ) ),
40+
array( 'back_link' => true )
41+
);
42+
}
2843
}
2944

3045
/**
@@ -84,7 +99,9 @@ public static function initialize_current_site( $force ) {
8499
self::initialize_conflict_detection_options();
85100
}
86101

87-
self::initialize_svg_styles();
102+
if ( fa()->is_block_editor_support_enabled() ) {
103+
self::initialize_svg_styles();
104+
}
88105
}
89106

90107
/**
@@ -140,6 +157,6 @@ private static function initialize_conflict_detection_options() {
140157
* @throws ConfigCorruptionException
141158
*/
142159
private static function initialize_svg_styles() {
143-
FontAwesome_SVG_Styles_Manager::instance()->fetch_svg_styles( fa(), fa_release_provider() );
160+
FontAwesome_SVG_Styles_Manager::ensure_svg_styles_with_admin_notice_warning( fa(), fa_release_provider() );
144161
}
145162
}

includes/class-fontawesome-config-controller.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,15 @@ public function update_item( $request ) {
135135
$db_item
136136
);
137137

138-
FontAwesome_SVG_Styles_Manager::instance()->fetch_svg_styles( fa(), $this->release_provider() );
138+
/**
139+
* The admin notices will not be shown as a consequence of any failure in this
140+
* function call, since this is a REST controller.
141+
* However, this will allow us attempt the fetch, without it causing a fatal error
142+
* if it fails.
143+
*/
144+
if ( fa()->is_block_editor_support_enabled() ) {
145+
FontAwesome_SVG_Styles_Manager::ensure_svg_styles_with_admin_notice_warning( fa(), $this->release_provider() );
146+
}
139147

140148
$return_data = $this->build_item( fa() );
141149
return new FontAwesome_REST_Response( $return_data, 200 );

0 commit comments

Comments
 (0)