Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
4b2d620
fix(wpml): backfill existing forms, translatable title, package lifec…
vanshk141999 Jul 15, 2026
2e5ab57
test(wpml): cover delete_package + title_name; use null coalescing
vanshk141999 Jul 15, 2026
269d6e4
fix(wpml): address review — schema-versioned backfill, unique enqueue…
vanshk141999 Jul 16, 2026
4e7708c
fix(wpml): declare package kind via wpml_active_string_package_kinds
vanshk141999 Jul 19, 2026
68b24ca
Merge pull request #2966 from brainstormforce/master-dev-2.12.2-post
vanshk141999 Jul 20, 2026
de9607e
Merge pull request #2967 from brainstormforce/dev-nr-2.12.2-post
vanshk141999 Jul 20, 2026
7ce98c4
Fix: entries search never matched submitted form data
vanshk141999 Jul 21, 2026
ae88084
Require 3+ characters for entry-data text search
vanshk141999 Jul 21, 2026
24fe6d2
Fix comment: query compiler method is Base::prepare_where_clauses()
vanshk141999 Jul 21, 2026
4ab4446
Docs: update get_entries() search arg docblock to match new search scope
vanshk141999 Jul 21, 2026
6278b6b
Docs: update entriesApi search JSDoc to match new search scope
vanshk141999 Jul 21, 2026
9ff176f
Cache the search COUNT in a 30s transient
vanshk141999 Jul 21, 2026
9e9ada3
Fix PHPStan: narrow get_transient() mixed with is_numeric() before ab…
vanshk141999 Jul 21, 2026
c7e7b11
Fix: frontend crash on forms with default styles disabled
vanshk141999 Jul 22, 2026
5922603
readme: AI-first rewrite (Contact Form Builder + AI/Payment/Survey/Quiz)
vanshk141999 Jul 22, 2026
2244aae
readme: bump Tested up to 7.0.2 to match wordpress.org
vanshk141999 Jul 22, 2026
aa89c5f
readme: regenerate README.md from readme.txt (grunt readme)
vanshk141999 Jul 22, 2026
a509284
Merge pull request #2978 from brainstormforce/chore/ai-first-readme-r…
vanshk141999 Jul 22, 2026
d519a0a
Remove the entries `language` column (failing migration + submission …
vanshk141999 Jul 22, 2026
6a1a04d
Fix: load Payment History CSS only when the block/shortcode is present
vanshk141999 Jul 23, 2026
70d3659
fix: address review — remove dangling Language column from entries admin
vanshk141999 Jul 27, 2026
74321c1
fix: address review — head-load Payment History CSS for Elementor/Bri…
vanshk141999 Jul 27, 2026
40c30c7
Fix: correct Cloudflare Turnstile wp_enqueue_script $args (WP 7.0 not…
vanshk141999 Jul 27, 2026
dcb87ea
fix: align number field top margin with other fields (label-as-placeh…
vanshk141999 Jul 27, 2026
6ba586e
feat(entries): add frontend admin-bar "Entries" deep-link (#2988)
vanshk141999 Jul 28, 2026
a014f22
feat(entries): scope frontend admin-bar Entries node to Instant Form …
vanshk141999 Jul 28, 2026
0b96b11
Merge pull request #2970 from brainstormforce/fix/entries-search-form…
vanshk141999 Jul 29, 2026
3fde592
Merge pull request #2977 from brainstormforce/fix/dark-bg-normalizer-…
vanshk141999 Jul 29, 2026
30c0b88
Merge pull request #2980 from brainstormforce/fix/remove-entries-lang…
vanshk141999 Jul 29, 2026
8f3c764
Merge pull request #2985 from brainstormforce/fix/cf-turnstile-enqueu…
vanshk141999 Jul 29, 2026
45ca33a
Address review: widen Entries admin-bar detection to all form embeds
vanshk141999 Jul 29, 2026
94ba331
fix(payment-history): head-load CSS for Bricks, clear Elementor cache…
vanshk141999 Jul 29, 2026
a39ed3e
test: cover manage_empty_global_dynamic_options to satisfy coverage-c…
vanshk141999 Jul 29, 2026
c36e56e
Merge pull request #2981 from brainstormforce/fix/payment-history-css…
vanshk141999 Jul 29, 2026
1449fa4
fix(number-field): use shared label-gap margin expression instead of …
vanshk141999 Jul 29, 2026
aaa2ab8
Merge pull request #2986 from brainstormforce/fix/label-placeholder-n…
vanshk141999 Jul 30, 2026
0d0c28b
Address review: seed Entries form registry at `wp` (bar renders befor…
vanshk141999 Jul 30, 2026
b1c6b56
Merge pull request #2941 from brainstormforce/fix/wpml-backfill-and-l…
vanshk141999 Jul 30, 2026
a49bc6d
Merge pull request #2989 from brainstormforce/feat/2988-entries-admin…
vanshk141999 Jul 30, 2026
2893f02
Merge branch 'master' of https://github.com/brainstormforce/sureforms…
vanshk141999 Jul 30, 2026
fb8ccb0
Merge pull request #2999 from brainstormforce/master-dev-2.12.3-pre
vanshk141999 Jul 30, 2026
dd4df99
Merge pull request #3000 from brainstormforce/dev-nr-2.12.3-pre
vanshk141999 Jul 30, 2026
1d69719
Version Bump 2.12.3
vanshk141999 Jul 30, 2026
7b332e1
Update 2.12.3 changelog entry
vanshk141999 Jul 30, 2026
2a58bf9
Remove per-release Upgrade Notice from readme
vanshk141999 Jul 30, 2026
990991b
date format
vanshk141999 Jul 30, 2026
d45db8b
Simplify 2.12.3 changelog wording
vanshk141999 Jul 30, 2026
4f8a3fe
Update 2.12.3 changelog wording
vanshk141999 Jul 30, 2026
6e12922
updated change log
vanshk141999 Jul 30, 2026
3237aa7
updated change log
vanshk141999 Jul 30, 2026
6b0c8a7
Merge pull request #3001 from brainstormforce/version-bump-2.12.3
vanshk141999 Jul 30, 2026
d741422
fix(2.12.3): WPML backfill only ran once per site, plus review fixes
vanshk141999 Jul 30, 2026
772e767
fix(2.12.3): address review round 2 on the WPML backfill lifecycle
vanshk141999 Jul 30, 2026
8b33b3e
chore(2.12.3): move release date to 3rd August, fix Insights + covera…
vanshk141999 Jul 30, 2026
624f717
fix(2.12.3): resolve review comments — sort allowlist, cache guard, s…
vanshk141999 Jul 31, 2026
de7a47f
fix(security): stored XSS in entries admin view via entity-encoded fi…
vanshk141999 Jul 31, 2026
5c09feb
fix(security): preserve rich-text rendering — DOMPurify direct-to-DOM…
vanshk141999 Jul 31, 2026
403aca5
fix(security): reject submitted field keys the form does not define (…
vanshk141999 Jul 31, 2026
11f5a4a
fix(security): rename misleading Helper::encrypt/decrypt to encode/de…
vanshk141999 Jul 31, 2026
7aebb37
fix(2.12.3): address review round 3 (#3003)
vanshk141999 Jul 31, 2026
8c08c81
Merge pull request #3003 from brainstormforce/fix/2.12.3-review-blockers
vanshk141999 Jul 31, 2026
7829a33
fix(security): harden the entries sanitizer policy; restore log entit…
vanshk141999 Jul 31, 2026
c8eccea
fix(security): filter CSS per-property instead of forbidding style; b…
vanshk141999 Jul 31, 2026
dbb44fa
fix(security): address review — log markup, field discriminator, medi…
vanshk141999 Jul 31, 2026
7ec239c
revert: drop the CI unit-test step from this PR
vanshk141999 Jul 31, 2026
cd13daf
fix(entries): repair payment-link and change-log regressions from the…
vanshk141999 Jul 31, 2026
e63304b
test(security): replicate the Wordfence PoC for CVE-2026-18406
vanshk141999 Jul 31, 2026
fbd0d7b
fix(security): address second-round review on the entries sanitizer
vanshk141999 Aug 3, 2026
1fadc9c
fix(security): escape {all_data} email label as text (#2991)
vanshk141999 Aug 4, 2026
ca4341e
fix(security): neutralise CSV formula injection in entries-export hea…
vanshk141999 Aug 4, 2026
47ced58
fix(security): gate generate-form-markup on capability and form type …
vanshk141999 Aug 4, 2026
142c136
fix(security): restrict nopriv unique check to the form's unique fiel…
vanshk141999 Aug 4, 2026
0008386
fix(security): require a verified payment on payment-enabled forms (#…
vanshk141999 Aug 4, 2026
0fcd5e1
test: cover decrypt alias, fetch_svg and set_input_label (#2994)
vanshk141999 Aug 4, 2026
484b8ed
fix(review): finish the encode/decode rename (#2994)
vanshk141999 Aug 4, 2026
96b8b9e
fix(review): share the CSV escaper and tighten its test (#2996)
vanshk141999 Aug 4, 2026
6ebc4e8
fix(review): drop the vestigial form-markup nonce (#2995)
vanshk141999 Aug 4, 2026
532c1e4
fix(review): harden the unique-field set derivation (#2997)
vanshk141999 Aug 4, 2026
509a15c
fix(review): narrow the conditional-logic exemption (#2998)
vanshk141999 Aug 4, 2026
7032596
fix(review): extract the block id with the shared helper (#2993)
vanshk141999 Aug 4, 2026
8e0e930
fix: move escape_csv_formula above the private methods (#2996)
vanshk141999 Aug 4, 2026
6dad7d0
Merge pull request #3004 from brainstormforce/fix/cve-2026-18406-entr…
vanshk141999 Aug 4, 2026
344e560
fix(review): drop unknown field keys instead of rejecting, and walk r…
vanshk141999 Aug 4, 2026
c96cb99
Merge pull request #3012 from brainstormforce/fix/2998-require-verifi…
vanshk141999 Aug 4, 2026
999a7a5
Merge pull request #3010 from brainstormforce/fix/2995-restrict-gener…
vanshk141999 Aug 4, 2026
210d35b
Merge pull request #3009 from brainstormforce/fix/2996-escape-csv-exp…
vanshk141999 Aug 4, 2026
a258755
Merge pull request #3008 from brainstormforce/fix/2991-escape-all-dat…
vanshk141999 Aug 4, 2026
db46106
Merge pull request #3005 from brainstormforce/fix/2993-validate-field…
vanshk141999 Aug 4, 2026
1ab16a9
Merge pull request #3011 from brainstormforce/fix/2997-restrict-uniqu…
vanshk141999 Aug 4, 2026
4d10e83
fix(review): correct @since tags and finish the terminology sweep (#2…
vanshk141999 Aug 4, 2026
6c114d3
Merge pull request #3006 from brainstormforce/fix/2994-rename-encrypt…
vanshk141999 Aug 4, 2026
16855d9
Merge branch 'dev' of https://github.com/brainstormforce/sureforms in…
vanshk141999 Aug 5, 2026
07954bf
Merge pull request #3015 from brainstormforce/dev-nr-2.12.3-sec
vanshk141999 Aug 5, 2026
cdf142a
Version Bump 2.12.3
vanshk141999 Aug 5, 2026
c53f29f
Replace @since x.x.x placeholders with 2.12.3
vanshk141999 Aug 5, 2026
78b6cde
Consolidate security fixes into a single changelog entry
vanshk141999 Aug 5, 2026
8ec0828
Merge pull request #3016 from brainstormforce/version-bump-2.12.3
vanshk141999 Aug 5, 2026
72e317b
chore: update i18n translations
github-actions[bot] Aug 5, 2026
5660380
Merge pull request #3017 from brainstormforce/i18n/next-release
vanshk141999 Aug 5, 2026
50c14ab
fix(2.12.3): release hygiene — docblock disclosure, @since sweep, cha…
vanshk141999 Aug 5, 2026
25557f3
fix(2.12.3): run the @since sweep, widen its scope, correct two carry…
vanshk141999 Aug 5, 2026
d130f56
fix(2.12.3): backfill @since with real ship versions instead of 2.12.3
vanshk141999 Aug 5, 2026
3348873
Merge pull request #3018 from brainstormforce/fix/2.12.3-release-hygiene
vanshk141999 Aug 5, 2026
eb165c1
Merge pull request #3002 from brainstormforce/next-release
vanshk141999 Aug 5, 2026
e78a1d7
updated security change log
vanshk141999 Aug 6, 2026
03a17ec
updated change log
vanshk141999 Aug 6, 2026
92375b1
updated change log
vanshk141999 Aug 6, 2026
179ade2
Merge pull request #3021 from brainstormforce/update-changelog-2.12.3
vanshk141999 Aug 6, 2026
01dab56
chore: strip internal-only paths from public mirror sync
vanshk141999 Aug 6, 2026
f7debd8
Sync master from upstream
vanshk141999 Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/code-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,9 @@ jobs:

- name: Lint CSS
run: npm run lint-css

# No other job runs the JS unit tests. Without this step the entry
# sanitizer policy has no gate: widening it back to DOMPurify's
# defaults would break tests that never execute in CI.
- name: JS unit tests
run: npm run test:unit
21 changes: 20 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,25 @@ module.exports = function ( grunt ) {
],
},
plugin_function_comment: {
// JS is swept as well as PHP. The sweep used to glob PHP only, so
// `@since` version placeholders in JS survived every release.
// (Do not write the literal placeholder in these comments — this task
// rewrites its own file, and it would be substituted on the next bump.)
//
// `src` is the source of truth, but it is NOT sufficient on its own —
// two placeholders currently ship from trees `src/**/*.js` cannot reach:
// inc/page-builders/elementor/assets/elementor-preview-styling.js:8
// assets/js/payment-history.js:8
// `inc/` is not in .distignore at all, and .distignore excludes only
// `assets/js/unminified`, so `assets/js/*.js` goes to wordpress.org.
// Both are therefore in scope deliberately; narrowing to `src` would
// leave placeholders in the shipped ZIP.
src: [
'*.php',
'**/*.php',
'src/**/*.js',
'inc/**/*.js',
'assets/js/**/*.js',
'!node_modules/**',
'!php-tests/**',
'!bin/**',
Expand All @@ -285,7 +301,10 @@ module.exports = function ( grunt ) {
overwrite: true,
replacements: [
{
from: /x.x.x/ig,
// Dots escaped: unescaped they match any character, so the
// pattern would also rewrite incidental strings such as
// "x1x2x" — a real risk now that minified JS is in scope.
from: /x\.x\.x/gi,
to: '<%=pkg.version %>',
},
],
Expand Down
528 changes: 249 additions & 279 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/js/payment-history.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* and multi-step subscription cancellation flow.
*
* @package
* @since x.x.x
* @since 2.8.0
*/

( function () {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/unminified/blocks/textarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ function handleQuillEditorA11Y( areaField ) {
* Localizes the Quill editor toolbar labels using WordPress translations.
*
* @param {HTMLElement} areaField - The textarea element enhanced by Quill editor.
* @since x.x.x
* @since 2.8.0
*/
function localizeQuillToolbar( areaField ) {
if ( typeof srfm_quill_i18n === 'undefined' ) {
Expand Down
28 changes: 27 additions & 1 deletion assets/js/unminified/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,22 @@ function onHCaptchaError() {
element.classList.add( 'srfm-has-dark-bg' );
}

/**
* Skip the CSS variable adjustment for de-skinned forms. When default styling
* is disabled ( the `srfm-styling-none` marker class ), the default stylesheet
* that consumes `--srfm-expandable-menu-background` is not enqueued, the
* derived variables ( e.g. `--srfm-color-input-text` ) are not printed — so
* the luminance check below degenerates and this branch would run for every
* form — and the container may have no inline <style> tag at all, which
* previously caused an unguarded `querySelector( 'style' ).innerHTML` crash
* that also broke initialization of every later form on the page.
* The `srfm-has-dark-bg` class above is still applied — it works without the
* default stylesheet and is documented as a hook for custom styling.
*/
if ( element.classList.contains( 'srfm-styling-none' ) ) {
return;
}

/**
* Lets calculate the form's text color.
*/
Expand All @@ -517,7 +533,17 @@ function onHCaptchaError() {
'form-id'
) } { ${ cssElements.join( ' ' ) } }`;

element.querySelector( 'style' ).innerHTML += styles;
// The container's inline <style> block is conditional ( it is omitted when
// default styling is disabled and no custom CSS exists ) — create it when
// missing instead of dereferencing null.
let styleTag = element.querySelector( 'style' );

if ( ! styleTag ) {
styleTag = document.createElement( 'style' );
element.appendChild( styleTag );
}

styleTag.innerHTML += styles;
}
}

Expand Down
4 changes: 2 additions & 2 deletions assets/js/unminified/preview-styling.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* properties are defined in a <style> tag nested inside the container.
*
* @package
* @since x.x.x
* @since 2.7.0
*/
( function () {
'use strict';
Expand Down Expand Up @@ -41,7 +41,7 @@
* Build CSS text for all variable overrides and apply via the dynamic <style> tag.
*
* @param {Object} cssVars Key-value pairs of CSS variable names to values.
* @since x.x.x
* @since 2.7.0
*/
function applyCssVarOverrides( cssVars ) {
let cssText = '.' + containerId + ' {\n';
Expand Down
2 changes: 1 addition & 1 deletion inc/abilities/ABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ Complete reference for all 15 abilities registered with the WordPress Abilities

### 10. `sureforms/get-entry`

**Get Entry Details** — Retrieve detailed entry information including submitted field data with decrypted labels.
**Get Entry Details** — Retrieve detailed entry information including submitted field data with decoded labels.

**File:** `inc/abilities/entries/get-entry.php`

Expand Down
2 changes: 1 addition & 1 deletion inc/abilities/entries/bulk-get-entries.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* Bulk_Get_Entries ability class.
*
* Retrieves detailed information about multiple form submission entries
* in a single call, including parsed form data with decrypted labels.
* in a single call, including parsed form data with decoded labels.
*
* @since 2.5.2
*/
Expand Down
6 changes: 3 additions & 3 deletions inc/abilities/entries/entry-parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ trait Entry_Parser {
/**
* Parse a raw entry array into the standard response shape.
*
* Handles form data decryption, form title lookup,
* Handles form data decoding, form title lookup,
* submission info building (with IP masking), and user info.
*
* @param array<string,mixed> $entry Raw entry from the database.
* @since 2.5.2
* @return array<string,mixed> Parsed entry data (without entry_id — caller prepends it).
*/
protected function parse_entry( array $entry ) {
// Parse form data with decrypted labels.
// Parse form data with decoded labels (submitter-controlled; escape at the sink).
$form_data = [];
$excluded_fields = Helper::get_excluded_fields();
$entry_form_data = $entry['form_data'] ?? [];
Expand All @@ -53,7 +53,7 @@ protected function parse_entry( array $entry ) {

$label_parts = explode( '-lbl-', $field_name );
$label = isset( $label_parts[1] ) ? explode( '-', $label_parts[1] )[0] : '';
$label = $label ? Helper::decrypt( $label ) : '';
$label = $label ? Helper::decode( $label ) : '';
$field_block_name = Helper::get_block_name_from_field( $field_name );

$form_data[] = [
Expand Down
2 changes: 1 addition & 1 deletion inc/abilities/entries/get-entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* Get_Entry ability class.
*
* Retrieves detailed information about a specific form submission entry,
* including parsed form data with decrypted labels.
* including parsed form data with decoded labels.
*
* @since 2.5.2
*/
Expand Down
10 changes: 5 additions & 5 deletions inc/abilities/entries/list-entries.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function get_input_schema() {
],
'search' => [
'type' => 'string',
'description' => __( 'Search entries by entry ID.', 'sureforms' ),
'description' => __( 'Search entries by entry ID, form title, or submitted form data. Text search requires at least 3 characters; numeric terms match the entry ID exactly.', 'sureforms' ),
],
'date_from' => [
'type' => 'string',
Expand All @@ -97,7 +97,7 @@ public function get_input_schema() {
'orderby' => [
'type' => 'string',
'description' => __( 'Column to order results by.', 'sureforms' ),
'enum' => [ 'created_at', 'ID', 'form_id', 'status', 'language' ],
'enum' => [ 'created_at', 'ID', 'id', 'form_id', 'status' ],
'default' => 'created_at',
],
'order' => [
Expand Down Expand Up @@ -128,7 +128,6 @@ public function get_output_schema() {
'form_id' => [ 'type' => 'integer' ],
'form_title' => [ 'type' => 'string' ],
'status' => [ 'type' => 'string' ],
'language' => [ 'type' => 'string' ],
'created_at' => [ 'type' => 'string' ],
],
],
Expand All @@ -155,7 +154,9 @@ public function execute( $input ) {
$args = [
'form_id' => Helper::get_integer_value( $input['form_id'] ?? 0 ),
'status' => ! empty( $input['status'] ) ? sanitize_text_field( Helper::get_string_value( $input['status'] ) ) : 'all',
'search' => ! empty( $input['search'] ) ? sanitize_text_field( Helper::get_string_value( $input['search'] ) ) : '',
// isset(), not ! empty(): a literal "0" search is a valid term and must not be
// dropped (which would return every entry). Mirrors the REST path.
'search' => isset( $input['search'] ) ? sanitize_text_field( Helper::get_string_value( $input['search'] ) ) : '',
'date_from' => ! empty( $input['date_from'] ) ? sanitize_text_field( Helper::get_string_value( $input['date_from'] ) ) : '',
'date_to' => ! empty( $input['date_to'] ) ? sanitize_text_field( Helper::get_string_value( $input['date_to'] ) ) : '',
'per_page' => $per_page,
Expand All @@ -182,7 +183,6 @@ public function execute( $input ) {
'form_id' => $form_id,
'form_title' => $form_title,
'status' => $entry['status'] ?? '',
'language' => Helper::get_string_value( $entry['language'] ?? '' ),
'created_at' => $entry['created_at'] ?? '',
];
}
Expand Down
11 changes: 11 additions & 0 deletions inc/compatibility/multilingual/providers/null-provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,15 @@ public function translate_package_string( array $package, string $name, string $
unset( $package, $name );
return $value;
}

/**
* Delete a string package. No-op for the Null provider.
*
* @param array<string,string> $package Package descriptor.
* @since 2.12.3
* @return void
*/
public function delete_package( array $package ): void {
unset( $package );
}
}
16 changes: 16 additions & 0 deletions inc/compatibility/multilingual/providers/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,20 @@ public function register_package_string( array $package, string $name, string $v
* @return string Translated value, or the original when no translation exists.
*/
public function translate_package_string( array $package, string $name, string $value ): string;

/*
* NOTE: delete_package( array $package ): void is deliberately NOT declared here.
*
* This interface is a public extension point — Multilingual_Manager::resolve_provider()
* accepts any object via the `srfm_multilingual_provider` filter (@since 2.11.0) and
* instanceof-checks it. Adding a bodyless method to a shipped interface is a
* fatal-error BC break: a third-party provider written against 2.11.0-2.12.2 would
* fail at class-declaration time on update, producing a white screen rather than a
* degraded feature. Guarding the call site does not help, because the fatal happens
* when the implementor's class is declared, not when the method is called.
*
* Both first-party providers implement delete_package(), and String_Collector
* feature-detects it before calling. If it is ever promoted into this contract, that
* must be a major release with a migration note for custom providers.
*/
}
25 changes: 25 additions & 0 deletions inc/compatibility/multilingual/providers/wpml-provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,31 @@ public function translate_package_string( array $package, string $name, string $
return is_string( $translated ) ? $translated : $value;
}

/**
* {@inheritDoc}
*
* Removes the package and its translations from WPML via the
* `wpml_delete_package` action, keyed by the package name + kind.
*
* @param array<string,string> $package Package descriptor.
* @since 2.12.3
* @return void
*/
public function delete_package( array $package ): void {
if ( ! $this->supports_packages() ) {
return;
}

$name = $package['name'] ?? '';
$kind = $package['kind'] ?? '';

if ( '' === $name || '' === $kind ) {
return;
}

do_action( 'wpml_delete_package', $name, $kind );
}

/**
* Build the active-language list from the most reliable WPML surface available.
*
Expand Down
Loading