Changeset 3452204
- Timestamp:
- 02/02/2026 03:06:52 PM (3 weeks ago)
- Location:
- ai-translate/trunk
- Files:
-
- 6 edited
-
ai-translate.php (modified) (8 diffs)
-
includes/admin-page.php (modified) (5 diffs)
-
includes/class-ai-batch.php (modified) (1 diff)
-
includes/class-ai-cache-meta.php (modified) (2 diffs)
-
includes/class-ai-seo.php (modified) (1 diff)
-
includes/class-ai-translate-core.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ai-translate/trunk/ai-translate.php
r3451583 r3452204 233 233 } 234 234 235 // Ensure slug map table exists early in init236 add_action('init', function () {237 \AITranslate\AI_Slugs::install_table();238 }, 1);239 240 // Schedule cache metadata sync cron job241 add_action('ai_translate_sync_cache_metadata', function () {242 \AITranslate\AI_Cache_Meta::sync_from_filesystem();243 });244 245 if (!wp_next_scheduled('ai_translate_sync_cache_metadata')) {246 wp_schedule_event(time(), 'hourly', 'ai_translate_sync_cache_metadata');247 }248 249 250 251 235 // Add original-style language-prefixed rewrite rules using 'lang' query var to ensure WP resolves pages via pagename 252 236 // Priority 999: runs AFTER custom post types are registered (default priority 10) 253 add_action('init', function () { 237 function ai_translate_register_rewrite_rules() 238 { 254 239 $settings = get_option('ai_translate_settings', array()); 255 240 $enabled = isset($settings['enabled_languages']) && is_array($settings['enabled_languages']) ? $settings['enabled_languages'] : array(); … … 261 246 } 262 247 $langs = array_filter(array_map('sanitize_key', $langs)); 263 if (empty($langs)) return; 248 if (empty($langs)) { 249 return; 250 } 264 251 $regex = '(' . implode('|', array_map(function ($l) { 265 252 return preg_quote($l, '/'); … … 267 254 add_rewrite_rule('^' . $regex . '/?$', 'index.php?lang=$matches[1]', 'top'); 268 255 269 // Add rewrite rules for all public custom post types (generiek)270 256 $public_post_types = get_post_types(array('public' => true, '_builtin' => false), 'objects'); 271 257 foreach ($public_post_types as $post_type) { … … 273 259 $slug = $post_type->rewrite['slug']; 274 260 $type_name = $post_type->name; 275 // Add explicit rule for this CPT: /{lang}/{cpt-slug}/{post-name}276 261 add_rewrite_rule( 277 262 '^' . $regex . '/(?!wp-admin|wp-login\.php)(' . preg_quote($slug, '/') . ')/([^/]+)/?$', … … 282 267 } 283 268 284 // Generic rule for hierarchical paths - try to resolve without language prefix first285 269 add_rewrite_rule('^' . $regex . '/(?!wp-admin|wp-login\.php)(.+)$', 'index.php?ai_translate_path=$matches[2]&lang=$matches[1]', 'top'); 286 // Pagination for posts index: /{lang}/page/{n}/ (added last so it sits on top due to 'top')287 270 add_rewrite_rule('^' . $regex . '/page/([0-9]+)/?$', 'index.php?lang=$matches[1]&paged=$matches[2]', 'top'); 288 }, 999); // Priority 999: runs AFTER custom post types are registered (default priority 10) 271 } 272 273 add_action('init', 'ai_translate_register_rewrite_rules', 999); // Priority 999: runs AFTER custom post types are registered (default priority 10) 274 275 // Ensure slug map table exists early in init 276 add_action('init', function () { 277 \AITranslate\AI_Slugs::install_table(); 278 }, 1); 279 280 // Schedule cache metadata sync cron job 281 add_action('ai_translate_sync_cache_metadata', function () { 282 \AITranslate\AI_Cache_Meta::sync_from_filesystem(); 283 }); 284 285 if (!wp_next_scheduled('ai_translate_sync_cache_metadata')) { 286 wp_schedule_event(time(), 'hourly', 'ai_translate_sync_cache_metadata'); 287 } 288 289 290 291 // Ensure slug map table exists early in init 292 add_action('init', function () { 293 \AITranslate\AI_Slugs::install_table(); 294 }, 1); 295 296 // Schedule cache metadata sync cron job 297 add_action('ai_translate_sync_cache_metadata', function () { 298 \AITranslate\AI_Cache_Meta::sync_from_filesystem(); 299 }); 300 301 if (!wp_next_scheduled('ai_translate_sync_cache_metadata')) { 302 wp_schedule_event(time(), 'hourly', 'ai_translate_sync_cache_metadata'); 303 } 289 304 290 305 add_filter('query_vars', function ($vars) { … … 421 436 register_activation_hook(__FILE__, function () { 422 437 // Ensure rules are registered before flushing 423 do_action('init');438 ai_translate_register_rewrite_rules(); 424 439 425 440 // Automatically set permalinks to 'post-name' if they're currently 'plain' … … 560 575 * Handles language detection and redirection according to the 4 language switcher rules. 561 576 */ 577 // Check for _wp_old_slug redirects before other plugin logic (priority 1). 578 // Request path is checked first: if it matches an old slug in DB, redirect. Do not rely on is_404 579 // because themes/plugins may serve the homepage instead of 404 for unknown slugs. 580 add_action('template_redirect', function () { 581 if (!isset($_SERVER['REQUEST_URI'])) { 582 return; 583 } 584 $request_uri = (string) $_SERVER['REQUEST_URI']; 585 $request_path = parse_url($request_uri, PHP_URL_PATH); 586 if (!is_string($request_path) || $request_path === '' || $request_path === '/') { 587 return; 588 } 589 // Skip if URL has language prefix (let AI Translate handle those) 590 if (preg_match('#^/([a-z]{2})(?:/|$)#i', $request_path)) { 591 return; 592 } 593 // Single-segment path (e.g. /programmeren-met-een-ai-agen/) – might be an old slug 594 $path_parts = array_filter(explode('/', trim($request_path, '/'))); 595 if (empty($path_parts)) { 596 return; 597 } 598 $slug = end($path_parts); 599 global $wpdb; 600 $post_id = $wpdb->get_var($wpdb->prepare( 601 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_wp_old_slug' AND meta_value = %s LIMIT 1", 602 $slug 603 )); 604 if (!$post_id) { 605 return; 606 } 607 $post = get_post($post_id); 608 if (!$post || $post->post_status !== 'publish') { 609 return; 610 } 611 $permalink = get_permalink($post); 612 if ($permalink) { 613 wp_safe_redirect($permalink, 301); 614 exit; 615 } 616 }, 1); 617 562 618 add_action('template_redirect', function () { 563 619 // Handle ai_translate_path query var for custom URL resolution … … 1391 1447 1392 1448 /** 1449 * Resolve _wp_old_slug in parse_request and redirect to canonical URL before 404 is set. 1450 * Priority 0 so this runs before any 404 redirect plugin (which would send 404s to homepage). 1451 */ 1452 add_action('parse_request', function ($wp) { 1453 if (is_admin() || wp_doing_ajax() || ai_translate_is_xml_request()) { 1454 return; 1455 } 1456 $reqUriRaw = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : ''; 1457 if (strpos($reqUriRaw, '%25') !== false) { 1458 $reqUriRaw = urldecode($reqUriRaw); 1459 } 1460 $request_path = wp_parse_url($reqUriRaw, PHP_URL_PATH) ?: '/'; 1461 if (!is_string($request_path) || $request_path === '' || $request_path === '/') { 1462 return; 1463 } 1464 if (preg_match('#^/([a-z]{2})(?:/|$)#i', $request_path)) { 1465 return; 1466 } 1467 $path_parts = array_filter(explode('/', trim($request_path, '/'))); 1468 if (empty($path_parts)) { 1469 return; 1470 } 1471 $slug = end($path_parts); 1472 global $wpdb; 1473 $post_id = $wpdb->get_var($wpdb->prepare( 1474 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_wp_old_slug' AND meta_value = %s LIMIT 1", 1475 $slug 1476 )); 1477 if (!$post_id) { 1478 return; 1479 } 1480 $post = get_post($post_id); 1481 if (!$post || $post->post_status !== 'publish') { 1482 return; 1483 } 1484 $permalink = get_permalink($post); 1485 if ($permalink) { 1486 wp_safe_redirect($permalink, 301); 1487 exit; 1488 } 1489 }, 0); 1490 1491 /** 1393 1492 * Ensure language-only URLs (e.g., /en/, /de/) route to the site's front page. 1394 1493 */ -
ai-translate/trunk/includes/admin-page.php
r3451583 r3452204 5 5 // Load the core class if it doesn't exist yet. 6 6 // Since this file is in the "includes" folder, the core class is at the same level. 7 if (! class_exists('AI_Translate_Core')) {7 if (! \class_exists('AI_Translate_Core')) { 8 8 $core_class_file = plugin_dir_path(__FILE__) . 'class-ai-translate-core.php'; 9 9 if (file_exists($core_class_file)) { … … 306 306 307 307 add_action('wp_ajax_ai_translate_delete_cache_file', __NAMESPACE__ . '\\ajax_delete_cache_file'); 308 309 /** 310 * Return route_id used for cache key when warming cache. Must match class-ai-ob current_route_id() for the same URL. 311 * 312 * @param int $post_id Post ID (0 = homepage) 313 * @return string route_id e.g. 'post:123' or 'path:' . md5('/') 314 */ 315 function warm_cache_route_id($post_id) 316 { 317 if ($post_id === 0) { 318 $front_page_id = (int) get_option('page_on_front'); 319 return ($front_page_id > 0) ? ('post:' . $front_page_id) : ('path:' . md5('/')); 320 } 321 return 'post:' . $post_id; 322 } 308 323 309 324 /** … … 417 432 usleep(500000); // 0.5 second 418 433 419 // Use correct route_id format (post:ID for posts, path:md5(/) for homepage) 420 if ($post_id === 0) { 421 $route_id = 'path:' . md5('/'); 422 } else { 423 $route_id = 'post:' . $post_id; 424 } 434 $route_id = warm_cache_route_id($post_id); 425 435 $cache_key = \AITranslate\AI_Cache::key($lang_code, $route_id, ''); 426 436 $cache_file = \AITranslate\AI_Cache::get_file_path($cache_key); … … 474 484 } elseif ($http_code === 301 || $http_code === 302) { 475 485 // Redirect - might be normal, but we'll mark as success if cache exists 476 $route_id = 'post:' . $post_id;486 $route_id = warm_cache_route_id($post_id); 477 487 $cache_key = \AITranslate\AI_Cache::key($lang_code, $route_id, ''); 478 488 $cache_file = \AITranslate\AI_Cache::get_file_path($cache_key); … … 737 747 // This ensures the database is in sync with the filesystem 738 748 if ($warmed > 0) { 739 // Use correct route_id format (post:ID for posts, path:md5(/) for homepage) 740 if ($post_id === 0) { 741 $route_id = 'path:' . md5('/'); 742 } else { 743 $route_id = 'post:' . $post_id; 744 } 749 $route_id = warm_cache_route_id($post_id); 745 750 $enabled = \AITranslate\AI_Lang::enabled(); 746 751 $detectable = \AITranslate\AI_Lang::detectable(); -
ai-translate/trunk/includes/class-ai-batch.php
r3451600 r3452204 289 289 $choices = is_array($data) ? ($data['choices'] ?? []) : []; 290 290 if (!$choices || !isset($choices[0]['message']['content'])) { 291 error_log('[AI-Batch] Response ' . $idx . ' FAILED: no choices/content. Raw: ' . substr($content, 0, 500));292 291 $failedIndexes[] = (int)$idx; 293 292 continue; 294 293 } 295 294 $msg = (string)$choices[0]['message']['content']; 296 error_log('[AI-Batch] Response ' . $idx . ' message: ' . substr($msg, 0, 300));297 295 if (trim($msg) === '') { 298 error_log('[AI-Batch] Response ' . $idx . ' FAILED: empty message');299 296 $failedIndexes[] = (int)$idx; 300 297 continue; -
ai-translate/trunk/includes/class-ai-cache-meta.php
r3449890 r3452204 148 148 global $wpdb; 149 149 150 if (get_option('ai_translate_cache_meta_indexes_applied', false)) { 151 return; 152 } 153 150 154 $table_name = self::get_table_name(); 151 155 … … 172 176 173 177 foreach ($indexes_to_check as $index_name => $index_definition) { 174 if (!in_array($index_name, $existing_indexes, true)) { 175 // Index doesn't exist, create it 176 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange 177 $wpdb->query($wpdb->prepare( 178 "ALTER TABLE %i ADD %1s", 179 $table_name, 180 $index_definition 181 )); 182 } 183 } 178 if (in_array($index_name, $existing_indexes, true)) { 179 continue; 180 } 181 182 // Guard against race conditions / duplicate key errors by double-checking the schema. 183 $schema_index_exists = $wpdb->get_var($wpdb->prepare( 184 "SELECT COUNT(1) FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = %s AND table_name = %s AND index_name = %s", 185 $wpdb->dbname, 186 str_replace($wpdb->prefix, '', $table_name), 187 $index_name 188 )); 189 if ((int) $schema_index_exists > 0) { 190 continue; 191 } 192 193 // Index doesn't exist, create it 194 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange 195 $result = $wpdb->query($wpdb->prepare( 196 "ALTER TABLE %i ADD %1s", 197 $table_name, 198 $index_definition 199 )); 200 201 if ($result === false && stripos($wpdb->last_error, 'Duplicate key name') !== false) { 202 $wpdb->flush(); 203 } 204 } 205 206 update_option('ai_translate_cache_meta_indexes_applied', true); 184 207 } 185 208 -
ai-translate/trunk/includes/class-ai-seo.php
r3450395 r3452204 840 840 } 841 841 842 $front_page_id = (int) get_option('page_on_front'); 843 if ($front_page_id > 0 && (int) $post_id === $front_page_id) { 844 if (strtolower($lang) === strtolower($default)) { 845 return home_url('/'); 846 } 847 return home_url('/' . $lang . '/'); 848 } 849 842 850 if ((int) $post_id === 0) { 843 851 if (strtolower($lang) === strtolower($default)) { -
ai-translate/trunk/includes/class-ai-translate-core.php
r3451583 r3452204 78 78 79 79 if ($provider_key === '') { 80 throw new \Exception('Provider ontbreekt');80 throw new \Exception('Provider missing'); 81 81 } 82 82 if ($api_key === '') { 83 throw new \Exception('API key ontbreekt');83 throw new \Exception('API key missing'); 84 84 } 85 85 … … 89 89 } 90 90 if ($base === '') { 91 throw new \Exception('API URL ontbreekt');91 throw new \Exception('API URL missing'); 92 92 } 93 93 … … 123 123 $data = json_decode($body, true); 124 124 if (!is_array($data)) { 125 throw new \Exception(' Ongeldig antwoord vanAPI');125 throw new \Exception('Unknown response from API'); 126 126 } 127 127
Note: See TracChangeset
for help on using the changeset viewer.