| @@ -35,9 +35,8 @@ | ||
| 35 | 35 | add_action( 'init', [ $this, 'register_post_types' ] ); |
| 36 | 36 | add_action( 'init', [ $this, 'register_post_metas' ] ); |
| 37 | 37 | add_shortcode( 'sureforms', [ $this, 'forms_shortcode' ] ); |
| 38 | 38 | add_action( 'manage_posts_extra_tablenav', [ $this, 'maybe_render_blank_form_state' ] ); |
| 39 | - add_action( 'admin_bar_menu', [ $this, 'remove_admin_bar_menu_item' ], 80, 1 ); | |
| 40 | 39 | add_action( 'template_redirect', [ $this, 'srfm_instant_form_redirect' ] ); |
| 41 | 40 | add_action( 'template_redirect', [ $this, 'disable_sureforms_archive_page' ], 9 ); |
| 42 | 41 | add_action( 'load-edit.php', [ $this, 'redirect_forms_listing_page' ] ); |
| 43 | 42 | |
| @@ -42,8 +41,9 @@ | ||
| 42 | 41 | add_action( 'load-edit.php', [ $this, 'redirect_forms_listing_page' ] ); |
| 43 | 42 | |
| 44 | 43 | add_filter( 'rest_prepare_sureforms_form', [ $this, 'sureforms_normalize_meta_for_rest' ], 10, 2 ); |
| 45 | 44 | add_action( 'admin_bar_menu', [ $this, 'add_edit_form_to_admin_bar_menu' ], 100 ); |
| 45 | + add_action( 'admin_bar_menu', [ $this, 'add_new_form_to_admin_bar_menu' ], 100 ); | |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | 48 | /** |
| 49 | 49 | * Redirect the forms listing page to the updated forms page. |
| @@ -103,8 +103,47 @@ | ||
| 103 | 103 | ); |
| 104 | 104 | } |
| 105 | 105 | |
| 106 | 106 | /** |
| 107 | + * Add a "Form" shortcut to the admin bar "+ New" menu (#3026). | |
| 108 | + * | |
| 109 | + * Mirrors how core post types appear under "+ New", but added manually rather | |
| 110 | + * than via `show_in_admin_bar` so it does not also register a second front-end | |
| 111 | + * "Edit" node alongside the custom one in add_edit_form_to_admin_bar_menu(). | |
| 112 | + * Gated on the form CPT's own create capability (manage_options for this CPT), | |
| 113 | + * so it only shows for users who can actually create a form. | |
| 114 | + * | |
| 115 | + * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance. | |
| 116 | + * @since 2.12.4 | |
| 117 | + * @return void | |
| 118 | + */ | |
| 119 | + public function add_new_form_to_admin_bar_menu( $wp_admin_bar ) { | |
| 120 | + if ( ! is_admin_bar_showing() || ! $wp_admin_bar instanceof WP_Admin_Bar ) { | |
| 121 | + return; | |
| 122 | + } | |
| 123 | + | |
| 124 | + $post_type = get_post_type_object( SRFM_FORMS_POST_TYPE ); | |
| 125 | + | |
| 126 | + if ( ! $post_type || empty( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->create_posts ) ) { | |
| 127 | + return; | |
| 128 | + } | |
| 129 | + | |
| 130 | + // Core registers the "+ New" (new-content) group at priority 70, so running at | |
| 131 | + // 100 places this node inside it. If core skipped the group (the user can create | |
| 132 | + // nothing else), WP_Admin_Bar::_bind() drops this orphan node silently. | |
| 133 | + // name_admin_bar is the label core uses for "+ New" children; escaped because | |
| 134 | + // WP_Admin_Bar echoes node titles unescaped. | |
| 135 | + $wp_admin_bar->add_node( | |
| 136 | + [ | |
| 137 | + 'id' => 'new-' . SRFM_FORMS_POST_TYPE, | |
| 138 | + 'parent' => 'new-content', | |
| 139 | + 'title' => esc_html( $post_type->labels->name_admin_bar ), | |
| 140 | + 'href' => esc_url( admin_url( 'post-new.php?post_type=' . SRFM_FORMS_POST_TYPE ) ), | |
| 141 | + ] | |
| 142 | + ); | |
| 143 | + } | |
| 144 | + | |
| 145 | + /** | |
| 107 | 146 | * Remove this method in the future once _srfm_form_confirmation meta is updated. |
| 108 | 147 | * Normalize the _srfm_form_confirmation meta before it's sent to the REST API. |
| 109 | 148 | * Ensures the meta data is type-safe and includes necessary defaults like `hide_copy`. |
| 110 | 149 | * |
| @@ -114,22 +153,43 @@ | ||
| 114 | 153 | * @return WP_REST_Response Modified REST response with normalized meta. |
| 115 | 154 | * @since 1.7.3 |
| 116 | 155 | */ |
| 117 | 156 | public function sureforms_normalize_meta_for_rest( $response, $post ) { |
| 118 | - $meta_raw = get_post_meta( $post->ID, '_srfm_form_confirmation', true ); | |
| 119 | - $form_confirmation = maybe_unserialize( is_string( $meta_raw ) ? $meta_raw : '' ); | |
| 157 | + $meta_raw = get_post_meta( $post->ID, '_srfm_form_confirmation', true ); | |
| 158 | + // Meta may be a PHP array (new forms stored via update_post_meta with an array) | |
| 159 | + // or a serialized/JSON string (legacy forms). Handle both. | |
| 160 | + $form_confirmation = is_array( $meta_raw ) ? $meta_raw : maybe_unserialize( is_string( $meta_raw ) ? $meta_raw : '' ); | |
| 120 | 161 | |
| 121 | 162 | if ( ! is_array( $form_confirmation ) ) { |
| 122 | 163 | return $response; |
| 123 | 164 | } |
| 124 | 165 | |
| 166 | + // Only normalize keys that extensions (e.g. SureForms Pro) have actually | |
| 167 | + // declared in the REST schema; otherwise REST PUT validation rejects them | |
| 168 | + // with "<key> is not a valid property of Object.". | |
| 169 | + $registered = get_registered_meta_keys( 'post', SRFM_FORMS_POST_TYPE ); | |
| 170 | + $item_properties = $registered['_srfm_form_confirmation']['show_in_rest']['schema']['items']['properties'] ?? []; | |
| 171 | + | |
| 125 | 172 | foreach ( $form_confirmation as $index => $item ) { |
| 126 | 173 | if ( ! is_array( $item ) ) { |
| 127 | 174 | continue; |
| 128 | 175 | } |
| 129 | 176 | |
| 130 | - $form_confirmation[ $index ]['hide_copy'] = ! empty( $item['hide_copy'] ); | |
| 131 | - $form_confirmation[ $index ]['hide_download_all'] = ! empty( $item['hide_download_all'] ); | |
| 177 | + if ( isset( $item_properties['hide_copy'] ) ) { | |
| 178 | + $form_confirmation[ $index ]['hide_copy'] = ! empty( $item['hide_copy'] ); | |
| 179 | + } | |
| 180 | + if ( isset( $item_properties['hide_download_all'] ) ) { | |
| 181 | + $form_confirmation[ $index ]['hide_download_all'] = ! empty( $item['hide_download_all'] ); | |
| 182 | + } | |
| 183 | + | |
| 184 | + // DOMDocument::saveHTML() strips the "data:" prefix from data URIs in src attributes. | |
| 185 | + // Restore it so the editor displays SVG images correctly. | |
| 186 | + if ( isset( $item['message'] ) && is_string( $item['message'] ) && false !== strpos( $item['message'], 'src="image/svg+xml;base64' ) ) { | |
| 187 | + $normalized = preg_replace( '/src="image\/svg\+xml;base64/', 'src="data:image/svg+xml;base64', $item['message'] ); | |
| 188 | + if ( is_string( $normalized ) ) { | |
| 189 | + $form_confirmation[ $index ]['message'] = $normalized; | |
| 190 | + } | |
| 191 | + } | |
| 132 | 192 | } |
| 133 | 193 | |
| 134 | 194 | $response_data = $response->get_data(); |
| 135 | 195 | if ( is_array( $response_data ) ) { |
| @@ -271,20 +331,8 @@ | ||
| 271 | 331 | } |
| 272 | 332 | } |
| 273 | 333 | |
| 274 | 334 | /** |
| 275 | - * Remove add new form menu item. | |
| 276 | - * | |
| 277 | - * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance. | |
| 278 | - * | |
| 279 | - * @return void | |
| 280 | - * @since 0.0.1 | |
| 281 | - */ | |
| 282 | - public function remove_admin_bar_menu_item( $wp_admin_bar ) { | |
| 283 | - $wp_admin_bar->remove_node( 'new-sureforms_form' ); | |
| 284 | - } | |
| 285 | - | |
| 286 | - /** | |
| 287 | 335 | * Show blank slate styles. |
| 288 | 336 | * |
| 289 | 337 | * @return void |
| 290 | 338 | * @since 0.0.1 |
| @@ -399,8 +447,13 @@ | ||
| 399 | 447 | '_srfm_is_ai_generated' => 'boolean', |
| 400 | 448 | ] |
| 401 | 449 | ); |
| 402 | 450 | |
| 451 | + // NOTE: `_srfm_form_views` is intentionally NOT registered here. It is a | |
| 452 | + // server-side counter written only by Form_Views (get/add_post_meta + atomic | |
| 453 | + // SQL). Exposing it to the block editor via show_in_rest let a form save / | |
| 454 | + // autosave round-trip a stale value and clobber the live count back to 0. | |
| 455 | + | |
| 403 | 456 | // Form Custom CSS meta. |
| 404 | 457 | register_post_meta( |
| 405 | 458 | 'sureforms_form', |
| 406 | 459 | '_srfm_form_custom_css', |
| @@ -801,8 +854,12 @@ | ||
| 801 | 854 | ], |
| 802 | 855 | 'instant_form_border_radius_link' => [ |
| 803 | 856 | 'type' => 'boolean', |
| 804 | 857 | ], |
| 858 | + // Disable default SureForms styling. | |
| 859 | + 'disable_default_styles' => [ | |
| 860 | + 'type' => 'boolean', | |
| 861 | + ], | |
| 805 | 862 | ], |
| 806 | 863 | ], |
| 807 | 864 | ], |
| 808 | 865 | 'default' => [ |
| @@ -881,8 +938,10 @@ | ||
| 881 | 938 | 'instant_form_border_radius_bottom' => 12, |
| 882 | 939 | 'instant_form_border_radius_left' => 12, |
| 883 | 940 | 'instant_form_border_radius_unit' => 'px', |
| 884 | 941 | 'instant_form_border_radius_link' => true, |
| 942 | + // Disable default SureForms styling. | |
| 943 | + 'disable_default_styles' => false, | |
| 885 | 944 | ], |
| 886 | 945 | ] |
| 887 | 946 | ); |
| 888 | 947 | |
| @@ -1084,9 +1143,9 @@ | ||
| 1084 | 1143 | 'id' => isset( $item['id'] ) ? intval( $item['id'] ) : 0, |
| 1085 | 1144 | 'confirmation_type' => isset( $item['confirmation_type'] ) ? sanitize_text_field( $item['confirmation_type'] ) : '', |
| 1086 | 1145 | 'page_url' => isset( $item['page_url'] ) ? esc_url_raw( $item['page_url'] ) : '', |
| 1087 | 1146 | 'custom_url' => isset( $item['custom_url'] ) ? esc_url_raw( $item['custom_url'] ) : '', |
| 1088 | - 'message' => isset( $item['message'] ) ? Helper::strip_js_attributes( $item['message'] ) : '', | |
| 1147 | + 'message' => isset( $item['message'] ) ? wp_kses_post( $item['message'] ) : '', | |
| 1089 | 1148 | 'submission_action' => isset( $item['submission_action'] ) ? sanitize_text_field( $item['submission_action'] ) : '', |
| 1090 | 1149 | 'enable_query_params' => isset( $item['enable_query_params'] ) ? filter_var( $item['enable_query_params'], FILTER_VALIDATE_BOOLEAN ) : false, |
| 1091 | 1150 | 'query_params' => isset( $item['query_params'] ) && is_array( $item['query_params'] ) |
| 1092 | 1151 | ? array_map( |