← All changes
|
inc/compatibility/multilingual/string-collector.php
+114
-9
2.11.1
→
2.12.8
View file →
| @@ -70,8 +70,13 @@ | ||
| 70 | 70 | */ |
| 71 | 71 | public function __construct() { |
| 72 | 72 | add_action( 'save_post_' . SRFM_FORMS_POST_TYPE, [ $this, 'on_form_save' ], 20, 1 ); |
| 73 | 73 | |
| 74 | + // Prune the form's String Package when the form is permanently deleted so | |
| 75 | + // orphaned packages and their translations don't linger. Fires only on | |
| 76 | + // permanent delete, not on trash (a trashed form may be restored). | |
| 77 | + add_action( 'before_delete_post', [ $this, 'on_form_delete' ], 10, 1 ); | |
| 78 | + | |
| 74 | 79 | // Register the GLOBAL built-in validation strings once per admin request |
| 75 | 80 | // (no-op when no multilingual provider is active). These strings are not |
| 76 | 81 | // per-form, so they belong on an admin/authoring hook rather than on every |
| 77 | 82 | // frontend request. WPML dedupes by (domain, name, value), so re-asserting |
| @@ -79,11 +84,48 @@ | ||
| 79 | 84 | // the WPML-activated-after-SureForms case. |
| 80 | 85 | if ( is_admin() ) { |
| 81 | 86 | add_action( 'admin_init', [ $this, 'collect_validation_messages' ] ); |
| 82 | 87 | } |
| 88 | + | |
| 89 | + // Declare our String Package kind so WPML's "Translate Everything | |
| 90 | + // Automatically" gate — which reads this filter, not just the per-package | |
| 91 | + // post association — queues SureForms form packages for auto-translation. | |
| 92 | + // Registered unconditionally: WPML only fires this filter when active, and | |
| 93 | + // the callback is a pure array append, so it is a no-op otherwise. | |
| 94 | + add_filter( 'wpml_active_string_package_kinds', [ $this, 'declare_package_kind' ] ); | |
| 83 | 95 | } |
| 84 | 96 | |
| 85 | 97 | /** |
| 98 | + * Declare the SureForms String Package kind to WPML. | |
| 99 | + * | |
| 100 | + * WPML's package-level "Translate Everything Automatically" gate reads this | |
| 101 | + * filter to decide which string-package kinds to auto-translate. Each form is | |
| 102 | + * registered as one package with kind {@see String_Translator::PACKAGE_KIND}, | |
| 103 | + * from which WPML derives the kind slug via `sanitize_title()`; computing the | |
| 104 | + * key the same way here guarantees it matches the slug WPML assigns to our | |
| 105 | + * packages (no hardcoded slug that could drift from the kind label). | |
| 106 | + * | |
| 107 | + * @param mixed $kinds Associative map of kind slug => { title, slug, plural }. | |
| 108 | + * @since 2.12.3 | |
| 109 | + * @return mixed The kinds map with the SureForms Form kind added. | |
| 110 | + */ | |
| 111 | + public function declare_package_kind( $kinds ) { | |
| 112 | + if ( ! is_array( $kinds ) ) { | |
| 113 | + return $kinds; | |
| 114 | + } | |
| 115 | + | |
| 116 | + $slug = sanitize_title( String_Translator::PACKAGE_KIND ); | |
| 117 | + | |
| 118 | + $kinds[ $slug ] = [ | |
| 119 | + 'title' => String_Translator::PACKAGE_KIND, | |
| 120 | + 'slug' => $slug, | |
| 121 | + 'plural' => __( 'SureForms Forms', 'sureforms' ), | |
| 122 | + ]; | |
| 123 | + | |
| 124 | + return $kinds; | |
| 125 | + } | |
| 126 | + | |
| 127 | + /** | |
| 86 | 128 | * Entry point hooked to save_post_{post_type}. |
| 87 | 129 | * |
| 88 | 130 | * Skips autosaves and revisions, and bails when no multilingual provider is |
| 89 | 131 | * active. Otherwise delegates to {@see collect()} to register all strings. |
| @@ -110,8 +152,49 @@ | ||
| 110 | 152 | $this->collect( $form_id ); |
| 111 | 153 | } |
| 112 | 154 | |
| 113 | 155 | /** |
| 156 | + * Delete the form's String Package when the form is permanently deleted. | |
| 157 | + * | |
| 158 | + * Hooked to before_delete_post (not trash) so a package is only removed when | |
| 159 | + * its form is gone for good. Bails for other post types and when no provider | |
| 160 | + * is active. | |
| 161 | + * | |
| 162 | + * @param int $form_id The post ID being deleted. | |
| 163 | + * @since 2.12.3 | |
| 164 | + * @return void | |
| 165 | + */ | |
| 166 | + public function on_form_delete( int $form_id ): void { | |
| 167 | + if ( SRFM_FORMS_POST_TYPE !== get_post_type( $form_id ) ) { | |
| 168 | + return; | |
| 169 | + } | |
| 170 | + | |
| 171 | + $provider = Multilingual_Manager::get_instance()->provider(); | |
| 172 | + | |
| 173 | + if ( ! $provider->is_active() ) { | |
| 174 | + return; | |
| 175 | + } | |
| 176 | + | |
| 177 | + // delete_package() is intentionally absent from the Provider interface (see the | |
| 178 | + // note there): declaring it would fatal any third-party provider written against | |
| 179 | + // 2.11.0-2.12.2. Feature-detect instead, so a custom provider without it simply | |
| 180 | + // skips cleanup rather than crashing. | |
| 181 | + if ( ! method_exists( $provider, 'delete_package' ) ) { | |
| 182 | + return; | |
| 183 | + } | |
| 184 | + | |
| 185 | + // Deletion only needs the package identity (name + kind); build it directly | |
| 186 | + // rather than String_Translator::form_package(), which also runs | |
| 187 | + // get_the_title() / get_edit_post_link() the delete path doesn't use. | |
| 188 | + $provider->delete_package( | |
| 189 | + [ | |
| 190 | + 'name' => (string) $form_id, | |
| 191 | + 'kind' => String_Translator::PACKAGE_KIND, | |
| 192 | + ] | |
| 193 | + ); | |
| 194 | + } | |
| 195 | + | |
| 196 | + /** | |
| 114 | 197 | * Walk the form and register every translatable string with the provider. |
| 115 | 198 | * |
| 116 | 199 | * Public so unit tests can exercise the collection logic directly without |
| 117 | 200 | * needing to fire the save_post action, and so migration code paths can |
| @@ -118,15 +201,19 @@ | ||
| 118 | 201 | * back-fill strings for existing forms. |
| 119 | 202 | * |
| 120 | 203 | * @param int $form_id The form post ID. |
| 121 | 204 | * @since 2.11.0 |
| 122 | - * @return void | |
| 205 | + * @since 2.12.3 Returns whether collection actually ran, so callers (notably the | |
| 206 | + * backfill) can distinguish "collected" from "silently skipped because | |
| 207 | + * the provider went inactive" and avoid recording false progress. | |
| 208 | + * @return bool True when the form's strings were registered, false when the provider | |
| 209 | + * was unavailable and nothing was done. | |
| 123 | 210 | */ |
| 124 | - public function collect( int $form_id ): void { | |
| 211 | + public function collect( int $form_id ): bool { | |
| 125 | 212 | $provider = Multilingual_Manager::get_instance()->provider(); |
| 126 | 213 | |
| 127 | 214 | if ( ! $provider->is_active() ) { |
| 128 | - return; | |
| 215 | + return false; | |
| 129 | 216 | } |
| 130 | 217 | |
| 131 | 218 | // Group every per-form string into a single WPML String Package so they |
| 132 | 219 | // surface together under the form in the Translation Editor (instead of as |
| @@ -138,8 +225,18 @@ | ||
| 138 | 225 | if ( $this->packages_supported ) { |
| 139 | 226 | $provider->start_package( $package ); |
| 140 | 227 | } |
| 141 | 228 | |
| 229 | + // Form title (post title) — shown as a heading on the form and as the | |
| 230 | + // instant-form banner, so it is translatable like any other string. | |
| 231 | + $this->register_form_string( | |
| 232 | + $form_id, | |
| 233 | + String_Translator::title_name(), | |
| 234 | + Helper::get_string_value( get_the_title( $form_id ) ), | |
| 235 | + // "Group: Leaf" — WPML splits on ': ' to nest this under a Settings group. | |
| 236 | + __( 'Settings', 'sureforms' ) . ': ' . __( 'Form title', 'sureforms' ) | |
| 237 | + ); | |
| 238 | + | |
| 142 | 239 | // Submit button text. |
| 143 | 240 | $this->register_form_string( |
| 144 | 241 | $form_id, |
| 145 | 242 | String_Translator::submit_button_name(), |
| @@ -183,23 +280,29 @@ | ||
| 183 | 280 | |
| 184 | 281 | // reply_to is an email address (or a smart tag resolving to one), not |
| 185 | 282 | // human-readable copy, so it is intentionally excluded from the |
| 186 | 283 | // translatable set. from_name can legitimately be a localized display name. |
| 284 | + // [ meta key, label, editor type ]. The string name stays keyed on | |
| 285 | + // 'body' for continuity, but the value has to be read from | |
| 286 | + // `email_body` - that is what _srfm_email_notification's sanitize | |
| 287 | + // callback stores, so reading 'body' always found nothing and the | |
| 288 | + // message body was never registered for translation at all. | |
| 187 | 289 | $fields = [ |
| 188 | - 'subject' => [ __( 'Subject', 'sureforms' ), 'LINE' ], | |
| 189 | - 'body' => [ __( 'Message body', 'sureforms' ), 'AREA' ], | |
| 190 | - 'from_name' => [ __( '"From" name', 'sureforms' ), 'LINE' ], | |
| 290 | + 'subject' => [ 'subject', __( 'Subject', 'sureforms' ), 'LINE' ], | |
| 291 | + 'body' => [ 'email_body', __( 'Message body', 'sureforms' ), 'AREA' ], | |
| 292 | + 'from_name' => [ 'from_name', __( '"From" name', 'sureforms' ), 'LINE' ], | |
| 191 | 293 | ]; |
| 192 | 294 | /* translators: %d is the notification number. */ |
| 193 | 295 | $notification_group = __( 'Notifications', 'sureforms' ) . '/' . sprintf( __( 'Notification #%d', 'sureforms' ), (int) $index + 1 ); |
| 194 | 296 | foreach ( $fields as $field => $meta ) { |
| 195 | - $value = isset( $notification[ $field ] ) ? Helper::get_string_value( $notification[ $field ] ) : ''; | |
| 297 | + $meta_key = $meta[0]; | |
| 298 | + $value = isset( $notification[ $meta_key ] ) ? Helper::get_string_value( $notification[ $meta_key ] ) : ''; | |
| 196 | 299 | $this->register_form_string( |
| 197 | 300 | $form_id, |
| 198 | 301 | String_Translator::notification_name( (int) $index, $field ), |
| 199 | 302 | $value, |
| 200 | - $notification_group . ': ' . $meta[0], | |
| 201 | - $meta[1] | |
| 303 | + $notification_group . ': ' . $meta[1], | |
| 304 | + $meta[2] | |
| 202 | 305 | ); |
| 203 | 306 | } |
| 204 | 307 | } |
| 205 | 308 | |
| @@ -225,8 +328,10 @@ | ||
| 225 | 328 | if ( $this->packages_supported ) { |
| 226 | 329 | $provider->finish_package( $package ); |
| 227 | 330 | } |
| 228 | 331 | $this->active_package = null; |
| 332 | + | |
| 333 | + return true; | |
| 229 | 334 | } |
| 230 | 335 | |
| 231 | 336 | /** |
| 232 | 337 | * Register every built-in dynamic validation message with the multilingual |