← All changes
|
includes/abilities/templates/wdesignkit-save-template.php
+122
-9
2.5.5
→
2.7.0
View file →
| @@ -9,12 +9,12 @@ | ||
| 9 | 9 | exit(); |
| 10 | 10 | } |
| 11 | 11 | |
| 12 | 12 | wp_register_ability('wdesignkit/save-template', [ |
| 13 | - 'label' => __('Save WDesignKit Template', 'sprout-mcp'), | |
| 13 | + 'label' => __('Save WDesignKit Template', 'wdesignkit'), | |
| 14 | 14 | 'description' => __( |
| 15 | 15 | 'Saves a page builder layout to the user\'s WDesignKit cloud library as a new template. Provide the full builder data payload (JSON string for Gutenberg, base64-decoded Elementor export for Elementor) plus the source post_id so any nxt-* custom meta is captured alongside it.', |
| 16 | - 'sprout-mcp', | |
| 16 | + 'wdesignkit', | |
| 17 | 17 | ), |
| 18 | 18 | 'category' => 'wdesignkit', |
| 19 | 19 | 'input_schema' => [ |
| 20 | 20 | 'type' => 'object', |
| @@ -39,8 +39,13 @@ | ||
| 39 | 39 | 'type' => [ |
| 40 | 40 | 'type' => 'string', |
| 41 | 41 | 'description' => 'Template type (e.g. "page", "section", "block").', |
| 42 | 42 | ], |
| 43 | + 'plugins' => [ | |
| 44 | + 'type' => 'array', | |
| 45 | + 'description' => 'Optional list of cloud plugin IDs (integers) this template requires — populates the template\'s plugins_id so the library shows its plugin dependencies. Get the IDs from wdesignkit/get-template-plugins.', | |
| 46 | + 'items' => ['type' => 'integer'], | |
| 47 | + ], | |
| 43 | 48 | ], |
| 44 | 49 | 'required' => ['builder', 'data'], |
| 45 | 50 | 'additionalProperties' => false, |
| 46 | 51 | ], |
| @@ -52,10 +57,11 @@ | ||
| 52 | 57 | 'response' => ['type' => ['object', 'array']], |
| 53 | 58 | ], |
| 54 | 59 | ], |
| 55 | 60 | 'execute_callback' => 'wdesignkit_mcp_save_template', |
| 56 | - 'permission_callback' => 'sprout_mcp_permission_callback', | |
| 61 | + 'permission_callback' => 'wdesignkit_mcp_permission_callback', | |
| 57 | 62 | 'meta' => [ |
| 63 | + 'public' => true, | |
| 58 | 64 | 'show_in_rest' => true, |
| 59 | 65 | 'mcp' => ['public' => true], |
| 60 | 66 | 'annotations' => [ |
| 61 | 67 | 'instructions' => implode("\n", [ |
| @@ -84,8 +90,14 @@ | ||
| 84 | 90 | $post_id = sanitize_text_field((string) ($input['post_id'] ?? '')); |
| 85 | 91 | $name = sanitize_text_field((string) ($input['name'] ?? '')); |
| 86 | 92 | $type = sanitize_text_field((string) ($input['type'] ?? '')); |
| 87 | 93 | |
| 94 | + // Map the caller's friendly type to the literal value the cloud stores and filters on | |
| 95 | + // ('section' / 'pagetemplate'). "block" has no confirmed cloud equivalent, so it passes | |
| 96 | + // through unmapped rather than being silently rewritten to something wrong. | |
| 97 | + $cloud_type_map = ['page' => 'pagetemplate', 'section' => 'section']; | |
| 98 | + $cloud_type = $cloud_type_map[$type] ?? $type; | |
| 99 | + | |
| 88 | 100 | if (!in_array($builder, ['elementor', 'gutenberg'], true)) { |
| 89 | 101 | return ['success' => false, 'message' => 'builder must be "elementor" or "gutenberg".']; |
| 90 | 102 | } |
| 91 | 103 | if ($data === '') { |
| @@ -91,9 +103,73 @@ | ||
| 91 | 103 | if ($data === '') { |
| 92 | 104 | return ['success' => false, 'message' => 'data is required — pass the full builder export payload.']; |
| 93 | 105 | } |
| 94 | 106 | |
| 107 | + // Wrap the payload in the envelope the cloud, the importer and the editor JS all expect — | |
| 108 | + // the same one the Save Template UI builds in main_save_template.js: | |
| 109 | + // { file_type, title, page_id, content, el_type, settings } with the layout in `content`. | |
| 110 | + // | |
| 111 | + // Sending the raw layout as `data` instead broke both builders, in different ways: | |
| 112 | + // Elementor — _elementor_data decodes to a LIST, and adding the string key 'custom_meta' to | |
| 113 | + // a list makes wp_json_encode() emit an object, so the indices became "0","1",… | |
| 114 | + // Gutenberg — block markup is a STRING, so json_decode() returned null, the is_array() | |
| 115 | + // branch was skipped, and the markup was stored bare with no envelope at all. | |
| 116 | + // The editor then runs JSON.parse() on it and throws. | |
| 117 | + // Building the envelope first fixes both: custom_meta now goes onto an OBJECT, where an extra | |
| 118 | + // key cannot reshape `content`. | |
| 119 | + // | |
| 120 | + // el_type is left empty — the UI derives it from the Elementor preview DOM, which has no | |
| 121 | + // server-side equivalent. | |
| 122 | + if ('elementor' === $builder) { | |
| 123 | + $content = json_decode($data, true); | |
| 124 | + if (!is_array($content)) { | |
| 125 | + return [ | |
| 126 | + 'success' => false, | |
| 127 | + 'message' => 'data is not valid Elementor JSON — pass the _elementor_data export (a JSON array), not markup.', | |
| 128 | + ]; | |
| 129 | + } | |
| 130 | + } else { | |
| 131 | + // Gutenberg content is serialized block markup; keep the string unless block JSON was sent. | |
| 132 | + $decoded = json_decode($data, true); | |
| 133 | + $content = is_array($decoded) ? $decoded : $data; | |
| 134 | + | |
| 135 | + } | |
| 136 | + | |
| 137 | + $settings = []; | |
| 95 | 138 | if ($post_id !== '') { |
| 139 | + $page_settings = get_post_meta((int) $post_id, '_elementor_page_settings', true); | |
| 140 | + if (is_array($page_settings) && !empty($page_settings)) { | |
| 141 | + $settings = $page_settings; | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + $envelope = [ | |
| 146 | + 'file_type' => 'elementor' === $builder ? 'elementor' : 'wp_block', | |
| 147 | + 'title' => $name, | |
| 148 | + 'page_id' => $post_id !== '' ? (int) $post_id : 0, | |
| 149 | + 'content' => $content, | |
| 150 | + 'el_type' => '', | |
| 151 | + 'settings' => $settings, | |
| 152 | + ]; | |
| 153 | + | |
| 154 | + // The Plus Addons resolves its global button styles, radii and shadows from the kit by | |
| 155 | + // `_id`. Without those definitions travelling with the template, an imported section keeps | |
| 156 | + // the reference but loses the styling. Scoped to what this layout actually references, so a | |
| 157 | + // section carries its own globals rather than a copy of the whole kit. | |
| 158 | + $tp_globals = ('elementor' === $builder && is_array($content)) ? wdesignkit_mcp_tp_globals($content) : []; | |
| 159 | + if (!empty($tp_globals)) { | |
| 160 | + $envelope['tp_globals'] = $tp_globals; | |
| 161 | + | |
| 162 | + // A Plus global can reference an Elementor global colour or font of its own. Those refs | |
| 163 | + // live in the lists rather than the layout, so they need capturing separately or the | |
| 164 | + // button style arrives pointing at an id the destination kit does not have. | |
| 165 | + $tp_refs = wdesignkit_mcp_tp_global_refs($tp_globals); | |
| 166 | + if (!empty($tp_refs['color']) || !empty($tp_refs['typography'])) { | |
| 167 | + $envelope['tp_global_refs'] = $tp_refs; | |
| 168 | + } | |
| 169 | + } | |
| 170 | + | |
| 171 | + if ($post_id !== '') { | |
| 96 | 172 | $custom_fields = []; |
| 97 | 173 | foreach ((array) get_post_custom($post_id) as $key => $value) { |
| 98 | 174 | if (is_string($key) && str_contains($key, 'nxt-')) { |
| 99 | 175 | $custom_fields[$key] = $value; |
| @@ -100,24 +176,61 @@ | ||
| 100 | 176 | } |
| 101 | 177 | } |
| 102 | 178 | |
| 103 | 179 | if (!empty($custom_fields)) { |
| 104 | - $decoded = json_decode($data, true); | |
| 105 | - if (is_array($decoded)) { | |
| 106 | - $decoded['custom_meta'] = $custom_fields; | |
| 107 | - $data = wp_json_encode($decoded); | |
| 108 | - } | |
| 180 | + $envelope['custom_meta'] = $custom_fields; | |
| 109 | 181 | } |
| 110 | 182 | } |
| 111 | 183 | |
| 184 | + $data = wp_json_encode($envelope); | |
| 185 | + | |
| 112 | 186 | $args = [ |
| 113 | 187 | 'token' => $auth['token'], |
| 114 | 188 | 'data' => $data, |
| 115 | 189 | 'post_id' => $post_id, |
| 116 | 190 | 'builder' => $builder, |
| 191 | + // The cloud SetSaveTemplate endpoint reads the display name from 'title' | |
| 192 | + // (request->get('title')) and ignores 'name' — sending only 'name' is why | |
| 193 | + // templates saved via this ability came back with a blank title. Send 'title' | |
| 194 | + // (keep 'name' too, harmlessly, for any other consumer). | |
| 195 | + 'title' => $name, | |
| 117 | 196 | 'name' => $name, |
| 118 | - 'type' => $type, | |
| 197 | + // The cloud stores the template's type from 'template_type', NOT 'type': | |
| 198 | + // $TemplateType = $request->get('template_type'); | |
| 199 | + // 'type' => !empty($TemplateType) ? $TemplateType : 'pagetemplate', | |
| 200 | + // (WdkitPluginController::SetSaveTemplate lines 171 / 252). Sending only 'type' meant | |
| 201 | + // $TemplateType was always empty, so EVERY template saved through this ability was | |
| 202 | + // stored as 'pagetemplate' regardless of the caller's input — a section saved here | |
| 203 | + // could never be found by list-templates(type: 'section'). Send 'template_type' too. | |
| 204 | + // | |
| 205 | + // The cloud's own queries match the literal values 'section' and 'pagetemplate' | |
| 206 | + // (see the whereIn at SetSaveTemplate line 574), not 'page'/'block', so map first. | |
| 207 | + 'type' => $type, | |
| 208 | + 'template_type' => $cloud_type, | |
| 119 | 209 | ]; |
| 210 | + | |
| 211 | + // Optional: record which cloud plugin IDs this template requires. The cloud | |
| 212 | + // SetSaveTemplate endpoint reads 'plugins' as a JSON string of integer plugin IDs | |
| 213 | + // and stores them as the template's plugins_id (previously always empty because the | |
| 214 | + // ability had no way to supply them). | |
| 215 | + $plugins_in = $input['plugins'] ?? []; | |
| 216 | + if (is_string($plugins_in)) { | |
| 217 | + $decoded = json_decode($plugins_in, true); | |
| 218 | + $plugins_in = is_array($decoded) ? $decoded : []; | |
| 219 | + } | |
| 220 | + if (is_array($plugins_in)) { | |
| 221 | + $plugin_ids = []; | |
| 222 | + foreach ($plugins_in as $pid) { | |
| 223 | + $pid = (int) $pid; | |
| 224 | + if ($pid > 0) { | |
| 225 | + $plugin_ids[] = $pid; | |
| 226 | + } | |
| 227 | + } | |
| 228 | + if (!empty($plugin_ids)) { | |
| 229 | + // Sent as a JSON string: the cloud json_decodes 'plugins' server-side. | |
| 230 | + $args['plugins'] = wp_json_encode(array_values(array_unique($plugin_ids))); | |
| 231 | + } | |
| 232 | + } | |
| 120 | 233 | |
| 121 | 234 | $response = wdesignkit_mcp_template_cloud_call('save_template', $args, 'json'); |
| 122 | 235 | |
| 123 | 236 | return [ |