10.0.0-preview - #631
Conversation
piqusy
left a comment
There was a problem hiding this comment.
Automated review pass on this large UI/Tailwind migration PR. Left 6 line comments on the highest-confidence issues found (a global wp_kses caching regression, broken block icons from a key rename, a new escaping wrap that strips required aria/autocomplete attributes, a debug-email field regression, a dropped cache call, and a Tailwind prefix typo repeated in a few places). Everything else reviewed (REST routes, file-upload validation, Pardot token/cookie fixes, captcha labels, DI wiring, Labels/Fallback cleanup) checked out clean.
| static $result = null; | ||
|
|
||
| /** | ||
| * Add forms additional attributes to allow list. | ||
| * | ||
| * @return array<string, array<string, bool>> | ||
| */ | ||
| private function setForm(): array | ||
| { | ||
| return self::FORM; | ||
| if ($result !== null) { | ||
| return $result; | ||
| } |
There was a problem hiding this comment.
setCustomWpksesPostTags() now memoizes its result in a static $result, ignoring the $tags/$context args on every call after the first. This is a global WordPress core filter (wp_kses_allowed_html), not something scoped to this plugin's own calls, and WP core itself invokes it with different contexts within the same request.
Concrete failure case (traced against WP core's kses.php): saving any post as a user without unfiltered_html (Editors, Authors, Contributors, even Admins on multisite) triggers, in the same wp_insert_post() call:
title_save_pre→wp_filter_kses($title)→wp_kses($title, 'title_save_pre')→ hits thedefaultbranch inwp_kses_allowed_html()with$tags = $allowedtags(small comment-tag set). This filter fires first and gets cached.content_save_pre→wp_filter_post_kses($content)→wp_kses($content, 'post')→ should get$allowedposttags(full post tag set: p, div, h1-h6, img, table, etc.) but instead gets the cached title-context result from step 1.
Net effect: post_content gets silently stripped down to a near-comment-level allow-list for any non-superuser saving any post on the site. Suggest keying the cache by $context (or dropping the memoization — the original per-call computation was cheap array merges, not worth caching).
There was a problem hiding this comment.
There are a few reasonable arguments for memoizing the result:
- Avoid repeated array work:
array_merge()plus the loop over form elements runs on everywp_kses_allowed_htmlinvocation. - The plugin additions are stable:
self::FORMand the SVG allow-list do not change during a request. - It returns the same result for plugin-owned assumptions: If this callback were called only with one known
$tagsset and context, caching would be valid. - The result is relatively large: Avoiding repeated construction could reduce small amounts of allocation in requests with many KSES calls.
Those arguments support caching the context-independent plugin data, such as getSvg(), but not caching the complete callback result. The complete result depends on $tags, and WordPress invokes this global filter with different base allow-lists and contexts during one request. $context alone is also not a fully reliable cache key because callers could provide different $tags for the same context.
So the current implementation is defensible as a micro-optimization, but the cache is placed at the wrong boundary. The performance gain is likely minor compared with the correctness risk.
What I'll do is remove the outer static $result; retaining the getSvg() memoization.
| @@ -130,7 +125,7 @@ class="<?php echo esc_attr($formsClass); ?>" | |||
|
|
|||
| // Render blocks. | |||
| foreach ($output as $block) { | |||
| echo apply_filters('the_content', render_block($block)); // phpcs:ignore Eightshift.Security.HelpersEscape.OutputNotEscaped | |||
| echo wp_kses_post(apply_filters('the_content', render_block($block))); | |||
There was a problem hiding this comment.
This wp_kses_post() wrap is new in this PR (previously the output was echoed directly with a phpcs:ignore Eightshift.Security.HelpersEscape.OutputNotEscaped) — good escaping fix in principle, but the plugin's custom wp_kses allow-list (src/View/EscapedView.php, via vendor AbstractEscapedView::FORM) was never updated to include aria-required, aria-invalid, or autocomplete on <input>.
src/Blocks/components/input/input.php sets all three on every rendered input ($inputAttrs['autocomplete'], $inputAttrs['aria-required'], $inputAttrs['aria-invalid']). Since wp_kses_post() strips any attribute not in the allow-list, every rendered <input> on the frontend silently loses these attributes.
Suggest adding aria-required, aria-invalid, and autocomplete to the input entry in the FORM allow-list alongside this change.
| // translators: %s replaces the debug key. | ||
| $body .= '<p style="font-family: monospace;">' . \sprintf(\wp_kses_post(\__('Debug Key: <strong>%s</strong>', 'eightshift-forms')), \esc_html($debugKeyValue)) . '</p>'; | ||
|
|
||
| // translators: %s replaces the debug key description. | ||
| $body .= '<p style="font-family: monospace;">' . \sprintf(\wp_kses_post(\__('Debug Key description: <strong>%s</strong>', 'eightshift-forms')), \esc_html($this->settingsFallback->getFlagLabel($debugKeyValue))) . '</p>'; | ||
| $body .= '<p style="font-family: monospace;">' . \sprintf(\wp_kses_post(\__('Debug Key description: <strong>%s</strong>', 'eightshift-forms')), \esc_html(Labels::getLabel($debugKeyValue))) . '</p>'; |
There was a problem hiding this comment.
The "Debug Key description" field switched from SettingsFallback::getFlagLabel() (returned the flag's technical 'label' field, e.g. "Captcha feature is disabled.") to Labels::getLabel(), which only ever returns the flag's 'output' field (src/Labels/Labels.php:429 — self::getFlagsList()[$key]['output']) — the same public-facing message already shown elsewhere in this email.
Result: admins receiving a Mailer fallback/troubleshooting email now see the same generic public-facing message duplicated in both the main message and the "Debug Key description" line, losing the more technical detail the old label field provided for triage.
If the intent was just to drop the SettingsFallbackDataInterface dependency, Labels may need a variant that exposes the description/label field rather than reusing getLabel()'s public-output value here.
There was a problem hiding this comment.
I'll check this between the v10 preview and the full release, thanks
| @@ -664,8 +660,8 @@ public static function getBlockLocations(string $formId, string $type): array | |||
|
|
|||
| $isDeveloperModeActive = DeveloperHelpers::isDeveloperModeActive(); | |||
|
|
|||
| $output = \array_map( | |||
| function ($item) use ($isDeveloperModeActive) { | |||
| return \array_map( | |||
There was a problem hiding this comment.
This refactor inlined the final array_map(...) directly into the return, dropping the \wp_cache_add($cacheKey, $output, $cacheGroup, \HOUR_IN_SECONDS); call that used to run right after building $output (and before the old return $output;).
Only the "no matching posts" branch a few lines up (if (!$items) { \wp_cache_add(...); return []; }) still populates the cache now. Any call that actually finds block locations — the common case, used by admin form/result listing pages — always misses cache and re-hits $wpdb->get_results() on every call instead of once per hour.
Suggest restoring the wp_cache_add() call for this branch too, e.g. by assigning to a variable before returning.
Description
Broad UI, editor, and tooling overhaul for Eightshift Forms, plus integration and labels refinements. Migrates the styling system to Tailwind, reworks the block editor experience, and cleans up block markup and build config.
Note
This is 99% production ready, a few checks with older projects are still required for full release.
Added
Changed
esf:prefix across admin, editor, and frontend assets.tabNoBgtotabWithBg, with tabs now displaying a background by default.Fixed
show asoption handling in the editor.Removed
adminListingPaginationcomponent attribute; pagination state is now supplied throughadminListingData.ratingIsReadOnly,checkboxAsToggleSize,textareaIsMonospace,textareaSize, andtextareaLimitHeight.card-inlinepresentation props and replacedcardInlineTitleLinkwithcardInlineUrl.QA Guide
Use a test site and an existing or newly created form.
For each issue, record the form used, the steps to reproduce it, the expected result, the actual result, and a screenshot.
Screenshots / Videos
Linked documentation PR