← 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(); | |