PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
← All changes | includes/extensions/Custom_Code_Manager/Custom_Code_Manager.php +204 -19 51.1.44 → 51.1.87 View file →
@@ -116,8 +116,9 @@
116 116 add_action('wp_ajax_kng_cc_import', [$this, 'ajax_import']);
117 117 add_action('wp_ajax_kng_cc_search_content', [$this, 'ajax_search_content']);
118 118 add_action('wp_ajax_kng_cc_get_snippet', [$this, 'ajax_get_snippet']);
119 119 add_action('wp_ajax_kng_cc_bulk_action', [$this, 'ajax_bulk_action']);
120 + add_action('wp_ajax_kng_cc_save_settings', [$this, 'ajax_save_settings']);
120 121
121 122 // Frontend injection
122 123 add_action('wp_head', [$this, 'inject_head'], 1);
123 124 add_action('wp_footer', [$this, 'inject_footer'], 99);
@@ -122,8 +123,11 @@
122 123 add_action('wp_head', [$this, 'inject_head'], 1);
123 124 add_action('wp_footer', [$this, 'inject_footer'], 99);
124 125 add_action('wp_body_open', [$this, 'inject_body_open'], 1);
125 126
127 + // Register custom hooks for snippets with custom_hook location
128 + add_action('init', [$this, 'register_custom_hooks'], 20);
129 +
126 130 // Script attributes filter
127 131 add_filter('script_loader_tag', [$this, 'modify_script_tag'], 10, 3);
128 132
129 133 // Debug output for admins
@@ -180,8 +184,37 @@
180 184 {
181 185 $view = isset($_GET['view']) ? sanitize_key($_GET['view']) : 'list';
182 186 $snippet_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
183 187
188 + // Theme mode (shared across all views)
189 + $theme_mode = get_user_meta(get_current_user_id(), 'king_addons_theme_mode', true);
190 + if (!in_array($theme_mode, ['light', 'dark', 'auto'], true)) {
191 + $theme_mode = 'auto';
192 + }
193 + // Store for sub-renderers
194 + $this->theme_mode = $theme_mode;
195 +
196 + // Early theme application script — runs before DOM renders to prevent flash
197 + ?>
198 + <script>
199 + (function() {
200 + var mode = '<?php echo esc_js($theme_mode); ?>';
201 + var mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
202 + function apply(isDark) {
203 + document.documentElement.classList.toggle('ka-v3-dark', isDark);
204 + if (document.body) document.body.classList.toggle('ka-v3-dark', isDark);
205 + }
206 + function onBody(fn) {
207 + if (document.body) { fn(); return; }
208 + document.addEventListener('DOMContentLoaded', fn, { once: true });
209 + }
210 + var isDark = mode === 'auto' ? !!(mql && mql.matches) : mode === 'dark';
211 + apply(isDark);
212 + onBody(function() { apply(isDark); });
213 + })();
214 + </script>
215 + <?php
216 +
184 217 switch ($view) {
185 218 case 'edit':
186 219 case 'new':
187 220 $this->render_editor_page($snippet_id);
@@ -208,8 +241,9 @@
208 241 $snippets = $this->get_all_snippets();
209 242 $snippet_count = count($snippets);
210 243 $has_pro = self::hasPro();
211 244 $at_limit = !$has_pro && $snippet_count >= self::FREE_LIMIT;
245 + $theme_mode = $this->theme_mode;
212 246
213 247 include __DIR__ . '/templates/admin-list.php';
214 248 }
215 249
@@ -233,8 +267,9 @@
233 267
234 268 $has_pro = self::hasPro();
235 269 $defaults = $this->get_default_snippet();
236 270 $config = $snippet ? array_merge($defaults, $snippet) : $defaults;
271 + $theme_mode = $this->theme_mode;
237 272
238 273 include __DIR__ . '/templates/admin-editor.php';
239 274 }
240 275
@@ -246,8 +281,9 @@
246 281 private function render_settings_page(): void
247 282 {
248 283 $settings = $this->get_settings();
249 284 $has_pro = self::hasPro();
285 + $theme_mode = $this->theme_mode;
250 286
251 287 include __DIR__ . '/templates/admin-settings.php';
252 288 }
253 289
@@ -258,8 +294,9 @@
258 294 */
259 295 private function render_import_export_page(): void
260 296 {
261 297 $has_pro = self::hasPro();
298 + $theme_mode = $this->theme_mode;
262 299
263 300 include __DIR__ . '/templates/admin-import-export.php';
264 301 }
265 302
@@ -299,10 +336,12 @@
299 336
300 337 wp_localize_script('king-addons-cc-admin', 'kngCCAdmin', [
301 338 'ajaxUrl' => admin_url('admin-ajax.php'),
302 339 'nonce' => wp_create_nonce('kng_cc_admin'),
340 + 'themeNonce' => wp_create_nonce('king_addons_dashboard_ui'),
303 341 'hasPro' => self::hasPro(),
304 342 'freeLimit' => self::FREE_LIMIT,
343 + 'upgradeUrl' => 'https://kingaddons.com/pricing/?utm_source=kng-custom-code&utm_medium=wp-admin&utm_campaign=kng',
305 344 'strings' => [
306 345 'confirmDelete' => __('Are you sure you want to delete this snippet?', 'king-addons'),
307 346 'confirmBulkDelete' => __('Are you sure you want to delete the selected snippets?', 'king-addons'),
308 347 'saving' => __('Saving...', 'king-addons'),
@@ -704,8 +743,12 @@
704 743 'snippet' => $this->get_snippet($new_id),
705 744 'message' => __('Snippet duplicated', 'king-addons'),
706 745 ]);
707 746 } else {
747 + $has_pro = self::hasPro();
748 + if (!$has_pro) {
749 + wp_send_json_error(['message' => __('Free version limit reached', 'king-addons'), 'limit' => true]);
750 + }
708 751 wp_send_json_error(['message' => __('Failed to duplicate snippet', 'king-addons')]);
709 752 }
710 753 }
711 754
@@ -862,8 +905,25 @@
862 905
863 906 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
864 907 $content_type = isset($_POST['content_type']) ? sanitize_key($_POST['content_type']) : 'page';
865 908
909 + // If search is a numeric ID, fetch that specific post directly
910 + if (ctype_digit($search)) {
911 + $post = get_post((int)$search);
912 + if ($post && $post->post_type === $content_type && $post->post_status === 'publish') {
913 + wp_send_json_success([
914 + [
915 + 'id' => $post->ID,
916 + 'title' => $post->post_title,
917 + 'type' => $post->post_type,
918 + ]
919 + ]);
920 + } else {
921 + wp_send_json_success([]);
922 + }
923 + return;
924 + }
925 +
866 926 $args = [
867 927 'post_type' => $content_type,
868 928 'posts_per_page' => 20,
869 929 'post_status' => 'publish',
@@ -1228,8 +1288,19 @@
1228 1288 return 'desktop';
1229 1289 }
1230 1290
1231 1291 /**
1292 + * Checks if the extension is enabled in settings.
1293 + *
1294 + * @return bool
1295 + */
1296 + private function is_enabled(): bool
1297 + {
1298 + $settings = $this->get_settings();
1299 + return !empty($settings['enabled']);
1300 + }
1301 +
1302 + /**
1232 1303 * Injects code into wp_head.
1233 1304 *
1234 1305 * @return void
1235 1306 */
@@ -1234,8 +1305,12 @@
1234 1305 * @return void
1235 1306 */
1236 1307 public function inject_head(): void
1237 1308 {
1309 + if (!$this->is_enabled()) {
1310 + return;
1311 + }
1312 +
1238 1313 $snippets = $this->get_snippets_for_current_page('head');
1239 1314 $this->output_snippets($snippets);
1240 1315 }
1241 1316
@@ -1245,8 +1320,12 @@
1245 1320 * @return void
1246 1321 */
1247 1322 public function inject_footer(): void
1248 1323 {
1324 + if (!$this->is_enabled()) {
1325 + return;
1326 + }
1327 +
1249 1328 $snippets = $this->get_snippets_for_current_page('footer');
1250 1329 $this->output_snippets($snippets);
1251 1330 }
1252 1331
@@ -1256,9 +1335,9 @@
1256 1335 * @return void
1257 1336 */
1258 1337 public function inject_body_open(): void
1259 1338 {
1260 - if (!self::hasPro()) {
1339 + if (!$this->is_enabled() || !self::hasPro()) {
1261 1340 return;
1262 1341 }
1263 1342
1264 1343 $snippets = $this->get_snippets_for_current_page('body_open');
@@ -1265,8 +1344,87 @@
1265 1344 $this->output_snippets($snippets);
1266 1345 }
1267 1346
1268 1347 /**
1348 + * Registers custom hooks for snippets with custom_hook location.
1349 + *
1350 + * @return void
1351 + */
1352 + public function register_custom_hooks(): void
1353 + {
1354 + if (!$this->is_enabled() || !self::hasPro()) {
1355 + return;
1356 + }
1357 +
1358 + $snippets = $this->get_all_snippets([
1359 + 'post_status' => 'publish',
1360 + ]);
1361 +
1362 + $hooks_registered = [];
1363 +
1364 + foreach ($snippets as $snippet) {
1365 + if ($snippet['location'] !== 'custom_hook' || empty($snippet['custom_hook'])) {
1366 + continue;
1367 + }
1368 +
1369 + $hook_name = $snippet['custom_hook'];
1370 +
1371 + // Register each unique hook only once
1372 + if (isset($hooks_registered[$hook_name])) {
1373 + continue;
1374 + }
1375 +
1376 + $hooks_registered[$hook_name] = true;
1377 +
1378 + add_action($hook_name, function () use ($hook_name) {
1379 + if (!$this->is_enabled()) {
1380 + return;
1381 + }
1382 +
1383 + $snippets = $this->get_snippets_for_custom_hook($hook_name);
1384 + $this->output_snippets($snippets);
1385 + }, 10);
1386 + }
1387 + }
1388 +
1389 + /**
1390 + * Gets snippets for a specific custom hook.
1391 + *
1392 + * @param string $hook_name Hook name.
1393 + * @return array
1394 + */
1395 + private function get_snippets_for_custom_hook(string $hook_name): array
1396 + {
1397 + if ($this->cached_snippets === null) {
1398 + $this->cached_snippets = $this->get_all_snippets([
1399 + 'post_status' => 'publish',
1400 + ]);
1401 + }
1402 +
1403 + $matching = [];
1404 +
1405 + foreach ($this->cached_snippets as $snippet) {
1406 + if ($snippet['location'] !== 'custom_hook' || $snippet['custom_hook'] !== $hook_name) {
1407 + continue;
1408 + }
1409 +
1410 + if ($this->should_load_snippet($snippet)) {
1411 + $matching[] = $snippet;
1412 + }
1413 + }
1414 +
1415 + usort($matching, function ($a, $b) {
1416 + $priority_diff = $a['priority'] - $b['priority'];
1417 + if ($priority_diff !== 0) {
1418 + return $priority_diff;
1419 + }
1420 + return $a['id'] - $b['id'];
1421 + });
1422 +
1423 + return $matching;
1424 + }
1425 +
1426 + /**
1269 1427 * Outputs snippets.
1270 1428 *
1271 1429 * @param array $snippets Snippets to output.
1272 1430 * @return void
@@ -1325,14 +1483,10 @@
1325 1483 if ($has_pro) {
1326 1484 if ($snippet['js_module']) {
1327 1485 $attrs[] = 'type="module"';
1328 1486 }
1329 - if ($snippet['js_defer']) {
1330 - $attrs[] = 'defer';
1331 - }
1332 - if ($snippet['js_async']) {
1333 - $attrs[] = 'async';
1334 - }
1487 + // Note: defer and async attributes are only effective on scripts with a src attribute.
1488 + // For inline scripts they have no effect per HTML spec, so we don't add them here.
1335 1489 }
1336 1490
1337 1491 echo '<script ' . implode(' ', $attrs) . ">\n";
1338 1492 echo $code;
@@ -1388,8 +1542,42 @@
1388 1542 return update_option('kng_cc_settings', $settings);
1389 1543 }
1390 1544
1391 1545 /**
1546 + * AJAX: Save settings.
1547 + *
1548 + * @return void
1549 + */
1550 + public function ajax_save_settings(): void
1551 + {
1552 + check_ajax_referer('kng_cc_admin', 'nonce');
1553 +
1554 + if (!current_user_can('manage_options')) {
1555 + wp_send_json_error(['message' => __('Permission denied', 'king-addons')]);
1556 + }
1557 +
1558 + $data = isset($_POST['settings']) ? json_decode(wp_unslash($_POST['settings']), true) : [];
1559 +
1560 + if (empty($data) || !is_array($data)) {
1561 + wp_send_json_error(['message' => __('Invalid data', 'king-addons')]);
1562 + }
1563 +
1564 + $settings = [
1565 + 'enabled' => !empty($data['enabled']),
1566 + 'default_location_css' => sanitize_key($data['default_location_css'] ?? 'head'),
1567 + 'default_location_js' => sanitize_key($data['default_location_js'] ?? 'footer'),
1568 + 'default_priority' => (int)($data['default_priority'] ?? 10),
1569 + 'debug_mode' => !empty($data['debug_mode']),
1570 + ];
1571 +
1572 + if ($this->save_settings($settings)) {
1573 + wp_send_json_success(['message' => __('Settings saved', 'king-addons')]);
1574 + } else {
1575 + wp_send_json_error(['message' => __('Failed to save settings', 'king-addons')]);
1576 + }
1577 + }
1578 +
1579 + /**
1392 1580 * Checks if debug mode is enabled.
1393 1581 *
1394 1582 * @return bool
1395 1583 */
@@ -1409,18 +1597,15 @@
1409 1597 if (empty($this->debug_log)) {
1410 1598 return;
1411 1599 }
1412 1600
1413 - echo "\n<!-- King Addons Custom Code Debug Log -->\n";
1414 - echo "<script>\n";
1415 - echo "console.group('King Addons Custom Code Manager');\n";
1416 - foreach ($this->debug_log as $entry) {
1417 - $status = $entry['result'] ? '✓' : '✗';
1418 - echo "console.log('" . $status . " " . esc_js($entry['snippet']) . " (ID: " . $entry['id'] . ")');\n";
1419 - }
1420 - echo "console.groupEnd();\n";
1421 - echo "</script>\n";
1601 + // echo "\n<!-- King Addons Custom Code Debug Log -->\n";
1602 + // echo "<script>\n";
1603 + // echo "console.group('King Addons Custom Code Manager');\n";
1604 + // foreach ($this->debug_log as $entry) {
1605 + // $status = $entry['result'] ? '🟢' : '🔴';
1606 + // echo "console.log('" . $status . " " . esc_js($entry['snippet']) . " (ID: " . $entry['id'] . ")');\n";
1607 + // }
1608 + // echo "console.groupEnd();\n";
1609 + // echo "</script>\n";
1422 1610 }
1423 1611 }
1424 -
1425 -// Initialize
1426 -Custom_Code_Manager::getInstance();