PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.0
2.7.0 2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.0 2.3.3 2.3.2 2.3.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 All 129 releases
wdesignkit / includes / admin / import / class-wdkit-import-settings.php

class-wdkit-import-settings.php in WDesignKit – AI Templates, Widget Builder & MCP Workflow 2.7.0, at includes/admin/import/class-wdkit-import-settings.php

303 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Site / theme / plugin settings for an import.
4 *
5 * ── The whitelist ──────────────────────────────────────────────────────────
6 *
7 * Traced from the three existing handlers. These are the ONLY WordPress options the browser
8 * importer writes through them, and therefore the only ones this class can write:
9 *
10 * update_site_setting() show_on_front, page_on_front, woocommerce_shop_page_id,
11 * blogname, blogdescription
12 * update_theme_setting() nxt-theme-options (fixed sub-keys, no input at all)
13 * update_plugin_setting() elementor_unfiltered_files_upload,
14 * elementor_load_fa4_shim,
15 * elementor_experiment-container,
16 * elementor_experiment-e_font_icon_svg
17 *
18 * There is deliberately no method here that takes an option name. A caller chooses *which
19 * group* to apply and supplies values for known fields; it can never name a key. That is the
20 * whole design — a future remote payload must not be able to reach `update_option()`.
21 *
22 * All three underlying writes are idempotent: assignments, not merges.
23 *
24 * @package Wdesignkit
25 * @since 2.6.5
26 */
27
28 if ( ! defined( 'ABSPATH' ) ) {
29 exit;
30 }
31
32 if ( ! class_exists( 'Wdkit_Import_Settings' ) ) {
33
34 /**
35 * Whitelisted settings application.
36 */
37 class Wdkit_Import_Settings {
38
39 /**
40 * Site options this class may write, and how each is validated.
41 *
42 * @var array<string,string>
43 */
44 private static $site_options = array(
45 'show_on_front' => 'front_type',
46 'page_on_front' => 'post_id',
47 'woocommerce_shop_page_id' => 'post_id',
48 'blogname' => 'text',
49 'blogdescription' => 'text',
50 );
51
52 /**
53 * Plugin identifiers whose settings group is recognised.
54 *
55 * @var string[]
56 */
57 private static $plugin_types = array( 'elementor' );
58
59 /**
60 * Apply site settings — front page, shop page, name, tagline.
61 *
62 * @param array $context Validated import context.
63 * @param array $page_map original template id => imported post id.
64 * @param array $page_records Per-page results, used for theme-builder conditions.
65 * @return array{applied:string[],skipped:string[]}
66 */
67 public static function apply_site( $context, $page_map = array(), $page_records = array() ) {
68 $result = array(
69 'applied' => array(),
70 'skipped' => array(),
71 );
72
73 /* Front page first: it is the cheaper check and the more accurate reason. The
74 * underlying handler treats a missing front page id as "site not found" and writes
75 * nothing at all, so there is no point reaching for the API to find that out. */
76 $front_id = self::front_page_id( $page_records );
77
78 /* Reported whether or not the write succeeds, because a caller needs to know which
79 * page WAS chosen even when the API call behind it could not be reached. Additive:
80 * existing readers of `applied`/`skipped` are unaffected. */
81 $result['front_page_id'] = $front_id;
82 $result['shop_page_id'] = self::shop_page_id( $page_records );
83
84 if ( 0 === $front_id ) {
85 $result['skipped'][] = 'no_front_page';
86
87 return $result;
88 }
89
90 if ( ! class_exists( 'Wdkit_Api_Call' ) ) {
91 $result['skipped'][] = 'api_unavailable';
92
93 return $result;
94 }
95
96 $info = ! empty( $context['site_info'] ) && is_array( $context['site_info'] ) ? $context['site_info'] : array();
97
98 $args = array(
99 'id' => $front_id,
100 'temp_type' => 'page',
101 'shop_id' => $result['shop_page_id'],
102 'site_name' => isset( $info['site_name'] ) ? $info['site_name'] : '',
103 'site_tagline' => isset( $info['tagline'] ) ? $info['tagline'] : '',
104 'page_information' => self::page_information( $page_records ),
105
106 /* This step (stage_setup) always runs before stage_finalize's
107 * schedule_media_sweep() ever queues anything, so the attachment sweep it
108 * triggers can never find a page already deferred and would otherwise download
109 * every still-remote image synchronously right here - measured at 99s of a ~108s
110 * import. schedule_media_sweep() runs later in the same import and picks up
111 * everything still remote, so nothing is skipped, only moved off the request.
112 * See ClickUp 14ynqxywncc. */
113 'defer_attachment_downloads' => true,
114 );
115
116 $response = Wdkit_Api_Call::get_instance()->wdkit_apply_site_settings_data( $args );
117
118 /* A transport failure returns WP_Error, and empty() on one is fatal in PHP 8 -
119 * see the note in Wdkit_Import_Dependencies::enable_widgets(). */
120 $response = is_array( $response ) ? $response : array();
121
122 if ( ! empty( $response['success'] ) ) {
123 $result['applied'] = array( 'show_on_front', 'page_on_front' );
124
125 if ( '' !== $args['site_name'] ) {
126 $result['applied'][] = 'blogname';
127 }
128
129 if ( '' !== $args['site_tagline'] ) {
130 $result['applied'][] = 'blogdescription';
131 }
132
133 if ( '' !== $args['shop_id'] ) {
134 $result['applied'][] = 'woocommerce_shop_page_id';
135 }
136 } else {
137 $result['skipped'][] = 'handler_reported_failure';
138 }
139
140 return $result;
141 }
142
143 /**
144 * Apply the Nexter theme container settings.
145 *
146 * Takes no values — the underlying handler writes a fixed set. Skipped when the theme
147 * option does not exist and the active theme is not Nexter, so an unrelated theme is
148 * never given Nexter's options.
149 *
150 * @return array{applied:bool,skipped:string}
151 */
152 public static function apply_theme() {
153 if ( ! class_exists( 'Wdkit_Api_Call' ) ) {
154 return array(
155 'applied' => false,
156 'skipped' => 'api_unavailable',
157 );
158 }
159
160 $response = Wdkit_Api_Call::get_instance()->wdkit_apply_theme_settings_data();
161 $response = is_array( $response ) ? $response : array();
162
163 return array(
164 'applied' => ! empty( $response['success'] ),
165 'skipped' => '',
166 );
167 }
168
169 /**
170 * Apply a plugin's settings group.
171 *
172 * Unknown identifiers are ignored rather than passed through — the underlying handler
173 * would report "Plugin not found", and there is nothing to gain from asking it.
174 *
175 * @param string $plugin_type Plugin identifier.
176 * @return array{applied:bool,skipped:string}
177 */
178 public static function apply_plugin( $plugin_type ) {
179 $plugin_type = is_string( $plugin_type ) ? strtolower( trim( $plugin_type ) ) : '';
180
181 if ( ! in_array( $plugin_type, self::$plugin_types, true ) ) {
182 return array(
183 'applied' => false,
184 'skipped' => 'unknown_plugin_type',
185 );
186 }
187
188 if ( ! class_exists( 'Wdkit_Api_Call' ) ) {
189 return array(
190 'applied' => false,
191 'skipped' => 'api_unavailable',
192 );
193 }
194
195 $response = Wdkit_Api_Call::get_instance()->wdkit_apply_plugin_settings_data( $plugin_type );
196 $response = is_array( $response ) ? $response : array();
197
198 return array(
199 'applied' => ! empty( $response['success'] ),
200 'skipped' => '',
201 );
202 }
203
204 /**
205 * Which imported page should become the front page.
206 *
207 * Mirrors the browser's rule: a title containing "home" or "landing" wins, otherwise
208 * the first imported page. Returns 0 when nothing qualifies, which is what makes
209 * apply_site() stand down rather than pointing the front page at something arbitrary.
210 *
211 * @param array[] $page_records Per-page import results.
212 * @return int
213 */
214 private static function front_page_id( $page_records ) {
215 $first = 0;
216
217 foreach ( (array) $page_records as $page ) {
218 if ( ! is_array( $page ) || empty( $page['post_id'] ) ) {
219 continue;
220 }
221
222 $post_id = (int) $page['post_id'];
223
224 if ( 0 === $first ) {
225 $first = $post_id;
226 }
227
228 $title = isset( $page['title'] ) ? strtolower( (string) $page['title'] ) : '';
229
230 if ( false !== strpos( $title, 'home' ) || false !== strpos( $title, 'landing' ) ) {
231 return $post_id;
232 }
233 }
234
235 return $first;
236 }
237
238 /**
239 * Which imported page is the shop, if any.
240 *
241 * @param array[] $page_records Per-page import results.
242 * @return string Post id as a string, or '' when there is no shop page.
243 */
244 private static function shop_page_id( $page_records ) {
245 foreach ( (array) $page_records as $page ) {
246 if ( ! is_array( $page ) || empty( $page['post_id'] ) ) {
247 continue;
248 }
249
250 $title = isset( $page['title'] ) ? strtolower( (string) $page['title'] ) : '';
251
252 if ( false !== strpos( $title, 'shop' ) || false !== strpos( $title, 'store' ) ) {
253 return (string) (int) $page['post_id'];
254 }
255 }
256
257 return '';
258 }
259
260 /**
261 * The page map in the shape wdkit_nxt_thembuilder_update() expects.
262 *
263 * @param array[] $page_records Per-page import results.
264 * @return array[]
265 */
266 private static function page_information( $page_records ) {
267 $info = array();
268
269 foreach ( (array) $page_records as $page ) {
270 if ( ! is_array( $page ) || empty( $page['post_id'] ) ) {
271 continue;
272 }
273
274 $info[] = array(
275 'inserted_id' => (int) $page['post_id'],
276 'old_page_id' => isset( $page['old_page_id'] ) ? $page['old_page_id'] : '',
277 'post_type' => isset( $page['post_type'] ) ? $page['post_type'] : 'page',
278 );
279 }
280
281 return $info;
282 }
283
284 /**
285 * The complete whitelist, for tests and for anyone auditing this later.
286 *
287 * @return array{site:string[],plugin:string[],theme:string[]}
288 */
289 public static function whitelist() {
290 return array(
291 'site' => array_keys( self::$site_options ),
292 'plugin' => array(
293 'elementor_unfiltered_files_upload',
294 'elementor_load_fa4_shim',
295 'elementor_experiment-container',
296 'elementor_experiment-e_font_icon_svg',
297 ),
298 'theme' => array( 'nxt-theme-options' ),
299 );
300 }
301 }
302 }
303