Changeset 3408847
- Timestamp:
- 12/03/2025 06:15:52 AM (4 months ago)
- Location:
- ai-for-seo
- Files:
-
- 81 added
- 15 edited
-
tags/2.2.3 (added)
-
tags/2.2.3/ai-for-seo.php (added)
-
tags/2.2.3/assets (added)
-
tags/2.2.3/assets/css (added)
-
tags/2.2.3/assets/css/ai-for-seo-styles.css (added)
-
tags/2.2.3/assets/images (added)
-
tags/2.2.3/assets/images/andre-erbis-at-space-codes.webp (added)
-
tags/2.2.3/assets/images/andre-erbis-signature.png (added)
-
tags/2.2.3/assets/images/faq-screenshots (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-be-builder-1.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-elementor-1.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-elementor-2.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-page-post-1.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-page-post-2.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-page-post-3.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-seo-autopilot-1.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-seo-autopilot-2.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-seo-autopilot-3.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-yoast-1.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-yoast-2.jpg (added)
-
tags/2.2.3/assets/images/faq-screenshots/screenshot-yoast-3.jpg (added)
-
tags/2.2.3/assets/images/help-screenshots (added)
-
tags/2.2.3/assets/images/help-screenshots/first-steps-1.jpg (added)
-
tags/2.2.3/assets/images/help-screenshots/first-steps-2.jpg (added)
-
tags/2.2.3/assets/images/help-screenshots/first-steps-3.jpg (added)
-
tags/2.2.3/assets/images/help-screenshots/first-steps-4.jpg (added)
-
tags/2.2.3/assets/images/help-screenshots/first-steps-5.jpg (added)
-
tags/2.2.3/assets/images/help-screenshots/first-steps-6.jpg (added)
-
tags/2.2.3/assets/images/help-screenshots/first-steps-7.jpg (added)
-
tags/2.2.3/assets/images/icons (added)
-
tags/2.2.3/assets/images/icons/document-question-48x48.png (added)
-
tags/2.2.3/assets/images/logos (added)
-
tags/2.2.3/assets/images/logos/ai-for-seo-full-logo.png (added)
-
tags/2.2.3/assets/images/logos/ai-for-seo-logo-256x256.png (added)
-
tags/2.2.3/assets/images/logos/ai-for-seo-logo-32x32.png (added)
-
tags/2.2.3/assets/images/logos/ai-for-seo-logo-64x64.png (added)
-
tags/2.2.3/assets/images/logos/ai-for-seo-logo-animated-512x512.gif (added)
-
tags/2.2.3/assets/images/logos/ai-for-seo.svg (added)
-
tags/2.2.3/assets/js (added)
-
tags/2.2.3/assets/js/ai-for-seo-alt-text-injection.js (added)
-
tags/2.2.3/assets/js/ai-for-seo-scripts.js (added)
-
tags/2.2.3/changelog.txt (added)
-
tags/2.2.3/includes (added)
-
tags/2.2.3/includes/ajax (added)
-
tags/2.2.3/includes/ajax/display (added)
-
tags/2.2.3/includes/ajax/display/attachment-attributes-editor.php (added)
-
tags/2.2.3/includes/ajax/display/import-settings-preview.php (added)
-
tags/2.2.3/includes/ajax/display/metadata-editor.php (added)
-
tags/2.2.3/includes/ajax/process (added)
-
tags/2.2.3/includes/ajax/process/export-settings.php (added)
-
tags/2.2.3/includes/ajax/process/generate-attachment-attributes.php (added)
-
tags/2.2.3/includes/ajax/process/generate-metadata.php (added)
-
tags/2.2.3/includes/ajax/process/import-settings.php (added)
-
tags/2.2.3/includes/ajax/process/save-anything-categories (added)
-
tags/2.2.3/includes/ajax/process/save-anything-categories/save-attachment-attributes-editor-values.php (added)
-
tags/2.2.3/includes/ajax/process/save-anything-categories/save-environmental-variables.php (added)
-
tags/2.2.3/includes/ajax/process/save-anything-categories/save-metadata-editor-values.php (added)
-
tags/2.2.3/includes/ajax/process/save-anything-categories/save-robhub-environmental-variables.php (added)
-
tags/2.2.3/includes/ajax/process/save-anything-categories/save-settings.php (added)
-
tags/2.2.3/includes/api (added)
-
tags/2.2.3/includes/api/class-robhub-api-communicator.php (added)
-
tags/2.2.3/includes/menu-frame.php (added)
-
tags/2.2.3/includes/modal_schemas (added)
-
tags/2.2.3/includes/modal_schemas/autoload-modal-schemas.php (added)
-
tags/2.2.3/includes/modal_schemas/customize-pay-as-you-go.php (added)
-
tags/2.2.3/includes/modal_schemas/export-import-settings.php (added)
-
tags/2.2.3/includes/modal_schemas/get-more-credits.php (added)
-
tags/2.2.3/includes/modal_schemas/select-credits-pack.php (added)
-
tags/2.2.3/includes/modal_schemas/seo-autopilot.php (added)
-
tags/2.2.3/includes/modal_schemas/tos.php (added)
-
tags/2.2.3/includes/pages (added)
-
tags/2.2.3/includes/pages/account.php (added)
-
tags/2.2.3/includes/pages/content_types (added)
-
tags/2.2.3/includes/pages/content_types/attachment.php (added)
-
tags/2.2.3/includes/pages/content_types/list-filters.php (added)
-
tags/2.2.3/includes/pages/content_types/post.php (added)
-
tags/2.2.3/includes/pages/dashboard.php (added)
-
tags/2.2.3/includes/pages/help.php (added)
-
tags/2.2.3/includes/pages/settings.php (added)
-
tags/2.2.3/readme.txt (added)
-
tags/2.2.3/wpml-config.xml (added)
-
trunk/ai-for-seo.php (modified) (240 diffs)
-
trunk/assets/images/help-screenshots/first-steps-5.jpg (modified) (previous)
-
trunk/assets/js/ai-for-seo-scripts.js (modified) (2 diffs)
-
trunk/changelog.txt (modified) (1 diff)
-
trunk/includes/ajax/process/generate-attachment-attributes.php (modified) (2 diffs)
-
trunk/includes/api/class-robhub-api-communicator.php (modified) (1 diff)
-
trunk/includes/menu-frame.php (modified) (1 diff)
-
trunk/includes/modal_schemas/customize-pay-as-you-go.php (modified) (1 diff)
-
trunk/includes/modal_schemas/get-more-credits.php (modified) (5 diffs)
-
trunk/includes/pages/content_types/attachment.php (modified) (2 diffs)
-
trunk/includes/pages/content_types/post.php (modified) (5 diffs)
-
trunk/includes/pages/dashboard.php (modified) (7 diffs)
-
trunk/includes/pages/help.php (modified) (8 diffs)
-
trunk/includes/pages/settings.php (modified) (6 diffs)
-
trunk/readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ai-for-seo/trunk/ai-for-seo.php
r3399672 r3408847 4 4 Plugin URI: https://aiforseo.ai 5 5 Description: One-Click SEO solution. "AI for SEO" helps your website to rank higher in Web Search results. 6 Version: 2.2. 26 Version: 2.2.3 7 7 Author: spacecodes 8 8 Author URI: https://spa.ce.codes … … 16 16 } 17 17 18 if(isset($_GET['deactivate-ai-for-seo'])) { 19 deactivate_plugins( plugin_basename( __FILE__ ) ); 20 exit; 21 } 22 23 18 24 // ___________________________________________________________________________________________ \\ 19 25 // === CONSTANTS AND VARIABLES =============================================================== \\ 20 26 // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\ 21 27 22 const AI4SEO_PLUGIN_VERSION_NUMBER = "2.2. 2";28 const AI4SEO_PLUGIN_VERSION_NUMBER = "2.2.3"; 23 29 const AI4SEO_PLUGIN_NAME = "AI for SEO"; 24 30 const AI4SEO_PLUGIN_DESCRIPTION = 'One-Click SEO solution. "AI for SEO" helps your website to rank higher in Web Search results.'; … … 68 74 const AI4SEO_SEMAPHORE_POLL_INTERVAL_SECONDS = .1; // .1 seconds 69 75 const AI4SEO_SEMAPHORE_TTL_SECONDS = 30; // 30 seconds 70 const AI4SEO_POST_TABLE_ANALYSIS_BATCH_SIZE = 1000; // number of posts to analyze per batch 71 const AI4SEO_POST_TABLE_ANALYSIS_MAX_EXECUTION_TIME = 5; // maximum execution time in seconds per batch 76 const AI4SEO_POST_TABLE_ANALYSIS_BATCH_SIZE = 5000; // number of posts to analyze per batch 77 const AI4SEO_POST_TABLE_ANALYSIS_MAX_EXECUTION_TIME = 4; // maximum execution time in seconds per batch 78 const AI4SEO_POST_TABLE_ANALYSIS_SLEEP_BETWEEN_RUNS = 100000; // microseconds to sleep between runs 79 const AI4SEO_POST_TABLE_ANALYSIS_PROCESSING_TIMEOUT = 30; // seconds 72 80 73 81 const AI4SEO_CRON_JOBS_ENABLED = true; # set to true to enable cron jobs, false to disable them … … 101 109 function ai4seo_get_change_log(): array { 102 110 return [ 111 [ 112 'date' => 'December 3rd, 2025', 113 'version' => '2.2.3', 114 'important' => true, 115 'updates' => [ 116 'Bug Fixes & Maintenance: Fixed 7 minor bugs and implemented 2 usability improvements.' 117 ], 118 ], 103 119 [ 104 120 'date' => 'November 20th, 2025', … … 888 904 889 905 $ai4seo_settings = AI4SEO_DEFAULT_SETTINGS; 906 $ai4seo_are_settings_initialized = false; 890 907 891 908 $ai4seo_fallback_allowed_user_roles = array("administrator" => "Administrator"); … … 977 994 $ai4seo_scripts_version_number = isset($_GET["ai4seo_debug_uncached_assets"]) && $_GET["ai4seo_debug_uncached_assets"] ? time() : AI4SEO_PLUGIN_VERSION_NUMBER; 978 995 $ai4seo_user_has_at_least_plan = array(); # cache variable to store if user has at least a specific plan 996 $ai4seo_did_run_post_table_analysis = false; 979 997 980 998 // used to store various details about all supported metadata fields to use it on many places throughout the plugin … … 1273 1291 "select" => array( 1274 1292 "id" => array(), 1293 "name" => array(), 1275 1294 "class" => array(), 1276 1295 "style" => array(), … … 1300 1319 "textarea" => array( 1301 1320 "id" => array(), 1321 "name" => array(), 1302 1322 "class" => array(), 1303 1323 "style" => array(), … … 1325 1345 ), 1326 1346 "em" => array(), 1347 "form" => array( 1348 "id" => array(), 1349 "class" => array(), 1350 "style" => array(), 1351 "method" => array(), 1352 "action" => array(), 1353 ), 1327 1354 ); 1328 1355 … … 1421 1448 ); 1422 1449 1423 // the robhub api communicator is used to communicate with the robhub api which handles all the ai stuff1450 // the robhub api communicator is used to communicate with the robhub api which handles all the AI operations 1424 1451 $ai4seo_robhub_api = null; 1425 $ai4seo_are_settings_currently_being_initialized = false;1426 $ai4seo_are_settings_initialized = false;1427 1452 1428 1453 … … 1437 1462 if (wp_doing_cron()) { 1438 1463 // init cron jobs 1439 add_action("init", "ai4seo_init_robhub_api", 9);1440 1464 add_action("init", "ai4seo_init_cron_jobs", 10); 1441 1465 … … 1574 1598 // Make sure that the user is allowed to use this plugin 1575 1599 if (!ai4seo_can_manage_this_plugin()) { 1600 return; 1601 } 1602 1603 if (!ai4seo_singleton(__FUNCTION__)) { 1576 1604 return; 1577 1605 } … … 1627 1655 // ADMIN AREA ONLY (REST OF INIT CODE FROM HERE) -> 1628 1656 // make sure the robhub api communicator is initialized before anything else 1629 if (!ai4seo_init_robhub_api()) { 1630 // if the robhub api communicator could not be initialized, we cannot continue 1631 // show notice 1632 add_action('admin_notices', function() { 1633 echo '<div class="notice notice-error"><p>' . esc_html__("The AI for SEO plugin could not be initialized. Class 'Ai4Seo_RobHubApiCommunicator' could not be initialized. Please check your server configuration and try again.", "ai-for-seo") . '</p></div>'; 1634 }); 1635 1636 // exit here 1657 try { 1658 if (!ai4seo_robhub_api(true) || !ai4seo_robhub_api()->is_initialized) { 1659 // if the robhub api communicator could not be initialized, we cannot continue 1660 // show notice 1661 if (ai4seo_can_manage_this_plugin()) { 1662 add_action('admin_notices', function () { 1663 echo '<div class="notice notice-error"><p>' . esc_html__("The AI for SEO plugin could not be initialized. Class 'Ai4Seo_RobHubApiCommunicator' could not be initialized. Please check your server configuration and try again.", "ai-for-seo") . '</p></div>'; 1664 }); 1665 } 1666 1667 // exit here 1668 return; 1669 } 1670 } catch (Throwable $e) { 1671 // could not initialize the robhub api communicator -> abort here, echoing a notice 1672 if (ai4seo_can_manage_this_plugin()) { 1673 add_action('admin_notices', function () { 1674 echo '<div class="notice notice-error"><p>' . esc_html__("The AI for SEO plugin could not be initialized. Class 'Ai4Seo_RobHubApiCommunicator' could not be initialized. Please check your server configuration and try again.", "ai-for-seo") . '</p></div>'; 1675 }); 1676 } 1677 1637 1678 return; 1638 1679 } … … 1643 1684 // Make sure that the user is allowed to use this plugin 1644 1685 if (!ai4seo_can_manage_this_plugin()) { 1686 return; 1687 } 1688 1689 if (!ai4seo_singleton(__FUNCTION__)) { 1645 1690 return; 1646 1691 } … … 1702 1747 global $ai4seo_settings; 1703 1748 global $ai4seo_are_settings_initialized; 1704 global $ai4seo_are_settings_currently_being_initialized; 1705 1706 // Make sure that settings are only initialized once 1707 if ($ai4seo_are_settings_initialized) { 1749 1750 if (ai4seo_prevent_loops(__FUNCTION__, 1, 10)) { 1751 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 1708 1752 return; 1709 1753 } 1710 1754 1711 if ($ai4seo_are_settings_currently_being_initialized) { 1712 error_log("AI4SEO Init Settings: Settings are already being initialized. Exiting to prevent recursion."); 1713 //error_log(__FUNCTION__ . "() @ " . __LINE__ . ": " . print_r("AI4SEO Init Settings: Settings are already being initialized. Exiting to prevent recursion.", true) . "\r\n\r\nBACKTRACE\r\n " . ai4seo_get_backtrace_debug_message("\r\n") . "\r\n\r\n"); 1714 return; 1715 } 1716 1717 $ai4seo_are_settings_currently_being_initialized = true; 1718 1719 try { 1720 // Read settings from database 1721 $from_database_settings = ai4seo_read_settings(); 1722 1723 // Loop through settings and add the new values to $ai4seo_settings 1724 foreach ($from_database_settings as $setting_name => $setting_value) { 1725 // Make sure that this setting is valid 1726 if (!ai4seo_validate_setting_value($setting_name, $setting_value)) { 1727 continue; 1728 } 1729 1730 // Save the new values to $ai4seo_settings 1731 $ai4seo_settings[$setting_name] = $setting_value; 1732 } 1733 1734 $ai4seo_are_settings_currently_being_initialized = false; 1735 $ai4seo_are_settings_initialized = true; 1736 } finally { 1737 $ai4seo_are_settings_currently_being_initialized = false; 1738 } 1755 // Read settings from database 1756 $from_database_settings = ai4seo_read_settings(); 1757 1758 // Loop through settings and add the new values to $ai4seo_settings 1759 foreach ($from_database_settings as $setting_name => $setting_value) { 1760 // Make sure that this setting is valid 1761 if (!ai4seo_validate_setting_value($setting_name, $setting_value)) { 1762 continue; 1763 } 1764 1765 // Save the new values to $ai4seo_settings 1766 $ai4seo_settings[$setting_name] = $setting_value; 1767 } 1768 1769 $ai4seo_are_settings_initialized = true; 1739 1770 } 1740 1771 … … 1742 1773 1743 1774 function ai4seo_read_settings() : array { 1775 // prevent infinite loops (1 depth, max 10 calls) 1776 if (ai4seo_prevent_loops(__FUNCTION__, 1, 10)) { 1777 error_log('AI4SEO: Prevented infinite loop in ai4seo_read_settings()'); 1778 return array(); 1779 } 1780 1744 1781 // Read settings from database 1745 1782 $settings = ai4seo_get_option(AI4SEO_SETTINGS_OPTION_NAME); … … 1776 1813 */ 1777 1814 function ai4seo_on_activation() { 1815 if (!ai4seo_singleton(__FUNCTION__)) { 1816 return; 1817 } 1818 1778 1819 // set AI4SEO_ENVIRONMENTAL_VARIABLE_PLUGIN_ACTIVATION_TIME 1779 1820 if (!ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_PLUGIN_ACTIVATION_TIME)) { … … 1792 1833 */ 1793 1834 function ai4seo_on_deactivation() { 1835 if (!ai4seo_singleton(__FUNCTION__)) { 1836 return; 1837 } 1838 1794 1839 // un schedule all cron jobs 1795 1840 ai4seo_un_schedule_cron_jobs(); … … 1824 1869 1825 1870 function ai4seo_is_incognito_mode_enabled(): bool { 1871 // prevent infinite loops (0 depth, max 10 calls) 1872 if (ai4seo_prevent_loops(__FUNCTION__)) { 1873 error_log('AI4SEO: Prevented infinite loop in ai4seo_is_incognito_mode_enabled()'); 1874 return false; 1875 } 1876 1826 1877 if (isset($_REQUEST["ai4seo_debug_bypass_incognito_mode"]) && $_REQUEST["ai4seo_debug_bypass_incognito_mode"]) { 1827 1878 // If the debug bypass parameter is set, we can bypass the incognito mode … … 1848 1899 */ 1849 1900 function ai4seo_check_and_handle_plugin_update() { 1901 if (!ai4seo_singleton(__FUNCTION__)) { 1902 return; 1903 } 1904 1850 1905 $last_known_plugin_version = strval(ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_KNOWN_PLUGIN_VERSION)); 1851 1906 … … 1889 1944 */ 1890 1945 function ai4seo_tidy_up(string $last_known_plugin_version = AI4SEO_PLUGIN_VERSION_NUMBER) { 1946 if (!ai4seo_singleton(__FUNCTION__)) { 1947 return; 1948 } 1949 1891 1950 // reestablish cron jobs 1892 1951 ai4seo_un_schedule_cron_jobs(); … … 2225 2284 */ 2226 2285 function ai4seo_init_cron_jobs() { 2227 if (!ai4seo_init_robhub_api()) { 2286 if (!ai4seo_singleton(__FUNCTION__)) { 2287 return; 2288 } 2289 2290 try { 2291 if (!ai4seo_robhub_api(true) || !ai4seo_robhub_api()->is_initialized) { 2292 return; 2293 } 2294 } catch (Throwable $e) { 2228 2295 return; 2229 2296 } … … 2244 2311 // schedule cron jobs if not already scheduled 2245 2312 ai4seo_schedule_cron_jobs(); 2246 }2247 2248 // =========================================================================================== \\2249 2250 /**2251 * Function to init robhub-api-communicator2252 * @return bool true if the class is found, false if not2253 */2254 function ai4seo_init_robhub_api(): bool {2255 global $ai4seo_robhub_api;2256 2257 // Include RobHubApiCommunicator class2258 include_once(ai4seo_get_includes_api_path("class-robhub-api-communicator.php"));2259 2260 // return false if the class is not found2261 if (!class_exists("Ai4Seo_RobHubApiCommunicator")) {2262 return false;2263 }2264 2265 // check if $ai4seo_robhub_api_communicator is already set2266 if ($ai4seo_robhub_api instanceof Ai4Seo_RobHubApiCommunicator) {2267 return true;2268 }2269 2270 $product_activation_time = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_PLUGIN_ACTIVATION_TIME);2271 2272 $ai4seo_robhub_api = new Ai4Seo_RobHubApiCommunicator();2273 $ai4seo_robhub_api->set_environmental_variables_option_name(AI4SEO_ROBHUB_ENVIRONMENTAL_VARIABLES_OPTION_NAME);2274 $ai4seo_robhub_api->set_product_parameters("ai4seo", AI4SEO_PLUGIN_VERSION_NUMBER, $product_activation_time);2275 2276 // do we need to accept the terms of service?2277 $does_user_need_to_accept_tos_toc_and_pp = ai4seo_does_user_need_to_accept_tos_toc_and_pp();2278 $ai4seo_robhub_api->set_does_user_need_to_accept_tos_toc_and_pp($does_user_need_to_accept_tos_toc_and_pp);2279 2280 return true;2281 2313 } 2282 2314 … … 2295 2327 */ 2296 2328 function ai4seo_filter_admin_title(string $admin_title, string $title ): string { 2329 if (!ai4seo_singleton(__FUNCTION__)) { 2330 return false; 2331 } 2332 2297 2333 if ( ! ai4seo_is_user_inside_our_plugin_admin_pages() ) { 2298 2334 return $admin_title; … … 2343 2379 */ 2344 2380 function ai4seo_get_plugins_menu_registry(): array { 2381 if (ai4seo_prevent_loops(__FUNCTION__)) { 2382 error_log('AI4SEO: Prevented infinite loop in ai4seo_get_plugins_menu_registry()'); 2383 return array(); 2384 } 2385 2345 2386 // Static pages. 2346 2387 $dashboard_slug = str_replace( '?page=', '', ai4seo_get_subpage_url( 'dashboard', array(), false ) ); // typically 'ai-for-seo' … … 2398 2439 */ 2399 2440 function ai4seo_add_menu_entries() { 2441 if (!ai4seo_singleton(__FUNCTION__)) { 2442 return; 2443 } 2444 2400 2445 $svg_tags = ai4seo_get_svg_tags(); 2401 2446 … … 2408 2453 // Top-level title with notification bubble. 2409 2454 $menu_title = AI4SEO_PLUGIN_NAME; 2410 $notification_count = (int)ai4seo_get_num_unread_notification();2455 $notification_count = ai4seo_get_num_unread_notification(); 2411 2456 2412 2457 if ( $notification_count > 0 ) { … … 2500 2545 */ 2501 2546 function ai4seo_mark_parent_menu_active(?string $parent_file ): ?string { 2547 if (!ai4seo_singleton(__FUNCTION__)) { 2548 return ""; 2549 } 2550 2502 2551 if ( ai4seo_is_user_inside_our_plugin_admin_pages() ) { 2503 2552 $parent_file = AI4SEO_PLUGIN_IDENTIFIER; … … 2516 2565 */ 2517 2566 function ai4seo_mark_submenu_active(?string $submenu_file): ?string { 2567 if (!ai4seo_singleton(__FUNCTION__)) { 2568 return ''; 2569 } 2570 2518 2571 if ( ai4seo_is_user_inside_our_plugin_admin_pages() ) { 2519 2572 // Central registry for labels and slugs. … … 2539 2592 */ 2540 2593 function ai4seo_include_menu_frame_file() { 2594 if (!ai4seo_singleton(__FUNCTION__)) { 2595 return; 2596 } 2597 2541 2598 include_once(ai4seo_get_plugin_dir_path("includes/menu-frame.php")); 2542 2599 } … … 2549 2606 */ 2550 2607 function ai4seo_include_modal_schemas_file() { 2608 if (!ai4seo_singleton(__FUNCTION__)) { 2609 return; 2610 } 2611 2551 2612 include_once(ai4seo_get_includes_modal_schemas_path("autoload-modal-schemas.php")); 2552 2613 } … … 2561 2622 return; 2562 2623 } 2563 2564 2624 2565 2625 // check if we are outside the admin area … … 2629 2689 global $ai4seo_scripts_version_number; 2630 2690 2691 if (!ai4seo_singleton(__FUNCTION__)) { 2692 return; 2693 } 2694 2631 2695 // === INITIALISATIONS ====================================================== \\ 2632 2696 2633 $current_post_id = ai4seo_get_ post_id();2697 $current_post_id = ai4seo_get_current_post_id(); 2634 2698 $ajax_nonce = wp_create_nonce( AI4SEO_GLOBAL_NONCE_IDENTIFIER ); 2635 2699 $site_url = site_url(); … … 2694 2758 function ai4seo_add_metadata_editor_column_to_posts_table(array $columns): array { 2695 2759 // Make sure that this function is only called once 2696 if (!ai4seo_singleton(__FUNCTION__)) { 2697 return $columns; 2760 if (ai4seo_prevent_loops(__FUNCTION__)) { 2761 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 2762 return array(); 2698 2763 } 2699 2764 … … 2711 2776 */ 2712 2777 function ai4seo_add_metadata_editor_button_to_posts_table(string $column_name, int $post_id) { 2778 if (ai4seo_prevent_loops(__FUNCTION__)) { 2779 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 2780 return; 2781 } 2782 2713 2783 if ($column_name == AI4SEO_PLUGIN_IDENTIFIER) { 2714 2784 echo ai4seo_wp_kses(ai4seo_get_edit_metadata_button($post_id)); … … 2860 2930 */ 2861 2931 function ai4seo_inject_our_meta_tags_into_the_html_head(string $full_html_buffer): string { 2932 if (!ai4seo_singleton(__FUNCTION__)) { 2933 return $full_html_buffer; 2934 } 2935 2862 2936 // check if we are on a singular 2863 2937 if (!is_singular()) { … … 2866 2940 2867 2941 // Define variable for the page- or post-id 2868 $post_id = ai4seo_get_ post_id();2942 $post_id = ai4seo_get_current_post_id(); 2869 2943 2870 2944 // Stop function if no page- or post-id is defined … … 3202 3276 3203 3277 function ai4seo_inject_image_attributes_for_gutenberg( $content, $block ) { 3278 if (!ai4seo_singleton(__FUNCTION__)) { 3279 return $content; 3280 } 3281 3204 3282 // Skip admin, feeds, REST API and AJAX requests 3205 3283 if ( … … 3222 3300 */ 3223 3301 function ai4seo_inject_image_attributes_into_html($content) { 3302 if (!ai4seo_singleton(__FUNCTION__)) { 3303 return $content; 3304 } 3305 3224 3306 $alt_enabled = ai4seo_get_setting( AI4SEO_SETTING_ENABLE_RENDER_LEVEL_ALT_TEXT_INJECTION ); 3225 3307 $title_injection_mode = ai4seo_get_setting( AI4SEO_SETTING_IMAGE_TITLE_INJECTION_MODE ); … … 3416 3498 3417 3499 function ai4seo_filter_wp_image_attrs( $attr, $attachment, $size ) { 3500 if (!ai4seo_singleton(__FUNCTION__)) { 3501 return $attr; 3502 } 3503 3418 3504 if ( empty( $attr['alt'] ) ) { 3419 3505 $alt = get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ); … … 3438 3524 */ 3439 3525 function ai4seo_apply_possible_fallbacks(int $post_id, string $metadata_identifier, array &$our_metadata, array $visited_metadata_identifiers = array()): void { 3526 if (ai4seo_prevent_loops(__FUNCTION__)) { 3527 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 3528 return; 3529 } 3530 3440 3531 if (isset($our_metadata[$metadata_identifier]) && $our_metadata[$metadata_identifier]) { 3441 3532 return; … … 3774 3865 * @return string $text with placeholders replaced 3775 3866 */ 3776 function ai4seo_replace_white_label_placeholders( $text){3867 function ai4seo_replace_white_label_placeholders(string $text): string { 3777 3868 $text = str_replace( 3778 3869 ["{NAME}", "{VERSION}", "{WEBSITE}"], … … 3852 3943 */ 3853 3944 function ai4seo_get_attachment_placeholder_replacements(int $attachment_post_id): array { 3945 if (ai4seo_prevent_loops(__FUNCTION__)) { 3946 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 3947 return array(); 3948 } 3949 3854 3950 $replacements = ai4seo_get_common_placeholder_replacements(); 3855 3951 … … 4076 4172 */ 4077 4173 function ai4seo_modify_plugin_details_for_white_label(array $all_plugins): array { 4174 if (ai4seo_prevent_loops(__FUNCTION__)) { 4175 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 4176 return array(); 4177 } 4178 4078 4179 // Define variable for the plugin-file 4079 4180 $plugin_file = ai4seo_get_plugin_basename(); … … 4173 4274 */ 4174 4275 function ai4seo_get_meta_tags_from_html(string $head_html): array { 4276 if (ai4seo_prevent_loops(__FUNCTION__)) { 4277 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 4278 return array(); 4279 } 4280 4175 4281 if (!defined('AI4SEO_METADATA_DETAILS')) { 4176 4282 return array(); … … 4282 4388 // Make sure that the user is allowed to use this plugin 4283 4389 if (!ai4seo_can_manage_this_plugin()) { 4390 return; 4391 } 4392 4393 if (!ai4seo_singleton(__FUNCTION__)) { 4284 4394 return; 4285 4395 } … … 4323 4433 } 4324 4434 4435 if (!ai4seo_singleton(__FUNCTION__)) { 4436 return; 4437 } 4438 4325 4439 // check action 4326 4440 $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( $_REQUEST['action'] ) : ''; … … 4373 4487 global $wpdb; 4374 4488 4489 if (!ai4seo_singleton(__FUNCTION__)) { 4490 return; 4491 } 4492 4375 4493 // compare cached and real count of posts 4376 4494 $cached_num_posts_table_entries = (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_NUM_POSTS_TABLE_ENTRIES); … … 4405 4523 */ 4406 4524 function ai4seo_check_for_robhub_account_sync(): void { 4525 if (!ai4seo_singleton(__FUNCTION__)) { 4526 return; 4527 } 4528 4407 4529 $active_subpage = ai4seo_get_active_subpage(); 4408 4530 $active_subpage_is_dashboard = $active_subpage == "dashboard"; … … 4461 4583 // use singleton to only call this function once 4462 4584 if (!ai4seo_singleton(__FUNCTION__)) { 4463 return true;4585 return false; 4464 4586 } 4465 4587 … … 4580 4702 function ai4seo_get_all_possible_user_roles(): array { 4581 4703 global $ai4seo_fallback_allowed_user_roles; 4704 4705 if (ai4seo_prevent_loops(__FUNCTION__)) { 4706 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 4707 return $ai4seo_fallback_allowed_user_roles; 4708 } 4582 4709 4583 4710 if (!function_exists('wp_roles')) { … … 4715 4842 global $ai4seo_user_has_at_least_plan; 4716 4843 4844 if (ai4seo_prevent_loops(__FUNCTION__)) { 4845 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 4846 return false; 4847 } 4848 4717 4849 if (isset($ai4seo_user_has_at_least_plan[$required_plan])) { 4718 4850 return $ai4seo_user_has_at_least_plan[$required_plan]; … … 4738 4870 4739 4871 function ai4seo_get_plan_badge($plan): string { 4872 if (ai4seo_prevent_loops(__FUNCTION__)) { 4873 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 4874 return ''; 4875 } 4876 4740 4877 $css_class = 'ai4seo-plan-badge'; 4741 4878 $user_has_at_least_this_plan = ai4seo_user_has_at_least_plan($plan); … … 4801 4938 /** 4802 4939 * Function to return the robhub api communicator 4803 * @return Ai4Seo_RobHubApiCommunicator The robhub api communicator4804 */ 4805 function ai4seo_robhub_api( ):Ai4Seo_RobHubApiCommunicator {4940 * @return Ai4Seo_RobHubApiCommunicator|null The robhub api communicator 4941 */ 4942 function ai4seo_robhub_api($init_only = false): ?Ai4Seo_RobHubApiCommunicator { 4806 4943 global $ai4seo_robhub_api; 4807 4944 4945 if (ai4seo_prevent_loops(__FUNCTION__)) { 4946 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 4947 return null; 4948 } 4949 4950 // init the robhub api communicator if not already done 4808 4951 if (!$ai4seo_robhub_api instanceof Ai4Seo_RobHubApiCommunicator) { 4809 ai4seo_init_robhub_api(); 4952 $ai4seo_robhub_api_path = ai4seo_get_includes_api_path('class-robhub-api-communicator.php'); 4953 4954 if (!file_exists($ai4seo_robhub_api_path)) { 4955 if ($init_only) { 4956 return null; 4957 } 4958 4959 error_log('AI4SEO: RobHub API communicator file missing at ' . $ai4seo_robhub_api_path); 4960 throw new RuntimeException('RobHub API communicator file missing.'); 4961 } 4962 4963 require_once $ai4seo_robhub_api_path; 4964 4965 if (!class_exists('Ai4Seo_RobHubApiCommunicator')) { 4966 if ($init_only) { 4967 return null; 4968 } 4969 4970 error_log('AI4SEO: Failed to load Ai4Seo_RobHubApiCommunicator from ' . $ai4seo_robhub_api_path); 4971 throw new RuntimeException('Ai4Seo_RobHubApiCommunicator class not found after include.'); 4972 } 4973 4974 $ai4seo_robhub_api = new Ai4Seo_RobHubApiCommunicator(); 4975 $ai4seo_robhub_api->set_environmental_variables_option_name(AI4SEO_ROBHUB_ENVIRONMENTAL_VARIABLES_OPTION_NAME); 4976 $product_activation_time = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_PLUGIN_ACTIVATION_TIME); 4977 $ai4seo_robhub_api->set_product_parameters('ai4seo', AI4SEO_PLUGIN_VERSION_NUMBER, $product_activation_time); 4978 $does_user_need_to_accept_tos_toc_and_pp = ai4seo_does_user_need_to_accept_tos_toc_and_pp(); 4979 $ai4seo_robhub_api->set_does_user_need_to_accept_tos_toc_and_pp($does_user_need_to_accept_tos_toc_and_pp); 4980 $ai4seo_robhub_api->is_initialized = true; 4810 4981 } 4811 4982 … … 4825 4996 */ 4826 4997 function ai4seo_deep_sanitize($data, string $sanitize_value_function_name = 'sanitize_text_field', string $sanitize_key_function_name = 'sanitize_key') { 4998 if (ai4seo_prevent_loops(__FUNCTION__, 100, 99999)) { 4999 error_log('AI4SEO: Prevented deep recursion in ' . __FUNCTION__); 5000 return $data; 5001 } 5002 4827 5003 if (is_array($data)) { 4828 5004 $sanitized_data = array(); … … 4862 5038 global $ai4seo_can_manage_this_plugin; 4863 5039 5040 if (ai4seo_prevent_loops(__FUNCTION__)) { 5041 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 5042 return false; 5043 } 5044 4864 5045 // use cache if available 4865 5046 if ($ai4seo_can_manage_this_plugin !== null) { … … 4961 5142 * Function to prevent recursive loops 4962 5143 * @param string $function_name The name of the function to check 4963 * @param int $max_depth The maximum depth of recursion allowed (default 0)4964 * @param int $max_calls The maximum number of calls allowed globally (default 999 )5144 * @param int $max_depth The maximum depth of recursion allowed (default 1, min 1) 5145 * @param int $max_calls The maximum number of calls allowed globally (default 99999, min 1) 4965 5146 * @return bool True if the loop should be prevented, false otherwise 4966 5147 */ 4967 function ai4seo_prevent_loops( $function_name, $max_depth = 0, $max_calls = 999){5148 function ai4seo_prevent_loops(string $function_name, int $max_depth = 1, int $max_calls = 99999): bool { 4968 5149 static $call_counts = []; 5150 5151 if ($max_depth < 1) { 5152 $max_depth = 1; 5153 } 5154 5155 if ($max_calls < 1) { 5156 $max_calls = 1; 5157 } 4969 5158 4970 5159 // Initialize call count if not exists … … 4979 5168 if ($call_counts[$function_name] > $max_calls) { 4980 5169 return true; 5170 } 5171 5172 // if $call_counts[$function_name] is less than $max_depth, we cannot have reached max depth yet 5173 if ($call_counts[$function_name] <= $max_depth) { 5174 return false; 4981 5175 } 4982 5176 … … 4993 5187 4994 5188 // The current call is included in the backtrace, so depth is at least 1. 4995 // If max_depth is 0, we want to prevent ANY recursion (i.e., if depth > 1).4996 // If max_depth is 1, we allow 1recursive call (depth 2).4997 // So we return true if depth > $max_depth + 1.5189 // If max_depth is 1, we want to prevent ANY recursion (i.e., if depth > 1). 5190 // If max_depth is 2, we allow 2 recursive call (depth 2). 5191 // So we return true if depth > $max_depth 4998 5192 4999 if ($depth > $max_depth + 1) {5193 if ($depth > $max_depth) { 5000 5194 return true; 5001 5195 } 5002 5196 5003 5197 return false; 5198 } 5199 5200 // =========================================================================================== \\ 5201 5202 /** 5203 * Function to simulate a singleton (only one call per function per id) 5204 * @param $id 5205 * @return bool 5206 */ 5207 function ai4seo_singleton($id): bool { 5208 return !ai4seo_prevent_loops($id, 1, 1); 5004 5209 } 5005 5210 … … 5028 5233 5029 5234 return $text; 5030 }5031 5032 // =========================================================================================== \\5033 5034 /**5035 * Function to simulate a singleton (only one call per function per id)5036 * @param $id5037 * @return bool5038 */5039 function ai4seo_singleton($id): bool {5040 $fullId = "Singleton-$id";5041 if (isset($GLOBALS[$fullId])) {5042 return false;5043 } else {5044 $GLOBALS[$fullId] = true;5045 return true;5046 }5047 5235 } 5048 5236 … … 5163 5351 */ 5164 5352 function ai4seo_truncate_sentence(string $input, int $soft_cap, int $hard_cap = 0 ): string { 5353 if (ai4seo_prevent_loops(__FUNCTION__)) { 5354 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 5355 return $input; 5356 } 5357 5165 5358 // Ensure the input length is within the limits. 5166 5359 if ( ai4seo_mb_strlen( $input ) <= $soft_cap ) { … … 5222 5415 */ 5223 5416 function ai4seo_get_subpage_url(string $sub_page = "", array $additional_parameter = array(), bool $return_full_path = true): string { 5417 if (ai4seo_prevent_loops(__FUNCTION__)) { 5418 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 5419 return ''; 5420 } 5421 5224 5422 $sub_page = sanitize_key($sub_page); 5225 5423 … … 5292 5490 function ai4seo_get_post_type_page_url(string $post_type, int $current_page = 1, array $additional_parameter = array(), bool $return_full_path = true): string { 5293 5491 $additional_parameter["ai4seo_page"] = $current_page ?: "%#%"; # %#% = pagination workaround 5294 return ai4seo_get_subpage_url(AI4SEO_POST_TYPES_PLUGIN_PAGE_NAME, array("ai4seo_post_type" => $post_type) + $additional_parameter, $return_full_path); 5492 5493 return ai4seo_get_subpage_url( 5494 AI4SEO_POST_TYPES_PLUGIN_PAGE_NAME, 5495 array("ai4seo_post_type" => $post_type) + $additional_parameter, 5496 $return_full_path 5497 ); 5295 5498 } 5296 5499 … … 5331 5534 */ 5332 5535 function ai4seo_is_plugin_page_active(string $plugin_page = ""): bool { 5536 if (ai4seo_prevent_loops(__FUNCTION__)) { 5537 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 5538 return false; 5539 } 5540 5333 5541 $plugin_page = sanitize_key($plugin_page); 5334 5542 $active_plugin_page = ai4seo_get_active_subpage(); … … 5370 5578 */ 5371 5579 function ai4seo_get_active_subpage(): string { 5580 if (ai4seo_prevent_loops(__FUNCTION__)) { 5581 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 5582 return ''; 5583 } 5584 5372 5585 if (!ai4seo_is_user_inside_our_plugin_admin_pages()) { 5373 return "";5586 return ''; 5374 5587 } 5375 5588 … … 5391 5604 */ 5392 5605 function ai4seo_get_active_post_type_subpage(): string { 5606 if (ai4seo_prevent_loops(__FUNCTION__)) { 5607 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 5608 return ''; 5609 } 5610 5393 5611 if (!ai4seo_is_user_inside_our_plugin_admin_pages()) { 5394 5612 return ""; … … 5622 5840 'public' => true, 5623 5841 ); 5842 5624 5843 $post_types = get_post_types($args, 'objects'); 5625 5844 $publicly_accessible_post_types = array(); … … 5752 5971 */ 5753 5972 function ai4seo_get_time_difference_in_seconds(int $utc_timestamp): int { 5973 if (ai4seo_prevent_loops(__FUNCTION__)) { 5974 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 5975 return 0; 5976 } 5977 5754 5978 // Get the current timestamp in WordPress timezone 5755 5979 $timezone = ai4seo_get_option('timezone_string'); … … 5787 6011 */ 5788 6012 function ai4seo_format_unix_timestamp( int $unix_timestamp, string $date_format = 'auto', string $time_format = 'auto', string $separator = ' ', string $timezone = 'auto' ) : string { 6013 if (ai4seo_prevent_loops(__FUNCTION__)) { 6014 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6015 return strval($unix_timestamp); 6016 } 6017 5789 6018 $final_format = ''; 5790 6019 … … 5888 6117 */ 5889 6118 function ai4seo_get_timezone(string $timezone = 'auto' ) : DateTimeZone { 6119 if (ai4seo_prevent_loops(__FUNCTION__)) { 6120 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6121 return new DateTimeZone( 'UTC' ); 6122 } 6123 5890 6124 $timezone_string = ''; 5891 6125 … … 5939 6173 */ 5940 6174 function ai4seo_convert_datetime_local_to_timestamp(string $datetime_local, string $timezone = 'auto'): int { 6175 if (ai4seo_prevent_loops(__FUNCTION__)) { 6176 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6177 return 0; 6178 } 6179 5941 6180 // Get the WordPress timezone 5942 6181 if ($timezone == 'auto') { … … 5966 6205 */ 5967 6206 function ai4seo_deactivate_plugin(): bool { 6207 if (!ai4seo_singleton(__FUNCTION__)) { 6208 return false; 6209 } 6210 5968 6211 // Check if the user has the required permissions 5969 6212 if (!current_user_can('activate_plugins')) { … … 5987 6230 * @return string The clients ip 5988 6231 */ 5989 function ai4seo_get_client_ip() {6232 function ai4seo_get_client_ip(): string { 5990 6233 if (isset($_SERVER['HTTP_CLIENT_IP'])) { 5991 6234 $client_ip = sanitize_text_field($_SERVER['HTTP_CLIENT_IP']); … … 6032 6275 */ 6033 6276 function ai4seo_get_server_ip(): string { 6277 if (ai4seo_prevent_loops(__FUNCTION__)) { 6278 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6279 return ''; 6280 } 6281 6034 6282 $server_ip_response = ai4seo_file_get_contents('https://api.ipify.org'); 6035 6283 … … 6323 6571 */ 6324 6572 function ai4seo_get_mime_type_from_url(string $url ): ?string { 6573 if (ai4seo_prevent_loops(__FUNCTION__)) { 6574 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6575 return null; 6576 } 6577 6325 6578 if ( empty( $url ) ) { 6326 6579 return null; … … 6345 6598 6346 6599 function ai4seo_get_attachment_post_mime_type($attachment_post_id): ?string { 6600 if (ai4seo_prevent_loops(__FUNCTION__)) { 6601 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6602 return null; 6603 } 6604 6347 6605 $attachment_post = get_post($attachment_post_id); 6348 6606 … … 6394 6652 * @return string The formatted backtrace message. 6395 6653 */ 6396 function ai4seo_get_backtrace_debug_message( $separator = '<br>' ): string {6654 function ai4seo_get_backtrace_debug_message(string $separator = '<br>' ): string { 6397 6655 $backtrace_array = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); 6398 6656 $formatted_backtrace = []; … … 6435 6693 6436 6694 function ai4seo_get_recommended_credits_pack_size_by_num_missing_entries(): int { 6695 if (ai4seo_prevent_loops(__FUNCTION__)) { 6696 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6697 return 0; 6698 } 6699 6437 6700 $approximate_credits_needed = ai4seo_get_approximate_credits_needed(); 6438 6701 $credits_packs = ai4seo_get_credits_packs(); … … 6463 6726 6464 6727 function ai4seo_get_approximate_credits_needed() : int { 6728 if (ai4seo_prevent_loops(__FUNCTION__)) { 6729 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6730 return 0; 6731 } 6732 6465 6733 $approximate_credits_needed = 0; 6466 6734 … … 6487 6755 // =========================================================================================== \\ 6488 6756 6489 function ai4seo_normalize_text($text) {6490 $text = trim($text); // remove leading/trailing whitespace6491 $text = preg_replace('/\s+/u', ' ', $text); // normalize all whitespace to a single space6492 $text = str_replace("\xC2\xA0", ' ', $text); // replace non-breaking spaces6493 $text = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', '', $text); // invisible zero-width spaces6494 $text = html_entity_decode($text);6495 return stripslashes($text);6496 }6497 6498 // =========================================================================================== \\6499 6500 6757 function ai4seo_get_base64_from_image_file($image_url): array { 6758 if (ai4seo_prevent_loops(__FUNCTION__)) { 6759 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6760 return array( 6761 "success" => false, 6762 "message" => "Infinite loop detected", 6763 "code" => 91234725 6764 ); 6765 } 6766 6501 6767 // Use wp_safe_remote_get instead of file_get_contents for fetching remote files 6502 6768 try { … … 6582 6848 */ 6583 6849 function ai4seo_get_remote_body(string $url) { 6850 if (ai4seo_prevent_loops(__FUNCTION__)) { 6851 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 6852 return ''; 6853 } 6854 6584 6855 // Attempt 1: Standard remote fetch 6585 6856 $response = wp_safe_remote_get($url, array( … … 6732 7003 * @return string|false File contents on success, false on failure. 6733 7004 */ 6734 function ai4seo_file_get_contents(string $path, $context = null) 6735 { 7005 function ai4seo_file_get_contents(string $path, $context = null) { 6736 7006 if (ai4seo_is_function_usable('file_get_contents')) { 6737 7007 try { … … 6826 7096 function ai4seo_get_option(string $option_name, $default = false ) { 6827 7097 global $wpdb; 7098 7099 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 7100 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7101 return ''; 7102 } 6828 7103 6829 7104 if ( ! isset( $wpdb ) || ! $wpdb ) { … … 6888 7163 function ai4seo_update_option(string $option_name, $option_value, $autoload = null ): bool { 6889 7164 global $wpdb; 7165 7166 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 7167 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7168 return false; 7169 } 6890 7170 6891 7171 if ( ! isset( $wpdb ) || ! $wpdb ) { … … 7024 7304 global $wpdb; 7025 7305 7306 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 7307 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7308 return false; 7309 } 7310 7026 7311 if ( ! isset( $wpdb ) || ! $wpdb ) { 7027 7312 return false; … … 7077 7362 */ 7078 7363 function ai4seo_acquire_semaphore(string $critical_section_name ): bool { 7364 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 7365 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7366 return false; 7367 } 7368 7079 7369 ai4seo_register_semaphore_shutdown_handler(); 7080 7370 … … 7118 7408 */ 7119 7409 function ai4seo_release_semaphore(string $critical_section_name ): bool { 7410 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 7411 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7412 return false; 7413 } 7414 7120 7415 $option_key = ai4seo_get_semaphore_option_key( $critical_section_name ); 7121 7416 … … 7172 7467 */ 7173 7468 function ai4seo_register_semaphore_shutdown_handler() { 7174 static $ai4seo_is_semaphore_shutdown_handler_registered = false; 7175 7176 if ( $ai4seo_is_semaphore_shutdown_handler_registered === true ) { 7469 if (!ai4seo_singleton(__FUNCTION__)) { 7177 7470 return; 7178 7471 } 7179 7180 $ai4seo_is_semaphore_shutdown_handler_registered = true;7181 7472 7182 7473 register_shutdown_function( … … 7207 7498 */ 7208 7499 function ai4seo_try_create_lock(string $option_key, string $token ): bool { 7500 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 7501 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7502 return false; 7503 } 7504 7209 7505 $payload = array( 7210 7506 'token' => (string) $token, … … 7214 7510 // Atomic when the option does not yet exist. 7215 7511 // Do not autoload. 7216 return a dd_option( $option_key, $payload, '', 'no' );7512 return aa_option( $option_key, $payload, '', 'no' ); 7217 7513 } 7218 7514 … … 7245 7541 */ 7246 7542 function ai4seo_release_semaphore_by_key_and_token(string $option_key, string $token ): bool { 7543 if (ai4seo_prevent_loops(__FUNCTION__)) { 7544 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7545 return false; 7546 } 7547 7247 7548 $existing = ai4seo_get_option( $option_key ); 7248 7549 … … 7288 7589 * @return int Post ID or 0. 7289 7590 */ 7290 function ai4seo_get_post_id( array $args = array() ): int { 7591 function ai4seo_get_current_post_id(array $args = array() ): int { 7592 if (ai4seo_prevent_loops(__FUNCTION__)) { 7593 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7594 return 0; 7595 } 7596 7291 7597 $args = wp_parse_args( 7292 7598 $args, … … 7303 7609 if ( ! empty( $ai4seo_post_context_stack ) ) { 7304 7610 $override_id = (int) end( $ai4seo_post_context_stack ); 7611 7305 7612 if ( $override_id > 0 ) { 7306 7613 /** … … 7453 7760 global $wpdb; 7454 7761 7762 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 7763 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7764 return array(); 7765 } 7766 7455 7767 $publicly_accessible_post_types = ai4seo_get_publicly_accessible_post_types(); 7456 7768 … … 7532 7844 */ 7533 7845 function ai4seo_get_condensed_post_content_from_database(int $post_id, bool $debug = false): string { 7846 if (ai4seo_prevent_loops(__FUNCTION__)) { 7847 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7848 return ''; 7849 } 7850 7534 7851 // Retrieve the post object 7535 7852 $post = get_post($post_id); … … 7562 7879 * @return false|string The post or page content or false if the post_id is empty 7563 7880 */ 7564 function ai4seo_get_combined_post_content(int $post_id = 0, string $editor_identifier = "", $debug = false) { 7881 function ai4seo_get_combined_post_content(int $post_id = 0, string $editor_identifier = "", bool $debug = false) { 7882 if (ai4seo_prevent_loops(__FUNCTION__)) { 7883 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 7884 return false; 7885 } 7886 7565 7887 // Define variables for the current theme and the parent theme 7566 7888 $current_theme = wp_get_theme(); … … 7570 7892 if (empty($post_id)) { 7571 7893 // Get post- or page-id 7572 $post_id = ai4seo_get_ post_id();7894 $post_id = ai4seo_get_current_post_id(); 7573 7895 } 7574 7896 … … 7737 8059 global $shortcode_tags; 7738 8060 8061 if (ai4seo_prevent_loops(__FUNCTION__)) { 8062 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 8063 return; 8064 } 8065 7739 8066 // workaround for ACF blocks, as content for ACF blocks are defined inside <!-- wp:acf/... --> tags 7740 8067 if (ai4seo_is_acf_content($content)) { … … 7824 8151 7825 8152 function ai4seo_add_post_context($post_id, &$content) { 8153 if (ai4seo_prevent_loops(__FUNCTION__)) { 8154 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 8155 return; 8156 } 8157 7826 8158 // ADD POST ENTRY CONTEXT 7827 8159 $context = "POST/PAGE CONTEXT: "; … … 8112 8444 } 8113 8445 8446 if (!ai4seo_singleton(__FUNCTION__)) { 8447 return; 8448 } 8449 8114 8450 // Insert post id into option to be analyzed AI4SEO_POSTS_TO_BE_ANALYZED_OPTION_NAME 8115 8451 ai4seo_add_post_ids_to_option(AI4SEO_POSTS_TO_BE_ANALYZED_OPTION_NAME, $post_id); … … 8124 8460 */ 8125 8461 function ai4seo_analyze_post(int $post_id) { 8462 if (ai4seo_prevent_loops(__FUNCTION__)) { 8463 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 8464 return; 8465 } 8466 8126 8467 if (!is_numeric($post_id)) { 8127 8468 return; … … 8171 8512 $cache_key = 'ai4seo_url_exposed_taxonomies_v1'; 8172 8513 $cached = get_transient( $cache_key ); 8514 8173 8515 if ( is_array( $cached ) ) { 8174 8516 return $cached; 8517 } 8518 8519 if (ai4seo_prevent_loops(__FUNCTION__)) { 8520 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 8521 return array(); 8175 8522 } 8176 8523 … … 8721 9068 // Array of language codes and their corresponding names 8722 9069 $languages = array( 8723 'albanian' => __('Albanian', 'ai-for-seo'),8724 'arabic' => __('Arabic', 'ai-for-seo'),8725 'bulgarian' => __('Bulgarian', 'ai-for-seo'),8726 'chinese' => __('Chinese (General)', 'ai-for-seo'),8727 'simplified chinese' => __('Chinese (Simplified)', 'ai-for-seo'),8728 'traditional chinese' => __('Chinese (Traditional)', 'ai-for-seo'),8729 'croatian' => __('Croatian', 'ai-for-seo'),8730 'czech' => __('Czech', 'ai-for-seo'),8731 'danish' => __('Danish', 'ai-for-seo'),8732 'dutch' => __('Dutch', 'ai-for-seo'),8733 'american english' => __('English (America)', 'ai-for-seo'),8734 'british english' => __('English (Britain)', 'ai-for-seo'),8735 'estonian' => __('Estonian', 'ai-for-seo'),8736 'finnish' => __('Finnish', 'ai-for-seo'),8737 'european french' => __('French (Europe)', 'ai-for-seo'),8738 'canadian french' => __('French (Canada)', 'ai-for-seo'),8739 'german' => __('German', 'ai-for-seo'),8740 'greek' => __('Greek', 'ai-for-seo'),8741 'hebrew' => __('Hebrew', 'ai-for-seo'),8742 'hindi' => __('Hindi', 'ai-for-seo'),8743 'hungarian' => __('Hungarian', 'ai-for-seo'),8744 'icelandic' => __('Icelandic', 'ai-for-seo'),8745 'indonesian' => __('Indonesian', 'ai-for-seo'),8746 'italian' => __('Italian', 'ai-for-seo'),8747 'japanese' => __('Japanese', 'ai-for-seo'),8748 'korean' => __('Korean', 'ai-for-seo'),8749 'latvian' => __('Latvian', 'ai-for-seo'),8750 'lithuanian' => __('Lithuanian', 'ai-for-seo'),8751 'macedonian' => __('Macedonian', 'ai-for-seo'),8752 'maltese' => __('Maltese', 'ai-for-seo'),8753 'norwegian' => __('Norwegian', 'ai-for-seo'),8754 'polish' => __('Polish', 'ai-for-seo'),8755 'european portuguese' => __('Portuguese (Europe)', 'ai-for-seo'),8756 'brazilian portuguese' => __('Portuguese (Brazil)', 'ai-for-seo'),8757 'romanian' => __('Romanian', 'ai-for-seo'),8758 'russian' => __('Russian', 'ai-for-seo'),8759 'serbian' => __('Serbian', 'ai-for-seo'),8760 'slovak' => __('Slovak', 'ai-for-seo'),8761 'slovenian' => __('Slovenian', 'ai-for-seo'),8762 'spanish' => __('Spanish', 'ai-for-seo'),8763 'swedish' => __('Swedish', 'ai-for-seo'),8764 'thai' => __('Thai', 'ai-for-seo'),8765 'turkish' => __('Turkish', 'ai-for-seo'),8766 'ukrainian' => __('Ukrainian', 'ai-for-seo'),8767 'vietnamese' => __('Vietnamese', 'ai-for-seo'),9070 'albanian' => esc_html__('Albanian', 'ai-for-seo'), 9071 'arabic' => esc_html__('Arabic', 'ai-for-seo'), 9072 'bulgarian' => esc_html__('Bulgarian', 'ai-for-seo'), 9073 'chinese' => esc_html__('Chinese (General)', 'ai-for-seo'), 9074 'simplified chinese' => esc_html__('Chinese (Simplified)', 'ai-for-seo'), 9075 'traditional chinese' => esc_html__('Chinese (Traditional)', 'ai-for-seo'), 9076 'croatian' => esc_html__('Croatian', 'ai-for-seo'), 9077 'czech' => esc_html__('Czech', 'ai-for-seo'), 9078 'danish' => esc_html__('Danish', 'ai-for-seo'), 9079 'dutch' => esc_html__('Dutch', 'ai-for-seo'), 9080 'american english' => esc_html__('English (America)', 'ai-for-seo'), 9081 'british english' => esc_html__('English (Britain)', 'ai-for-seo'), 9082 'estonian' => esc_html__('Estonian', 'ai-for-seo'), 9083 'finnish' => esc_html__('Finnish', 'ai-for-seo'), 9084 'european french' => esc_html__('French (Europe)', 'ai-for-seo'), 9085 'canadian french' => esc_html__('French (Canada)', 'ai-for-seo'), 9086 'german' => esc_html__('German', 'ai-for-seo'), 9087 'greek' => esc_html__('Greek', 'ai-for-seo'), 9088 'hebrew' => esc_html__('Hebrew', 'ai-for-seo'), 9089 'hindi' => esc_html__('Hindi', 'ai-for-seo'), 9090 'hungarian' => esc_html__('Hungarian', 'ai-for-seo'), 9091 'icelandic' => esc_html__('Icelandic', 'ai-for-seo'), 9092 'indonesian' => esc_html__('Indonesian', 'ai-for-seo'), 9093 'italian' => esc_html__('Italian', 'ai-for-seo'), 9094 'japanese' => esc_html__('Japanese', 'ai-for-seo'), 9095 'korean' => esc_html__('Korean', 'ai-for-seo'), 9096 'latvian' => esc_html__('Latvian', 'ai-for-seo'), 9097 'lithuanian' => esc_html__('Lithuanian', 'ai-for-seo'), 9098 'macedonian' => esc_html__('Macedonian', 'ai-for-seo'), 9099 'maltese' => esc_html__('Maltese', 'ai-for-seo'), 9100 'norwegian' => esc_html__('Norwegian', 'ai-for-seo'), 9101 'polish' => esc_html__('Polish', 'ai-for-seo'), 9102 'european portuguese' => esc_html__('Portuguese (Europe)', 'ai-for-seo'), 9103 'brazilian portuguese' => esc_html__('Portuguese (Brazil)', 'ai-for-seo'), 9104 'romanian' => esc_html__('Romanian', 'ai-for-seo'), 9105 'russian' => esc_html__('Russian', 'ai-for-seo'), 9106 'serbian' => esc_html__('Serbian', 'ai-for-seo'), 9107 'slovak' => esc_html__('Slovak', 'ai-for-seo'), 9108 'slovenian' => esc_html__('Slovenian', 'ai-for-seo'), 9109 'spanish' => esc_html__('Spanish', 'ai-for-seo'), 9110 'swedish' => esc_html__('Swedish', 'ai-for-seo'), 9111 'thai' => esc_html__('Thai', 'ai-for-seo'), 9112 'turkish' => esc_html__('Turkish', 'ai-for-seo'), 9113 'ukrainian' => esc_html__('Ukrainian', 'ai-for-seo'), 9114 'vietnamese' => esc_html__('Vietnamese', 'ai-for-seo'), 8768 9115 ); 8769 9116 … … 8778 9125 * @return string 8779 9126 */ 8780 function ai4seo_get_chart_legend_translation( $legend_identifier): string {9127 function ai4seo_get_chart_legend_translation(string $legend_identifier): string { 8781 9128 $legend_identifier_original = $legend_identifier; 8782 9129 $legend_identifier = strtolower($legend_identifier); … … 8784 9131 switch ($legend_identifier) { 8785 9132 case "done": 8786 return __("Done", "ai-for-seo");9133 return esc_html__("Done", "ai-for-seo"); 8787 9134 case "processing": 8788 return __("Processing", "ai-for-seo");9135 return esc_html__("Processing", "ai-for-seo"); 8789 9136 case "missing": 8790 return __("Missing SEO / Pending", "ai-for-seo");9137 return esc_html__("Missing SEO / Pending", "ai-for-seo"); 8791 9138 case "failed": 8792 return __("Failed (please check details)", "ai-for-seo");9139 return esc_html__("Failed (please check details)", "ai-for-seo"); 8793 9140 default: 8794 9141 return $legend_identifier_original; … … 8800 9147 function ai4seo_get_select_all_checkbox($target_checkbox_name, $label = "auto"): string { 8801 9148 if ($label === "auto") { 8802 $label = __("Select All / Unselect All", "ai-for-seo");9149 $label = esc_html__("Select All / Unselect All", "ai-for-seo"); 8803 9150 } 8804 9151 … … 8848 9195 */ 8849 9196 function ai4seo_get_environmental_variable_accepted_time_output($environmental_variable_name): string { 9197 if (ai4seo_prevent_loops(__FUNCTION__)) { 9198 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9199 return ''; 9200 } 9201 8850 9202 $accepted_time = ai4seo_read_environmental_variable($environmental_variable_name); 8851 9203 … … 8855 9207 $readable_accepted_time = ai4seo_format_unix_timestamp($accepted_time); 8856 9208 $content .= ai4seo_get_svg_tag("square-check", "", "ai4seo-16x16-icon ai4seo-dark-green-icon") . " "; 8857 $content .= sprintf( __("Accepted on %s.", "ai-for-seo"), $readable_accepted_time);9209 $content .= sprintf(esc_html__("Accepted on %s.", "ai-for-seo"), $readable_accepted_time); 8858 9210 } else { 8859 9211 //$content .= ai4seo_get_svg_tag("square-xmark", "", "ai4seo-16x16-icon ai4seo-red-icon") . " "; 8860 //$content .= __("Not accepted yet.", "ai-for-seo");9212 //$content .= esc_html__("Not accepted yet.", "ai-for-seo"); 8861 9213 } 8862 9214 return $content; … … 8871 9223 */ 8872 9224 function ai4seo_was_seo_autopilot_set_up_at_least_x_seconds_ago(int $duration = 300): bool { 9225 if (ai4seo_prevent_loops(__FUNCTION__)) { 9226 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9227 return false; 9228 } 9229 8873 9230 $ai4seo_seo_autopilot_start_time = (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_SEO_AUTOPILOT_SET_UP_TIME); 8874 9231 … … 8985 9342 $voucher_code_output .= "</div>"; 8986 9343 $voucher_code_output .= "</div>"; 9344 8987 9345 return $voucher_code_output; 8988 9346 } … … 8997 9355 */ 8998 9356 function ai4seo_get_dashicon_tag(string $icon_name, string $css_class = ""): string { 8999 9000 9001 9357 return '<i class="dashicons dashicons-' . esc_attr($icon_name) . ' ' . esc_attr($css_class) . '"></i>'; 9002 9358 } … … 9041 9397 */ 9042 9398 function ai4seo_get_active_third_party_seo_plugin_details(): array { 9399 if (ai4seo_prevent_loops(__FUNCTION__)) { 9400 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9401 return array(); 9402 } 9403 9043 9404 $active_supported_third_party_seo_plugin_details = array(); 9044 9405 … … 9062 9423 */ 9063 9424 function ai4seo_get_any_third_party_seo_plugin_keyphrase(int $post_id): string { 9425 if (ai4seo_prevent_loops(__FUNCTION__)) { 9426 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9427 return ''; 9428 } 9429 9064 9430 $active_supported_third_party_seo_plugins = ai4seo_get_active_third_party_seo_plugin_details(); 9065 9431 … … 9092 9458 global $wpdb; 9093 9459 9460 if (ai4seo_prevent_loops(__FUNCTION__)) { 9461 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9462 return array(); 9463 } 9464 9094 9465 if (!$post_ids) { 9095 9466 return array(); … … 9177 9548 global $wpdb; 9178 9549 9550 if (ai4seo_prevent_loops(__FUNCTION__)) { 9551 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9552 return array(); 9553 } 9554 9179 9555 # todo: make this whole function dynamic 9180 9556 … … 9245 9621 function ai4seo_is_plugin_or_theme_active($identifier): bool { 9246 9622 global $ai4seo_cached_active_plugins_and_themes; 9623 9624 if (ai4seo_prevent_loops(__FUNCTION__)) { 9625 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9626 return false; 9627 } 9247 9628 9248 9629 // try use cache first … … 9425 9806 */ 9426 9807 function ai4seo_schedule_cron_jobs() { 9808 if (!ai4seo_singleton(__FUNCTION__)) { 9809 return; 9810 } 9811 9427 9812 // add custom cron schedule for automated metadata generation 9428 9813 if (!wp_next_scheduled(AI4SEO_BULK_GENERATION_CRON_JOB_NAME)) { … … 9443 9828 */ 9444 9829 function ai4seo_un_schedule_cron_jobs() { 9830 if (!ai4seo_singleton(__FUNCTION__)) { 9831 return; 9832 } 9833 9445 9834 wp_clear_scheduled_hook(AI4SEO_BULK_GENERATION_CRON_JOB_NAME); 9446 9835 wp_clear_scheduled_hook(AI4SEO_ANALYSE_PLUGIN_PERFORMANCE_CRON_JOB_NAME); … … 9455 9844 */ 9456 9845 function ai4seo_inject_additional_cronjob_call(string $cronjob_name, int $delay = 1) { 9846 if (ai4seo_prevent_loops(__FUNCTION__)) { 9847 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9848 return; 9849 } 9850 9457 9851 // is the cron job enabled? 9458 9852 if (!AI4SEO_CRON_JOBS_ENABLED && wp_doing_cron()) { … … 9519 9913 */ 9520 9914 function ai4seo_set_last_cron_job_call_time(string $cron_job_name, int $time = 0): bool { 9915 if (ai4seo_prevent_loops(__FUNCTION__)) { 9916 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9917 return false; 9918 } 9919 9521 9920 if (!wp_doing_cron()) { 9522 9921 return false; … … 9534 9933 $last_specific_cronjob_calls[$cron_job_name] = $time; 9535 9934 ai4seo_update_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_SPECIFIC_CRON_JOB_CALLS, $last_specific_cronjob_calls); 9935 9536 9936 return true; 9537 9937 } … … 9545 9945 */ 9546 9946 function ai4seo_get_last_cron_job_call_time(string $cron_job_name = ""): int { 9947 if (ai4seo_prevent_loops(__FUNCTION__)) { 9948 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9949 return 0; 9950 } 9951 9547 9952 if ($cron_job_name) { 9548 9953 $cron_job_name = sanitize_key($cron_job_name); … … 9564 9969 */ 9565 9970 function ai4seo_get_cron_job_status(string $cron_job_name): string { 9971 if (ai4seo_prevent_loops(__FUNCTION__)) { 9972 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9973 return ''; 9974 } 9975 9566 9976 $all_cronjob_job_status = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_CRON_JOB_STATUS_LIST); 9977 9567 9978 return $all_cronjob_job_status[$cron_job_name] ?? "unknown"; 9568 9979 } … … 9577 9988 */ 9578 9989 function ai4seo_set_cron_job_status(string $cron_job_name, string $status): bool { 9990 if (ai4seo_prevent_loops(__FUNCTION__)) { 9991 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 9992 return false; 9993 } 9994 9579 9995 if (!wp_doing_cron()) { 9580 9996 return false; … … 9599 10015 */ 9600 10016 function ai4seo_refresh_cron_job_status_update_time(string $cron_job_name): bool { 10017 if (ai4seo_prevent_loops(__FUNCTION__)) { 10018 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 10019 return false; 10020 } 10021 9601 10022 $all_cronjob_job_status_time = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_CRON_JOB_STATUS_LAST_UPDATE_TIMES); 9602 10023 $all_cronjob_job_status_time[$cron_job_name] = time(); 10024 9603 10025 return ai4seo_update_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_CRON_JOB_STATUS_LAST_UPDATE_TIMES, $all_cronjob_job_status_time); 9604 10026 } … … 9612 10034 */ 9613 10035 function ai4seo_get_cron_job_status_update_time(string $cron_job_name): int { 10036 if (ai4seo_prevent_loops(__FUNCTION__)) { 10037 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 10038 return 0; 10039 } 10040 9614 10041 $all_cronjob_job_status_time = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_CRON_JOB_STATUS_LAST_UPDATE_TIMES); 10042 9615 10043 return $all_cronjob_job_status_time[$cron_job_name] ?? 0; 9616 10044 } … … 9623 10051 */ 9624 10052 function ai4seo_automated_generation_cron_job($debug = false): bool { 10053 if (!ai4seo_singleton(__FUNCTION__)) { 10054 return true; 10055 } 10056 9625 10057 // is the cron job enabled? 9626 10058 if (!AI4SEO_CRON_JOBS_ENABLED && wp_doing_cron()) { … … 9790 10222 */ 9791 10223 function ai4seo_automated_metadata_generation($debug = false, $only_this_post_id = 0): bool { 10224 if (ai4seo_prevent_loops(__FUNCTION__)) { 10225 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 10226 return false; 10227 } 10228 9792 10229 $active_meta_tags = ai4seo_get_active_meta_tags(); 9793 10230 … … 10205 10642 global $ai4seo_allowed_attachment_mime_types; 10206 10643 10644 if (ai4seo_prevent_loops(__FUNCTION__)) { 10645 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 10646 return false; 10647 } 10648 10207 10649 $active_attachment_attributes = ai4seo_get_active_attachment_attributes(); 10208 10650 $supported_attachment_post_types = ai4seo_get_supported_attachment_post_types(); … … 10542 10984 10543 10985 function ai4seo_generate_attachment_attributes_using_base64($attachment_url, $mime_type, $robhub_api_call_parameters) { 10986 if (ai4seo_prevent_loops(__FUNCTION__)) { 10987 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 10988 return array( 10989 "success" => false, 10990 "message" => "Prevented infinite loop", 10991 "code" => 361324724, 10992 ); 10993 } 10994 10544 10995 $base64_from_image_file_response = ai4seo_get_base64_from_image_file($attachment_url); 10545 10996 … … 10598 11049 function ai4seo_excavate_post_entries_with_missing_metadata(bool $debug = false): bool { 10599 11050 global $wpdb; 11051 11052 if (ai4seo_prevent_loops(__FUNCTION__)) { 11053 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 11054 return false; 11055 } 10600 11056 10601 11057 $metadata_credits_costs_per_post = ai4seo_calculate_metadata_credits_cost_per_post(); … … 10761 11217 global $ai4seo_allowed_attachment_mime_types; 10762 11218 11219 if (ai4seo_prevent_loops(__FUNCTION__)) { 11220 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 11221 return false; 11222 } 11223 10763 11224 $supported_attachment_post_types = ai4seo_get_supported_attachment_post_types(); 10764 11225 … … 10926 11387 */ 10927 11388 function ai4seo_analyze_plugin_performance(bool $debug = false): bool { 11389 if (!ai4seo_singleton(__FUNCTION__)) { 11390 return false; 11391 } 11392 10928 11393 // check if disable heavy db operations parameter is set 10929 11394 ai4seo_check_for_disable_heavy_db_operations_parameter(); 10930 10931 11395 ai4seo_set_cron_job_status(AI4SEO_ANALYSE_PLUGIN_PERFORMANCE_CRON_JOB_NAME, "processing"); 10932 11396 … … 10951 11415 10952 11416 function ai4seo_check_for_disable_heavy_db_operations_parameter(): void { 11417 if (ai4seo_prevent_loops(__FUNCTION__)) { 11418 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 11419 return; 11420 } 11421 10953 11422 if ( !isset( $_GET['ai4seo_disable_heavy_db_operations'] ) ) { 10954 11423 return; … … 10975 11444 */ 10976 11445 function ai4seo_try_start_posts_table_analysis(bool $restart_if_completed = false, bool $debug = false) { 11446 if (!ai4seo_singleton(__FUNCTION__ . ($restart_if_completed ? "_restart" : "_no_restart"))) { 11447 return; 11448 } 11449 11450 if (ai4seo_prevent_loops(__FUNCTION__)) { 11451 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 11452 return; 11453 } 11454 10977 11455 ai4seo_run_with_ignore_user_abort( 10978 11456 'ai4seo_run_posts_table_analysis_task', … … 10984 11462 10985 11463 function ai4seo_run_posts_table_analysis_task(bool $restart_if_completed = false, bool $debug = false) { 11464 global $ai4seo_did_run_post_table_analysis; 11465 11466 if (!ai4seo_singleton(__FUNCTION__ . ($restart_if_completed ? "_restart" : "_no_restart"))) { 11467 return; 11468 } 11469 11470 if (ai4seo_prevent_loops(__FUNCTION__)) { 11471 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 11472 return; 11473 } 11474 10986 11475 if (ai4seo_get_setting(AI4SEO_SETTING_DISABLE_HEAVY_DB_OPERATIONS) && (!isset($_GET["ai4seo_debug_posts_table_analysis"]) || !$_GET["ai4seo_debug_posts_table_analysis"] || !$debug)) { 10987 11476 if ($debug) { … … 11003 11492 $posts_table_analysis_start_time = (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_POSTS_TABLE_ANALYSIS_START_TIME, false); 11004 11493 $do_restart = false; 11005 $processing_timeout = 180; // 3 minutes 11006 $usleep_between_runs = 100000; // 0.1 seconds 11007 $total_max_run_time = AI4SEO_POST_TABLE_ANALYSIS_MAX_EXECUTION_TIME; // 5 seconds 11494 $processing_timeout = AI4SEO_POST_TABLE_ANALYSIS_PROCESSING_TIMEOUT; // XX seconds 11495 $usleep_between_runs = AI4SEO_POST_TABLE_ANALYSIS_SLEEP_BETWEEN_RUNS; // 0.X seconds 11496 $total_max_run_time = AI4SEO_POST_TABLE_ANALYSIS_MAX_EXECUTION_TIME; // X seconds 11497 11498 // double it when running in ajax 11499 if (defined('DOING_AJAX') && DOING_AJAX) { 11500 $total_max_run_time *= 2; 11501 $usleep_between_runs /= 2; 11502 } 11503 11504 $max_runs_per_task = $total_max_run_time / ($usleep_between_runs / 1000000); // calculate max runs per task based on sleep time 11008 11505 11009 11506 // first, check if the state is "in-progress" and if the last start time was longer ago than $timeout -> restart … … 11016 11513 } 11017 11514 11018 error_log("AI4SEO: Posts table analysis timed out -> restarting");11515 //error_log("AI4SEO: Posts table analysis timed out -> restarting"); 11019 11516 } else { 11020 11517 if ($debug) { 11021 echo "<pre>" . esc_html(__FUNCTION__) . " > Posts table analysis already in progress -> stop</pre>";11518 echo "<pre>" . esc_html(__FUNCTION__) . " > Posts table analysis already in progress since " . esc_html(date("Y-m-d H:i:s", $posts_table_analysis_start_time)) . " -> stop</pre>"; 11022 11519 } 11023 11520 … … 11061 11558 $previous_posts_table_analysis_last_post_id = -1; 11062 11559 $run_counter = 0; 11063 $max_run_counter = 10;11064 11560 $is_finished = false; 11065 11561 11066 11562 try { 11067 while (time() - $start_time < $total_max_run_time && $run_counter < $max_run _counter) {11563 while (time() - $start_time < $total_max_run_time && $run_counter < $max_runs_per_task) { 11068 11564 $run_counter++; 11069 11565 … … 11113 11609 } 11114 11610 } 11611 11612 $ai4seo_did_run_post_table_analysis = true; 11115 11613 } 11116 11614 … … 11132 11630 global $ai4seo_allowed_attachment_mime_types; 11133 11631 11632 if (ai4seo_prevent_loops(__FUNCTION__)) { 11633 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 11634 return true; 11635 } 11636 11134 11637 $total_rows_per_run = AI4SEO_POST_TABLE_ANALYSIS_BATCH_SIZE; 11638 11639 // if ajax -> double it 11640 if (defined('DOING_AJAX') && DOING_AJAX) { 11641 $total_rows_per_run *= 2; 11642 } 11135 11643 11136 11644 // Cursor-based pagination query … … 11256 11764 // check if fully covered 11257 11765 if ($this_percentage == 100) { 11258 $new_post_ids_by_option[AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME][] = $this_post_id; 11259 11260 if (!isset($current_generation_status_summary[AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME][$this_post_type])) { 11261 $current_generation_status_summary[AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME][$this_post_type] = 0; 11766 // remove from fully covered those entries that has not been generated yet 11767 if ($generate_metadata_for_fully_covered_entries && !$this_post_was_generated) { 11768 $this_percentage = 0; // set to 0 to mark as missing 11769 } else { 11770 $new_post_ids_by_option[AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME][] = $this_post_id; 11771 11772 if (!isset($current_generation_status_summary[AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME][$this_post_type])) { 11773 $current_generation_status_summary[AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME][$this_post_type] = 0; 11774 } 11775 11776 $current_generation_status_summary[AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME][$this_post_type]++; 11262 11777 } 11263 11264 $current_generation_status_summary[AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME][$this_post_type]++; 11265 11266 // when $generate_metadata_for_fully_covered_entries = true -> we consider this entry also missing, if we did not generate the data yet 11267 if ($generate_metadata_for_fully_covered_entries && !$this_post_was_generated) { 11268 $new_post_ids_by_option[AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME][] = $this_post_id; 11269 11270 if (!isset($current_generation_status_summary[AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME][$this_post_type])) { 11271 $current_generation_status_summary[AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME][$this_post_type] = 0; 11272 } 11273 11274 $current_generation_status_summary[AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME][$this_post_type]++; 11275 } 11276 } else { 11778 } 11779 11780 if ($this_percentage < 100) { 11277 11781 $new_post_ids_by_option[AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME][] = $this_post_id; 11278 11782 … … 11343 11847 $this_post_was_generated = in_array($this_post_id, $generated_data_post_ids); 11344 11848 $this_attachment_post_type = 'attachment'; 11849 $is_fully_covered = ($num_fields_covered >= $num_total_attachment_attributes_fields); 11345 11850 11346 11851 // check if fully covered 11347 if ($num_fields_covered >= $num_total_attachment_attributes_fields) { 11348 $new_post_ids_by_option[AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][] = (int) $this_post_id; 11349 11350 if (!isset($current_generation_status_summary[AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type])) { 11351 $current_generation_status_summary[AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type] = 0; 11852 if ($is_fully_covered) { 11853 // remove from fully covered those entries that has not been generated yet 11854 if ($generate_attachment_attributes_for_fully_covered_entries && !$this_post_was_generated) { 11855 $is_fully_covered = false; // set to false to mark as missing 11856 } else { 11857 $new_post_ids_by_option[AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][] = (int) $this_post_id; 11858 11859 if (!isset($current_generation_status_summary[AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type])) { 11860 $current_generation_status_summary[AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type] = 0; 11861 } 11862 11863 $current_generation_status_summary[AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type]++; 11352 11864 } 11353 11354 $current_generation_status_summary[AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type]++; 11355 11356 // when $generate_media_attributes_for_fully_covered_entries = true -> we consider this entry also missing, if we did not generate the data yet 11357 if ($generate_attachment_attributes_for_fully_covered_entries && !$this_post_was_generated) { 11358 $new_post_ids_by_option[AI4SEO_MISSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][] = (int) $this_post_id; 11359 11360 if (!isset($current_generation_status_summary[AI4SEO_MISSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type])) { 11361 $current_generation_status_summary[AI4SEO_MISSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type] = 0; 11362 } 11363 11364 $current_generation_status_summary[AI4SEO_MISSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][$this_attachment_post_type]++; 11365 } 11366 } else { 11865 } 11866 11867 if (!$is_fully_covered) { 11367 11868 $new_post_ids_by_option[AI4SEO_MISSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][] = (int) $this_post_id; 11368 11869 … … 11452 11953 11453 11954 function ai4seo_read_generation_status_summary() { 11955 if (ai4seo_prevent_loops(__FUNCTION__)) { 11956 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 11957 return array(); 11958 } 11959 11454 11960 // read AI4SEO_GENERATION_STATUS_SUMMARY_OPTION_NAME 11455 11961 $generation_status_summary = ai4seo_get_option( AI4SEO_GENERATION_STATUS_SUMMARY_OPTION_NAME, '{}' ); … … 11515 12021 11516 12022 function ai4seo_reset_posts_table_analysis() { 12023 if (ai4seo_prevent_loops(__FUNCTION__)) { 12024 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 12025 return; 12026 } 12027 11517 12028 ai4seo_update_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_POSTS_TABLE_ANALYSIS_LAST_POST_ID, 0, false); 11518 12029 ai4seo_update_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_POSTS_TABLE_ANALYSIS_STATE, "idle", false); … … 11563 12074 $num_missing_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME); 11564 12075 $num_missing_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_MISSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 12076 11565 12077 return array_merge($num_missing_metadata_by_post_type, $num_missing_attachment_attributes); 11566 12078 } … … 11575 12087 $num_fully_covered_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME); 11576 12088 $num_fully_covered_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 12089 11577 12090 return array_merge($num_fully_covered_metadata_by_post_type, $num_fully_covered_attachment_attributes); 11578 12091 } … … 11587 12100 $num_generated_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_GENERATED_METADATA_POST_IDS_OPTION_NAME); 11588 12101 $num_generated_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_GENERATED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 12102 11589 12103 return array_merge($num_generated_metadata_by_post_type, $num_generated_attachment_attributes); 11590 12104 } … … 11597 12111 */ 11598 12112 function ai4seo_get_num_finished_posts_by_post_type(): array { 11599 $generate_metadata_for_fully_covered_entries = ai4seo_get_setting(AI4SEO_SETTING_GENERATE_METADATA_FOR_FULLY_COVERED_ENTRIES); 11600 11601 if ($generate_metadata_for_fully_covered_entries) { 11602 $num_finished_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_GENERATED_METADATA_POST_IDS_OPTION_NAME); 11603 } else { 11604 $num_finished_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME); 11605 } 11606 11607 $generate_media_attributes_for_fully_covered_entries = ai4seo_get_setting(AI4SEO_SETTING_GENERATE_ATTACHMENT_ATTRIBUTES_FOR_FULLY_COVERED_ENTRIES); 11608 11609 if ($generate_media_attributes_for_fully_covered_entries) { 11610 $num_finished_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_GENERATED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 11611 } else { 11612 $num_finished_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 11613 } 12113 $num_finished_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME); 12114 $num_finished_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 11614 12115 11615 12116 return array_merge($num_finished_metadata_by_post_type, $num_finished_attachment_attributes); … … 11626 12127 $num_failed_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_FAILED_METADATA_POST_IDS_OPTION_NAME); 11627 12128 $num_failed_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_FAILED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 12129 11628 12130 return array_merge($num_failed_metadata_by_post_type, $num_failed_attachment_attributes); 11629 12131 } … … 11638 12140 $num_pending_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_PENDING_METADATA_POST_IDS_OPTION_NAME); 11639 12141 $num_pending_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_PENDING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 12142 11640 12143 return array_merge($num_pending_metadata_by_post_type, $num_pending_attachment_attributes); 11641 12144 } … … 11650 12153 $num_processing_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_PROCESSING_METADATA_POST_IDS_OPTION_NAME); 11651 12154 $num_processing_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_PROCESSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME); 12155 11652 12156 return array_merge($num_processing_metadata_by_post_type, $num_processing_attachment_attributes); 11653 12157 } … … 11719 12223 function ai4seo_read_our_plugins_metadata_by_post_ids( array $post_ids ): array { 11720 12224 global $wpdb; 12225 12226 if (ai4seo_prevent_loops(__FUNCTION__)) { 12227 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 12228 return array(); 12229 } 11721 12230 11722 12231 $active_meta_tags = ai4seo_get_active_meta_tags(); … … 11773 12282 continue; 11774 12283 } 11775 12284 11776 12285 foreach ( $this_rows as $this_row ) { 11777 12286 $this_post_id = absint( $this_row['post_id'] ); … … 11802 12311 function ai4seo_read_third_party_seo_plugin_metadata_by_post_ids($third_party_plugin_name, array $post_ids): array { 11803 12312 global $wpdb; 12313 12314 if (ai4seo_prevent_loops(__FUNCTION__)) { 12315 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 12316 return array(); 12317 } 11804 12318 11805 12319 // make sure all entries of post_ids are numeric … … 12072 12586 * @return array the metadata by post-ids, using metadata-identifier keys 12073 12587 */ 12074 function ai4seo_read_all_in_one_seo_metadata_by_post_ids( $post_ids): array {12588 function ai4seo_read_all_in_one_seo_metadata_by_post_ids(array $post_ids): array { 12075 12589 // check table "wp_aioseo_posts" for the post id. Columns are "title", "description", "og_title", "og_description", "twitter_title", "twitter_description" 12076 12590 $metadata_identifier_mapping = array( … … 12144 12658 */ 12145 12659 function ai4seo_read_available_metadata_by_post_ids(array $post_ids, bool $consider_third_party_seo_plugin_metadata = true): array { 12660 if (ai4seo_prevent_loops(__FUNCTION__)) { 12661 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 12662 return array(); 12663 } 12664 12146 12665 // make sure post_ids is not empty 12147 12666 if (empty($post_ids)) { … … 12246 12765 */ 12247 12766 function ai4seo_read_num_available_metadata_by_post_ids(array $post_ids): array { 12767 if (ai4seo_prevent_loops(__FUNCTION__)) { 12768 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 12769 return array(); 12770 } 12771 12248 12772 if (!defined('AI4SEO_METADATA_DETAILS')) { 12249 12773 return array(); … … 12281 12805 */ 12282 12806 function ai4seo_read_percentage_of_available_metadata_by_post_ids(array $post_ids, int $round_precision = 0): array { 12807 if (ai4seo_prevent_loops(__FUNCTION__)) { 12808 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 12809 return array(); 12810 } 12811 12283 12812 $active_meta_tags = ai4seo_get_active_meta_tags(); 12284 12813 … … 12390 12919 function ai4seo_read_is_posts_metadata_fully_covered(int $post_id): bool { 12391 12920 $percentage_of_active_metadata_by_post_ids = ai4seo_read_percentage_of_available_metadata_by_post_ids(array($post_id)); 12921 12392 12922 return (($percentage_of_active_metadata_by_post_ids[$post_id] ?? 0) == 100); 12393 12923 } … … 12573 13103 12574 13104 /** 12575 * Compares two post content summaries. Returns true if they share a XX% similarity12576 * @param $post_content_summary_1 string the first post content summary12577 * @param $post_content_summary_2 string the second post content summary12578 * @param $min_similarity_percentage int the percentage of similarity12579 * @return bool12580 */12581 function ai4seo_are_post_content_summaries_similar(string $post_content_summary_1, string $post_content_summary_2, int $min_similarity_percentage = 90): bool {12582 // make sure that the similarity percentage is between 0 and 10012583 $min_similarity_percentage = max(0, min(100, $min_similarity_percentage));12584 12585 // compare the two strings12586 $similarity_percentage = 0;12587 similar_text($post_content_summary_1, $post_content_summary_2, $similarity_percentage);12588 12589 // return true if the similarity is greater than the given percentage12590 return ($similarity_percentage >= $min_similarity_percentage);12591 }12592 12593 // =========================================================================================== \\12594 12595 /**12596 13105 * Returns the configured maximum length for the given editor field identifier. 12597 13106 * … … 12654 13163 12655 13164 return $string; 12656 }12657 12658 // =========================================================================================== \\12659 12660 /**12661 * Validates that an editor input respects its configured character limit.12662 * Only ment for AJAX calls.12663 * @param string $ai4seo_value The value to validate.12664 * @param string $ai4seo_identifier The metadata or attachment identifier.12665 * @param string $ai4seo_field_label The human-readable field label for error messages.12666 * @return string12667 */12668 function ai4seo_assert_editor_input_length(string $ai4seo_value, string $ai4seo_identifier, string $ai4seo_field_label): string {12669 $ai4seo_length_limit = ai4seo_get_max_editor_input_length($ai4seo_identifier);12670 12671 if (ai4seo_mb_strlen($ai4seo_value) > $ai4seo_length_limit) {12672 $ai4seo_error_message = sprintf(12673 esc_html__('%1$s cannot exceed %2$d characters.', 'ai-for-seo'),12674 esc_html($ai4seo_field_label),12675 $ai4seo_length_limit12676 );12677 12678 ai4seo_send_json_error($ai4seo_error_message, 5011221025);12679 wp_die();12680 }12681 12682 return $ai4seo_value;12683 13165 } 12684 13166 … … 12711 13193 function ai4seo_update_active_metadata(int $post_id, array $metadata_updates, bool $overwrite_existing_data = false): bool { 12712 13194 if (!defined('AI4SEO_METADATA_DETAILS')) { 13195 return false; 13196 } 13197 13198 if (ai4seo_prevent_loops(__FUNCTION__)) { 13199 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 12713 13200 return false; 12714 13201 } … … 12788 13275 **/ 12789 13276 function ai4seo_update_third_party_seo_plugins_metadata(int $post_id, string $metadata_identifier, string $metadata_value, bool $overwrite_existing_data): bool { 13277 if (ai4seo_prevent_loops(__FUNCTION__)) { 13278 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 13279 return false; 13280 } 13281 12790 13282 $we_should_not_save_our_own_metadata = false; 12791 13283 … … 13113 13605 */ 13114 13606 function ai4seo_get_posts_language(int $post_id): string { 13607 if (ai4seo_prevent_loops(__FUNCTION__)) { 13608 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 13609 return ''; 13610 } 13611 13115 13612 $metadata_generation_language = sanitize_text_field(ai4seo_get_setting(AI4SEO_SETTING_METADATA_GENERATION_LANGUAGE)); 13116 13613 … … 13138 13635 */ 13139 13636 function ai4seo_get_active_meta_tags(): array { 13637 if (ai4seo_prevent_loops(__FUNCTION__)) { 13638 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 13639 return array(); 13640 } 13641 13140 13642 $active_meta_tags = ai4seo_get_setting(AI4SEO_SETTING_ACTIVE_META_TAGS); 13141 13643 … … 13178 13680 global $wpdb; 13179 13681 13682 if (ai4seo_prevent_loops(__FUNCTION__)) { 13683 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 13684 return array(); 13685 } 13686 13180 13687 // allow single ID 13181 13688 if ( ! is_array( $attachment_post_ids ) ) { … … 13209 13716 $chunks = array_chunk( $attachment_post_ids, 10000 ); 13210 13717 $first_chunk = true; 13718 13211 13719 13212 13720 // --- TITLE / CAPTION / DESCRIPTION / GUID ----------------------------------------- \\ … … 13256 13764 } 13257 13765 13766 13258 13767 // --- ALT TEXT --------------------------------------------------------------------- \\ 13768 13259 13769 if ( in_array( 'alt-text', $active_attachment_attributes, true ) ) { 13260 13770 $postmeta_table = esc_sql( $wpdb->postmeta ); … … 13330 13840 */ 13331 13841 function ai4seo_create_empty_attachment_attributes_coverage_array(array $attachment_post_ids): array { 13842 if (ai4seo_prevent_loops(__FUNCTION__)) { 13843 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 13844 return array(); 13845 } 13846 13332 13847 // make sure all entries of post_ids are numeric 13333 13848 foreach ($attachment_post_ids as $attachment_post_id) { … … 13378 13893 */ 13379 13894 function ai4seo_are_attachment_attributes_fully_covered(int $attachment_post_id): bool { 13895 if (ai4seo_prevent_loops(__FUNCTION__)) { 13896 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 13897 return true; 13898 } 13899 13380 13900 // get the total amount of attachment attributes 13381 13901 $active_attachment_attributes = ai4seo_get_active_attachment_attributes(); … … 13440 13960 */ 13441 13961 function ai4seo_refresh_one_posts_attachment_attributes_coverage(int $attachment_post_id, $post = null) { 13962 if (ai4seo_prevent_loops(__FUNCTION__)) { 13963 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 13964 return; 13965 } 13966 13442 13967 if (!is_numeric($attachment_post_id)) { 13443 13968 return; … … 13473 13998 global $ai4seo_allowed_attachment_mime_types; 13474 13999 14000 if (ai4seo_prevent_loops(__FUNCTION__)) { 14001 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14002 return false; 14003 } 14004 13475 14005 if (!is_numeric($attachment_post_id)) { 13476 14006 return false; … … 13518 14048 */ 13519 14049 function ai4seo_smart_image_base64_encode( string $image_data ): string { 14050 if (ai4seo_prevent_loops(__FUNCTION__)) { 14051 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14052 return ''; 14053 } 14054 13520 14055 // Set the file size limit to 1 MB. 13521 14056 $max_file_size = 100000; // 1 MB in bytes. … … 13609 14144 function ai4seo_update_attachment_attributes(int $attachment_post_id, array $attachment_attribute_updates = array(), bool $force_overwrite_all_existing_data = false): bool { 13610 14145 global $wpdb; 14146 14147 if (ai4seo_prevent_loops(__FUNCTION__)) { 14148 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14149 return false; 14150 } 13611 14151 13612 14152 // sanitize … … 13741 14281 */ 13742 14282 function ai4seo_get_attachments_language(int $attachment_post_id): string { 14283 if (ai4seo_prevent_loops(__FUNCTION__)) { 14284 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14285 return ''; 14286 } 14287 13743 14288 $attachment_attributes_generation_language = sanitize_text_field(ai4seo_get_setting(AI4SEO_SETTING_ATTACHMENT_ATTRIBUTES_GENERATION_LANGUAGE)); 13744 14289 … … 13785 14330 * @return array the supported attachment post types 13786 14331 */ 13787 function ai4seo_get_supported_attachment_post_types() { 14332 function ai4seo_get_supported_attachment_post_types(): array { 14333 if (ai4seo_prevent_loops(__FUNCTION__)) { 14334 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14335 return array(); 14336 } 14337 13788 14338 $ai4seo_active_attachment_attributes = ai4seo_get_active_attachment_attributes(); 13789 14339 … … 13858 14408 return $active_attachment_attributes_names; 13859 14409 } 13860 13861 14410 13862 14411 … … 13872 14421 * @return bool True if the post meta was updated, false if not 13873 14422 */ 13874 function ai4seo_update_postmeta_if_empty( $post_id, $meta_key,$meta_value): bool {14423 function ai4seo_update_postmeta_if_empty(int $post_id, string $meta_key, string $meta_value): bool { 13875 14424 $post_id = sanitize_key($post_id); 13876 14425 $meta_key = sanitize_key($meta_key); … … 13909 14458 */ 13910 14459 function ai4seo_get_post_ids_from_option(string $option): array { 14460 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 14461 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14462 return array(); 14463 } 14464 13911 14465 $option = sanitize_key($option); 13912 14466 … … 13953 14507 */ 13954 14508 function ai4seo_add_post_ids_to_option($option, $post_ids): bool { 14509 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 14510 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14511 return false; 14512 } 14513 13955 14514 $option = sanitize_key($option); 13956 14515 … … 13991 14550 */ 13992 14551 function ai4seo_remove_contradictory_post_ids(string $add_to_this_option, array $post_ids) { 14552 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 14553 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14554 return; 14555 } 14556 13993 14557 switch ($add_to_this_option) { 13994 14558 // now missing -> remove from fully covered and generated … … 14002 14566 break; 14003 14567 14004 // now fully covered and/or generated-> remove from missing14568 // now fully covered -> remove from missing 14005 14569 case AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME: 14006 14570 ai4seo_remove_post_ids_from_option(AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME, $post_ids); … … 14032 14596 /** 14033 14597 * Remove post ids from an option that is saved as json 14034 * @param $ai4seo_option14035 * @param $post_ids14598 * @param string $remove_from_this_option 14599 * @param int|array $post_ids 14036 14600 * @return bool 14037 14601 */ 14038 function ai4seo_remove_post_ids_from_option($ai4seo_option, $post_ids): bool { 14039 $ai4seo_option = sanitize_key($ai4seo_option); 14602 function ai4seo_remove_post_ids_from_option(string $remove_from_this_option, $post_ids): bool { 14603 if (ai4seo_prevent_loops(__FUNCTION__, 2)) { 14604 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14605 return false; 14606 } 14607 14608 $remove_from_this_option = sanitize_key($remove_from_this_option); 14040 14609 14041 14610 if (!is_array($post_ids)) { … … 14051 14620 14052 14621 // get old post ids 14053 $old_post_ids = ai4seo_get_post_ids_from_option($ ai4seo_option);14622 $old_post_ids = ai4seo_get_post_ids_from_option($remove_from_this_option); 14054 14623 14055 14624 // remove the new post ids from the old ones … … 14074 14643 14075 14644 // update the option 14076 return ai4seo_update_option($ ai4seo_option, $new_post_ids);14645 return ai4seo_update_option($remove_from_this_option, $new_post_ids); 14077 14646 } 14078 14647 … … 14128 14697 */ 14129 14698 function ai4seo_send_json_success($response = [], $status_code = null) { 14699 if (!ai4seo_singleton(__FUNCTION__)) { 14700 return; 14701 } 14702 14130 14703 $noise = ''; 14131 14704 … … 14157 14730 */ 14158 14731 function ai4seo_send_json_error(string $error_message = "Unknown Error", int $error_code = 999, $error_headline = "", $add_contact_us_link = true) { 14732 if (!ai4seo_singleton(__FUNCTION__)) { 14733 return; 14734 } 14735 14159 14736 $clear_buffer = apply_filters('ai4seo_clear_buffer_on_error', true); 14160 14737 … … 14177 14754 14178 14755 function ai4seo_normalize_ajax_response_data(&$data) { 14756 if (!ai4seo_singleton(__FUNCTION__)) { 14757 return; 14758 } 14759 14179 14760 if (is_array($data)) { 14180 14761 array_walk_recursive($data, function (&$item) { … … 14194 14775 14195 14776 function ai4seo_normalize_ajax_response_item(&$item) { 14777 if (ai4seo_prevent_loops(__FUNCTION__)) { 14778 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 14779 return; 14780 } 14781 14196 14782 if (!is_string($item)) { 14197 14783 return; … … 14207 14793 */ 14208 14794 function ai4seo_print_ajax_nonce_field(): void { 14795 if (!ai4seo_singleton(__FUNCTION__)) { 14796 return; 14797 } 14798 14209 14799 // Only when our menu/page is active. 14210 14800 if ( !ai4seo_is_user_inside_our_plugin_admin_pages() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended … … 14227 14817 */ 14228 14818 function ai4seo_save_anything($additional_upcoming_updates = array()) { 14819 if (!ai4seo_singleton(__FUNCTION__)) { 14820 return; 14821 } 14822 14229 14823 // add $_POST to the updates 14230 14824 if (!is_array($additional_upcoming_updates)) { … … 14704 15298 // Make sure that this function is only called once 14705 15299 if (!ai4seo_singleton(__FUNCTION__)) { 14706 return;14707 }14708 14709 // Check if user has permission to manage this plugin14710 if (!ai4seo_can_manage_this_plugin()) {14711 ai4seo_send_json_error(esc_html__("You do not have permission to perform this action.", "ai-for-seo"), 13109825);14712 15300 return; 14713 15301 } … … 14926 15514 global $wpdb; 14927 15515 15516 // Make sure that this function is only called once 15517 if (!ai4seo_singleton(__FUNCTION__)) { 15518 return; 15519 } 15520 14928 15521 // read all pid's of wp_ngg_pictures 14929 15522 $nextgen_gallery_images = $wpdb->get_results("SELECT `pid`, `image_slug`, `galleryid`, `filename`, `description`, `alttext`, `imagedate`, `updated_at` FROM " . esc_sql($wpdb->prefix) . "ngg_pictures WHERE `pid` > 0", ARRAY_A); … … 15072 15665 global $ai4seo_are_settings_initialized; 15073 15666 15667 if (ai4seo_prevent_loops(__FUNCTION__, 5)) { 15668 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 15669 return ''; 15670 } 15671 15074 15672 if (!$ai4seo_are_settings_initialized) { 15075 15673 ai4seo_init_settings(); … … 15078 15676 if (!$ai4seo_are_settings_initialized) { 15079 15677 error_log("AI4SEO: Settings are not initialized. #7122824"); 15080 //error_log(__FUNCTION__ . "() @ " . __LINE__ . ": " . print_r("AI4SEO: Settings are not initialized. #7122824", true) . "\r\n\r\nBACKTRACE\r\n " . ai4seo_get_backtrace_debug_message("\r\n") . "\r\n\r\n");15678 #error_log(__FUNCTION__ . "() @ " . __LINE__ . ": " . print_r("AI4SEO: Settings are not initialized. #7122824", true) . "\r\n\r\nBACKTRACE\r\n " . ai4seo_get_backtrace_debug_message("\r\n") . "\r\n\r\n"); 15081 15679 return ""; 15082 15680 } … … 15106 15704 global $ai4seo_are_settings_initialized; 15107 15705 15706 if (ai4seo_prevent_loops(__FUNCTION__, 5)) { 15707 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 15708 return false; 15709 } 15710 15108 15711 if (!$ai4seo_are_settings_initialized) { 15109 15712 ai4seo_init_settings(); … … 15135 15738 15136 15739 /** 15137 * Function to update the wp_options table with the current settings, by removing default values15138 * @return bool15139 */15140 function ai4seo_push_local_setting_changes_to_database(): bool {15141 global $ai4seo_settings;15142 15143 $ai4seo_settings_copy = $ai4seo_settings;15144 15145 foreach ($ai4seo_settings_copy AS $ai4seo_setting_name => $ai4seo_setting_value) {15146 // if the setting is equal to the default setting, set it to null to prevent overhead15147 if (isset(AI4SEO_DEFAULT_SETTINGS[$ai4seo_setting_name]) && $ai4seo_setting_value == AI4SEO_DEFAULT_SETTINGS[$ai4seo_setting_name]) {15148 unset($ai4seo_settings_copy[$ai4seo_setting_name]);15149 }15150 }15151 15152 // Save updated settings to database15153 return ai4seo_update_option(AI4SEO_SETTINGS_OPTION_NAME, $ai4seo_settings_copy, true);15154 }15155 15156 // =========================================================================================== \\15157 15158 /**15159 15740 * Update values of given settings 15160 15741 * @param $setting_changes array An array of settings to update … … 15163 15744 function ai4seo_bulk_update_settings(array $setting_changes): bool { 15164 15745 global $ai4seo_settings; 15746 15747 if (ai4seo_prevent_loops(__FUNCTION__)) { 15748 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 15749 return false; 15750 } 15165 15751 15166 15752 $ai4seo_new_settings = $ai4seo_settings; … … 15185 15771 15186 15772 /** 15773 * Function to update the wp_options table with the current settings, by removing default values 15774 * @return bool 15775 */ 15776 function ai4seo_push_local_setting_changes_to_database(): bool { 15777 global $ai4seo_settings; 15778 15779 if (ai4seo_prevent_loops(__FUNCTION__)) { 15780 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 15781 return false; 15782 } 15783 15784 $ai4seo_settings_copy = $ai4seo_settings; 15785 15786 foreach ($ai4seo_settings_copy AS $ai4seo_setting_name => $ai4seo_setting_value) { 15787 // if the setting is equal to the default setting, set it to null to prevent overhead 15788 if (isset(AI4SEO_DEFAULT_SETTINGS[$ai4seo_setting_name]) && $ai4seo_setting_value == AI4SEO_DEFAULT_SETTINGS[$ai4seo_setting_name]) { 15789 unset($ai4seo_settings_copy[$ai4seo_setting_name]); 15790 } 15791 } 15792 15793 // Save updated settings to database 15794 return ai4seo_update_option(AI4SEO_SETTINGS_OPTION_NAME, $ai4seo_settings_copy, true); 15795 } 15796 15797 // =========================================================================================== \\ 15798 15799 /** 15187 15800 * Validate value of a setting 15188 15801 * @return bool True if the value is valid, false if not 15189 15802 */ 15190 15803 function ai4seo_validate_setting_value(string $setting_name, $setting_value): bool { 15804 if (ai4seo_prevent_loops(__FUNCTION__, 5)) { 15805 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 15806 return false; 15807 } 15808 15191 15809 switch ($setting_name) { 15192 15810 case AI4SEO_SETTING_BULK_GENERATION_DURATION: … … 15624 16242 function ai4seo_is_bulk_generation_enabled(string $post_type): bool { 15625 16243 $enabled_bulk_generation_post_types = ai4seo_get_setting(AI4SEO_SETTING_ENABLED_BULK_GENERATION_POST_TYPES) ?: array(); 16244 15626 16245 return is_array($enabled_bulk_generation_post_types) && in_array($post_type, $enabled_bulk_generation_post_types); 15627 16246 } … … 15635 16254 function ai4seo_is_any_bulk_generation_enabled(): bool { 15636 16255 $enabled_bulk_generations_post_types = ai4seo_get_setting(AI4SEO_SETTING_ENABLED_BULK_GENERATION_POST_TYPES) ?: array(); 16256 15637 16257 return count($enabled_bulk_generations_post_types) > 0; 15638 16258 } … … 15650 16270 function ai4seo_read_all_environmental_variables(bool $use_cache = true): array { 15651 16271 global $ai4seo_environmental_variables; 16272 16273 if (ai4seo_prevent_loops(__FUNCTION__, 5)) { 16274 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16275 return array(); 16276 } 15652 16277 15653 16278 if (!isset($ai4seo_environmental_variables) || !$ai4seo_environmental_variables) { … … 15696 16321 */ 15697 16322 function ai4seo_read_environmental_variable(string $environmental_variable_name, bool $use_cache = true) { 16323 if (ai4seo_prevent_loops(__FUNCTION__, 5)) { 16324 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16325 return null; 16326 } 16327 15698 16328 // Make sure that $environmental_variable_name-parameter has content 15699 16329 if (!$environmental_variable_name) { … … 15729 16359 function ai4seo_update_environmental_variable(string $environmental_variable_name, $new_environmental_variable_value, bool $use_cache = true): bool { 15730 16360 global $ai4seo_environmental_variables; 16361 16362 if (ai4seo_prevent_loops(__FUNCTION__, 5)) { 16363 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16364 return false; 16365 } 15731 16366 15732 16367 if (!isset(AI4SEO_DEFAULT_ENVIRONMENTAL_VARIABLES[$environmental_variable_name])) { … … 15795 16430 function ai4seo_delete_environmental_variable(string $environmental_variable_name): bool { 15796 16431 global $ai4seo_environmental_variables; 16432 16433 if (ai4seo_prevent_loops(__FUNCTION__, 5)) { 16434 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16435 return false; 16436 } 15797 16437 15798 16438 // Make sure that $environmental_variable_name-parameter has content … … 15861 16501 ); 15862 16502 16503 if (ai4seo_prevent_loops(__FUNCTION__)) { 16504 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16505 return $result; 16506 } 16507 15863 16508 // Nothing to do. 15864 16509 if ( empty( $environmental_variable_updates ) ) { … … 15938 16583 */ 15939 16584 function ai4seo_validate_environmental_variable_value(string $environmental_variable_name, $environmental_variable_value): bool { 16585 if (ai4seo_prevent_loops(__FUNCTION__, 5)) { 16586 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16587 return false; 16588 } 16589 15940 16590 switch ($environmental_variable_name) { 15941 16591 case AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_KNOWN_PLUGIN_VERSION: … … 16057 16707 */ 16058 16708 function ai4seo_read_robhub_environmental_variable(string $environmental_variable_constant_name) { 16709 if (ai4seo_prevent_loops(__FUNCTION__)) { 16710 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16711 return null; 16712 } 16713 16059 16714 // get the constant value 16060 16715 $constant_value = ai4seo_get_robhub_environmental_variable_constant_value($environmental_variable_constant_name); … … 16072 16727 */ 16073 16728 function ai4seo_update_robhub_environmental_variable(string $environmental_variable_constant_name, $new_environmental_variable_value): bool { 16729 if (ai4seo_prevent_loops(__FUNCTION__)) { 16730 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16731 return false; 16732 } 16733 16074 16734 // get the constant value 16075 16735 $constant_value = ai4seo_get_robhub_environmental_variable_constant_value($environmental_variable_constant_name); … … 16109 16769 */ 16110 16770 function ai4seo_push_notification(string $notification_index, string $message, bool $force = false, array $additional_fields = array()): bool { 16771 if (ai4seo_prevent_loops(__FUNCTION__)) { 16772 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16773 return false; 16774 } 16775 16111 16776 if (empty($notification_index)) { 16112 16777 return false; … … 16204 16869 /** 16205 16870 * Function to auto-dismiss expired notifications and get displayable notifications 16206 * @param bool $skip_num_displayable_notification_condition Whether to skip the condition that checks the number of displayable notifications 16871 * @param bool $skip_num_displayable_notification_condition Whether to skip the condition that checks the number of displayable notifications to prevent loops 16207 16872 * @return array Array of notifications that should be displayed (not dismissed and not expired) 16208 16873 */ 16209 16874 function ai4seo_get_displayable_notifications(bool $skip_num_displayable_notification_condition = false): array { 16875 if (ai4seo_prevent_loops(__FUNCTION__, 3)) { 16876 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16877 return array(); 16878 } 16879 16210 16880 $notifications = ai4seo_get_option(AI4SEO_NOTIFICATIONS_OPTION_NAME, array()); 16211 16881 … … 16286 16956 */ 16287 16957 function ai4seo_echo_notice_from_notification(string $notification_index, array $notification) { 16958 if (ai4seo_prevent_loops(__FUNCTION__)) { 16959 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 16960 return; 16961 } 16962 16288 16963 if (empty($notification_index) || empty($notification) || empty($notification['message'])) { 16289 16964 return; … … 16382 17057 */ 16383 17058 function ai4seo_get_notification_buttons(string $notification_index, array $notification): string { 17059 if (ai4seo_prevent_loops(__FUNCTION__)) { 17060 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 17061 return ''; 17062 } 17063 16384 17064 $show_dismiss_button = !(isset($notification['is_permanent']) && $notification['is_permanent']); 16385 17065 $show_not_now_button = (bool) ($notification['not_now_button'] ?? false); # replaces dismiss button if set … … 16515 17195 16516 17196 function ai4seo_check_notification_conditions(string $notification_index, array $additional_fields = array(), bool $skip_num_displayable_notification_condition = false): bool { 17197 if (ai4seo_prevent_loops(__FUNCTION__, 3)) { 17198 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 17199 return false; 17200 } 17201 16517 17202 $conditions = array(); 16518 17203 $debug = false; # set to true to enable debug logging … … 16711 17396 */ 16712 17397 function ai4seo_refresh_unread_notifications_count() { 17398 if (ai4seo_prevent_loops(__FUNCTION__)) { 17399 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 17400 return; 17401 } 17402 16713 17403 $displayable_notifications = ai4seo_get_displayable_notifications(); 16714 17404 … … 16752 17442 */ 16753 17443 function ai4seo_get_num_unread_notification(): int { 17444 if (ai4seo_prevent_loops(__FUNCTION__)) { 17445 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 17446 return 0; 17447 } 17448 16754 17449 return (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_UNREAD_NOTIFICATIONS_COUNT); 16755 17450 } … … 16762 17457 */ 16763 17458 function ai4seo_mark_all_displayable_notifications_as_read(): bool { 17459 if (ai4seo_prevent_loops(__FUNCTION__)) { 17460 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 17461 return false; 17462 } 17463 16764 17464 $displayable_notifications = ai4seo_get_displayable_notifications(); 16765 17465 $all_notifications = ai4seo_get_option(AI4SEO_NOTIFICATIONS_OPTION_NAME, array()); … … 16808 17508 */ 16809 17509 function ai4seo_mark_notification_as_read(string $notification_index): bool { 17510 if (ai4seo_prevent_loops(__FUNCTION__)) { 17511 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 17512 return false; 17513 } 17514 16810 17515 if (empty($notification_index)) { 16811 17516 return false; … … 16845 17550 */ 16846 17551 function ai4seo_mark_notification_as_dismissed(string $index): bool { 17552 if (ai4seo_prevent_loops(__FUNCTION__)) { 17553 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 17554 return false; 17555 } 17556 16847 17557 if (empty($index)) { 16848 17558 return false; … … 16891 17601 */ 16892 17602 function ai4seo_remove_notification(string $notification_index): bool { 17603 if (ai4seo_prevent_loops(__FUNCTION__)) { 17604 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 17605 return false; 17606 } 17607 16893 17608 if (empty($notification_index)) { 16894 17609 return false; … … 16922 17637 16923 17638 function ai4seo_check_for_new_notifications() { 17639 if (!ai4seo_singleton(__FUNCTION__)) { 17640 return; 17641 } 17642 16924 17643 $is_user_on_our_dashboard = ai4seo_is_plugin_page_active("dashboard"); 16925 17644 … … 16949 17668 function ai4seo_check_for_unfinished_posts_table_analysis_notification($force = false) { 16950 17669 global $wpdb; 17670 17671 if (!ai4seo_singleton(__FUNCTION__)) { 17672 return; 17673 } 17674 16951 17675 $notification_index = "unfinished-posts-table-analysis"; 16952 17676 … … 16998 17722 16999 17723 function ai4seo_check_for_plugin_update_notification($last_known_plugin_version, $force = false) { 17724 if (!ai4seo_singleton(__FUNCTION__)) { 17725 return; 17726 } 17727 17000 17728 $notification_index = "plugin-update"; 17001 17729 … … 17094 17822 17095 17823 function ai4seo_check_for_heavy_db_operations_disabled_notification( bool $force = false ) { 17824 if (!ai4seo_singleton(__FUNCTION__)) { 17825 return; 17826 } 17827 17096 17828 $notification_index = 'heavy-db-operations-disabled'; 17097 17829 … … 17126 17858 17127 17859 function ai4seo_check_for_wpml_heads_up_notification($force = false) { 17860 if (!ai4seo_singleton(__FUNCTION__)) { 17861 return; 17862 } 17863 17128 17864 $notification_index = "wpml-heads-up"; 17129 17865 … … 17159 17895 17160 17896 function ai4seo_check_for_rate_us_notification($force = false) { 17897 if (!ai4seo_singleton(__FUNCTION__)) { 17898 return; 17899 } 17900 17161 17901 $notification_index = "rate-us"; 17162 17902 … … 17194 17934 17195 17935 function ai4seo_check_for_low_credits_balance_notification($force = false) { 17936 if (!ai4seo_singleton(__FUNCTION__)) { 17937 return; 17938 } 17939 17196 17940 $notification_index = "low-credits-balance"; 17197 17941 … … 17246 17990 */ 17247 17991 function ai4seo_check_for_missing_entries_notification($force = false) { 17992 if (!ai4seo_singleton(__FUNCTION__)) { 17993 return; 17994 } 17995 17248 17996 $notification_index = "missing-entries"; 17249 17997 … … 17379 18127 17380 18128 function ai4seo_check_for_robhub_account_error_notification($api_response, $force = false) { 18129 if (!ai4seo_singleton(__FUNCTION__)) { 18130 return; 18131 } 18132 17381 18133 $notification_index = "robhub-account-error"; 17382 18134 … … 17424 18176 17425 18177 function ai4seo_check_for_plugin_update_available($latest_plugin_version, $force = false) { 18178 if (!ai4seo_singleton(__FUNCTION__)) { 18179 return; 18180 } 18181 17426 18182 $notification_index = "plugin-update-available"; 17427 18183 … … 17464 18220 17465 18221 function ai4seo_check_for_payg_status_errors($payg_status, $force = false) { 18222 if (!ai4seo_singleton(__FUNCTION__)) { 18223 return; 18224 } 18225 17466 18226 $notification_index = "payg-status-error"; 17467 18227 … … 17536 18296 */ 17537 18297 function ai4seo_check_for_inefficient_cron_jobs_notification($force = false) { 18298 if (!ai4seo_singleton(__FUNCTION__)) { 18299 return; 18300 } 18301 17538 18302 $notification_index = "inefficient-cron-jobs"; 17539 18303 … … 17617 18381 */ 17618 18382 function ai4seo_check_for_finished_seo_autopilot_notification($force = false) { 18383 if (!ai4seo_singleton(__FUNCTION__)) { 18384 return; 18385 } 18386 17619 18387 $notification_index = "seo-autopilot-finished"; 17620 18388 … … 17751 18519 17752 18520 function ai4seo_check_discount_notification($discount, $allow_notification_force = false) { 18521 if (!ai4seo_singleton(__FUNCTION__)) { 18522 return; 18523 } 18524 17753 18525 $notification_index = "discount"; 17754 18526 $discount_name = $discount['name'] ?? ''; … … 17877 18649 */ 17878 18650 function ai4seo_get_latest_tos_or_toc_or_pp_update_timestamp(): int { 18651 if (ai4seo_prevent_loops(__FUNCTION__)) { 18652 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 18653 return 0; 18654 } 18655 17879 18656 // check the last known aiforseo.ai's terms update 17880 18657 $last_website_toc_and_pp_update_time = (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_WEBSITE_TOC_AND_PP_UPDATE_TIME); … … 18073 18850 */ 18074 18851 function ai4seo_set_tos_accept_details(bool $accepted_enhanced_reporting, string $action = "unknown") { 18852 if (ai4seo_prevent_loops(__FUNCTION__)) { 18853 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 18854 return; 18855 } 18856 18075 18857 // collect additional data and put it into the wp_option "AI4SEO_ADDITIONAL_TOS_ACCEPT_DETAILS" 18076 18858 $additional_tos_accept_details = array( … … 18104 18886 } 18105 18887 18888 if (!ai4seo_singleton(__FUNCTION__)) { 18889 return; 18890 } 18891 18106 18892 // check in wp_options if we have additional tos accept details 18107 18893 $additional_tos_accept_details = ai4seo_get_option(AI4SEO_ADDITIONAL_TOS_ACCEPT_DETAILS_OPTION_NAME); … … 18165 18951 */ 18166 18952 function ai4seo_add_latest_activity_entry(int $post_id, string $status, string $action, int $cost = 0, string $details = ""): bool { 18953 if (ai4seo_prevent_loops(__FUNCTION__)) { 18954 error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__); 18955 return false; 18956 } 18957 18167 18958 $new_entry["timestamp"] = time(); 18168 18959 … … 18263 19054 // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\ 18264 19055 18265 function ai4seo_send_pay_as_you_go_settings() { 19056 function ai4seo_send_pay_as_you_go_settings(): bool { 19057 if (!ai4seo_singleton(__FUNCTION__)) { 19058 return false; 19059 } 19060 18266 19061 // call robhub api endpoint "payg-settings" with current payg settings 18267 19062 $robhub_endpoint = "client/payg-settings"; -
ai-for-seo/trunk/assets/js/ai-for-seo-scripts.js
r3399672 r3408847 7827 7827 hidden_mode_triggers: 0, 7828 7828 full_reload_triggers: 0, 7829 user_interaction_locks: 0 7829 user_interaction_locks: 0, 7830 last_ajax_response_duration_ms: 0 7830 7831 }; 7831 7832 … … 8279 8280 } 8280 8281 8282 let ajax_response_start_time = 0; 8283 8284 if (ai4seo_dashboard_debug_metrics) { 8285 console.info('AI for SEO: Dashboard refresh attempt #' + ai4seo_dashboard_metrics.refresh_attempts); 8286 ajax_response_start_time = performance.now(); 8287 } 8288 8281 8289 ai4seo_perform_ajax_call('ai4seo_get_dashboard_html', {}, false) // auto_check_response = false 8282 8290 .then(response => { 8291 8292 if (ai4seo_dashboard_debug_metrics) { 8293 let ajax_response_duration = performance.now() - ajax_response_start_time; 8294 ai4seo_dashboard_metrics.last_ajax_response_duration_ms = ajax_response_duration; 8295 console.info('AI for SEO: Dashboard AJAX response time: ' + ajax_response_duration.toFixed(2) + 'ms'); 8296 } 8283 8297 8284 8298 // Check if this request was cancelled (idempotent discard) -
ai-for-seo/trunk/changelog.txt
r3399672 r3408847 1 1 == Changelog == 2 3 = 2.2.3 = 4 * Bug Fixes & Maintenance: Fixed 7 minor bugs and implemented 2 usability improvements. 2 5 3 6 = 2.2.2 = -
ai-for-seo/trunk/includes/ajax/process/generate-attachment-attributes.php
r3399672 r3408847 232 232 233 233 if (!$ai4seo_this_success) { 234 #error_log("AI for SEO: Could not save attachmentattributes: " . print_r($ai4seo_new_attachment_attributes, true));235 ai4seo_send_json_error(esc_html__("Could not save attachmentattributes.", "ai-for-seo"), 3218161025);234 #error_log("AI for SEO: Could not save media attributes: " . print_r($ai4seo_new_attachment_attributes, true)); 235 ai4seo_send_json_error(esc_html__("Could not save media attributes.", "ai-for-seo"), 3218161025); 236 236 } 237 237 … … 242 242 243 243 if (!$ai4seo_this_success) { 244 #error_log("AI for SEO: Could not save attachmentalt text: " . print_r($ai4seo_new_attachment_attributes, true));245 ai4seo_send_json_error(esc_html__("Could not save attachmentalt text.", "ai-for-seo"), 3318161025);244 #error_log("AI for SEO: Could not save media alt text: " . print_r($ai4seo_new_attachment_attributes, true)); 245 ai4seo_send_json_error(esc_html__("Could not save media alt text.", "ai-for-seo"), 3318161025); 246 246 } 247 247 } -
ai-for-seo/trunk/includes/api/class-robhub-api-communicator.php
r3399672 r3408847 11 11 12 12 class Ai4Seo_RobHubApiCommunicator { 13 public bool $is_initialized = false; 13 14 private string $version = "v1"; 14 15 private string $api_url = "https://api.robhub.ai"; -
ai-for-seo/trunk/includes/menu-frame.php
r3395515 r3408847 35 35 $ai4seo_unread_notifications_count = 0; 36 36 } else { 37 $ai4seo_unread_notifications_count = (int)ai4seo_get_num_unread_notification();37 $ai4seo_unread_notifications_count = ai4seo_get_num_unread_notification(); 38 38 } 39 39 -
ai-for-seo/trunk/includes/modal_schemas/customize-pay-as-you-go.php
r3395515 r3408847 176 176 echo "<li>"; 177 177 echo sprintf( 178 esc_html__("I will automatically purchase %s Credits for %s whenever myCredits balance falls below %s Credits.", "ai-for-seo"),178 esc_html__("I will automatically purchase %s Credits for %s whenever the Credits balance falls below %s Credits.", "ai-for-seo"), 179 179 "<strong><span id='ai4seo-payg-summary-credits-amount'>" . esc_html($ai4seo_credits_packs[$ai4seo_payg_stripe_price_id]["credits_amount"] ?? 0) . "</span></strong>", 180 180 ($ai4seo_credits_pack_original_price != $ai4seo_credits_pack_discounted_price ? "<span style='text-decoration: line-through; color: #555;'>" . esc_html($ai4seo_used_currency_uppercase) . " <span id='ai4seo-payg-summary-reference-price'>" . esc_html(number_format_i18n($ai4seo_credits_pack_original_price, 2)) . "</span></span> " : "") . -
ai-for-seo/trunk/includes/modal_schemas/get-more-credits.php
r3399672 r3408847 118 118 echo "<div class='ai4seo-get-more-credits-section-left'>"; 119 119 echo "<div class='ai4seo-get-more-credits-section-big-number'>"; 120 echo $ai4seo_section_number;120 echo esc_html($ai4seo_section_number); 121 121 echo "</div>"; 122 122 echo "</div>"; … … 154 154 // PAID PLAN 155 155 echo "<div class='ai4seo-subscription-badge'>"; 156 echo ai4seo_ get_svg_tag("circle-check", "", "ai4seo-dark-green-icon") . " ";156 echo ai4seo_wp_kses(ai4seo_get_svg_tag("circle-check", "", "ai4seo-dark-green-icon") . " "); 157 157 echo sprintf( 158 158 esc_html__("Subscribed to %s.", "ai-for-seo"), … … 226 226 echo "<div class='ai4seo-get-more-credits-section-left'>"; 227 227 echo "<div class='ai4seo-get-more-credits-section-big-number'>"; 228 echo $ai4seo_section_number;228 echo esc_html($ai4seo_section_number); 229 229 echo "</div>"; 230 230 echo "</div>"; … … 261 261 echo "<div class='ai4seo-get-more-credits-section-left'>"; 262 262 echo "<div class='ai4seo-get-more-credits-section-big-number'>"; 263 echo $ai4seo_section_number;263 echo esc_html($ai4seo_section_number); 264 264 echo "</div>"; 265 265 echo "</div>"; … … 367 367 echo "<div class='ai4seo-get-more-credits-section-left'>"; 368 368 echo "<div class='ai4seo-get-more-credits-section-big-number'>"; 369 echo $ai4seo_section_number;369 echo esc_html($ai4seo_section_number); 370 370 echo "</div>"; 371 371 echo "</div>"; -
ai-for-seo/trunk/includes/pages/content_types/attachment.php
r3399672 r3408847 261 261 } 262 262 263 echo $ai4seo_filter_form_html;263 echo ai4seo_wp_kses($ai4seo_filter_form_html); 264 264 265 265 // Stop script if no posts have been found -> show message and stop page rendering … … 523 523 524 524 echo "<div class='ai4seo-pagination'>"; 525 echo $ai4seo_pagination_links;525 echo ai4seo_wp_kses($ai4seo_pagination_links); 526 526 echo "</div>"; 527 527 } -
ai-for-seo/trunk/includes/pages/content_types/post.php
r3399672 r3408847 64 64 $ai4seo_all_failed_metadata_post_ids = ai4seo_get_post_ids_from_option(AI4SEO_FAILED_METADATA_POST_IDS_OPTION_NAME); 65 65 66 $ai4seo_missing_metadata_post_ids = ai4seo_get_post_ids_from_option(AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME); 66 67 $ai4seo_generate_metadata_for_fully_covered_entries = ai4seo_get_setting(AI4SEO_SETTING_GENERATE_METADATA_FOR_FULLY_COVERED_ENTRIES); 68 $ai4seo_fully_covered_metadata_post_ids = ai4seo_get_post_ids_from_option(AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME); 67 69 68 70 if ($ai4seo_generate_metadata_for_fully_covered_entries) { 69 71 $ai4seo_generated_metadata_post_ids = ai4seo_get_post_ids_from_option(AI4SEO_GENERATED_METADATA_POST_IDS_OPTION_NAME); 70 $ai4seo_complete_metadata_post_ids = $ai4seo_generated_metadata_post_ids; 72 73 // remove from fully covered those entries that has not been generated yet 74 $ai4seo_fully_covered_metadata_post_ids = array_values(array_diff($ai4seo_fully_covered_metadata_post_ids, $ai4seo_generated_metadata_post_ids)); 71 75 } else { 72 76 $ai4seo_generated_metadata_post_ids = array(); 73 $ai4seo_complete_metadata_post_ids = ai4seo_get_post_ids_from_option(AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME);74 77 } 75 78 … … 127 130 128 131 $ai4seo_search_status_map = array( 129 'complete' => $ai4seo_ complete_metadata_post_ids,130 'missing' => ai4seo_get_post_ids_from_option(AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME),132 'complete' => $ai4seo_fully_covered_metadata_post_ids, 133 'missing' => $ai4seo_missing_metadata_post_ids, 131 134 'failed' => $ai4seo_all_failed_metadata_post_ids, 132 135 'processing' => array_merge($ai4seo_pending_metadata_post_ids, $ai4seo_processing_metadata_post_ids), … … 226 229 // === TABLE WITH ALL POSTS ================================================================== \\ 227 230 228 echo $ai4seo_filter_form_html;231 echo ai4seo_wp_kses($ai4seo_filter_form_html); 229 232 230 233 // Stop script if no posts have been found -> show message and stop page rendering … … 293 296 } 294 297 295 if ($ai4seo_generate_metadata_for_fully_covered_entries) { 296 $ai4seo_this_metadata_generation_is_not_finished = !in_array($ai4seo_this_post_id, $ai4seo_generated_metadata_post_ids); 297 } else { 298 $ai4seo_this_metadata_generation_is_not_finished = ($ai4seo_this_active_metadata_coverage_percentage < 100); 299 } 300 298 $ai4seo_this_metadata_generation_is_not_finished = !in_array($ai4seo_this_post_id, $ai4seo_fully_covered_metadata_post_ids); 301 299 $ai4seo_this_post_is_failed_to_fill = in_array($ai4seo_this_post_id, $ai4seo_current_page_failed_to_fill_post_ids); 302 300 … … 478 476 479 477 echo "<div class='ai4seo-pagination'>"; 480 echo $ai4seo_pagination_links;478 echo ai4seo_wp_kses($ai4seo_pagination_links); 481 479 echo "</div>"; 482 480 } -
ai-for-seo/trunk/includes/pages/dashboard.php
r3399672 r3408847 19 19 // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\ 20 20 21 global $ai4seo_did_run_post_table_analysis; 21 22 $ai4seo_current_utc_hour = (int)gmdate("H"); 22 23 $ai4seo_posts_table_analysis_state = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_POSTS_TABLE_ANALYSIS_STATE); … … 133 134 echo "</div>"; 134 135 135 if (!$ai4seo_heavy_db_operations_disabled ) {136 if (!$ai4seo_heavy_db_operations_disabled && $ai4seo_did_run_post_table_analysis) { 136 137 echo "<div id='ai4seo-no-dashboard-refresh-delay'></div>"; 137 138 } … … 291 292 echo "<div class='ai4seo-credits-number'>"; 292 293 if ($ai4seo_is_robhub_account_synced) { 293 echo $ai4seo_current_credits_balance;294 echo esc_html($ai4seo_current_credits_balance); 294 295 } else { 295 296 echo "<span class='ai4seo-red-message'>"; … … 415 416 echo "</div>"; 416 417 } else if ($ai4seo_posts_table_analysis_state !== 'completed') { 417 echo "<img src='" . esc_url(ai4seo_get_ai_for_seo_logo_url("256x256")) . "' alt='" . esc_attr__(" AI for SEO is active but waiting for analysis tasks to complete", "ai-for-seo") . "' class='ai4seo-bulk-generation-status-active-logo'>";418 echo "<img src='" . esc_url(ai4seo_get_ai_for_seo_logo_url("256x256")) . "' alt='" . esc_attr__("SEO Autopilot is active, but it is currently waiting for the analysis tasks to finish.", "ai-for-seo") . "' class='ai4seo-bulk-generation-status-active-logo'>"; 418 419 419 420 echo "<div class='ai4seo-bulk-generation-status-text'>"; … … 600 601 // if details given, output them else the action 601 602 if (isset($ai4seo_this_latest_activity_entry["details"]) && $ai4seo_this_latest_activity_entry["details"]) { 602 echo ai4seo_mb_substr(esc_html($ai4seo_this_latest_activity_entry["details"]), 0, 160);603 echo esc_html(ai4seo_mb_substr($ai4seo_this_latest_activity_entry["details"], 0, 160)); 603 604 } else { 604 605 // metadata-manually-generated", "metadata-bulk-generated", "attachment-attributes-manually-generated", "attachment-attributes-bulk-generated … … 692 693 693 694 echo "<p>" . esc_html__("Hi, I'm your personal SEO expert!", "ai-for-seo") . "</p>"; 694 echo "<p>" . esc_html__("Together, we’ll grow your visibility, connect you with the right audience, and turn your search goals into real results.", "ai-for-seo") . "</p>";695 695 echo "<p>" . esc_html__("Whether you need help setting up the plugin, planning your SEO strategy, or want to discuss a custom approach, the AI for SEO team and I have you covered.", "ai-for-seo") . "</p>"; 696 echo "<p>" . esc_html__("Ready to take your SEO to the next level? Let’s connect!", "ai-for-seo") . "</p>"; 696 echo "<p>" . ai4seo_wp_kses(__("Together, we’ll grow your visibility and turn your search goals into real results. Some users report an <strong>increase</strong> in reach and clicks of over <strong>1900%</strong>.", "ai-for-seo")) . "</p>"; 697 echo "<p>" . ai4seo_wp_kses(__("Ready to take your SEO to the next level? <strong>Let’s connect!</strong>", "ai-for-seo")) . "</p>"; 697 698 698 699 echo ai4seo_wp_kses(ai4seo_get_button_text_link_tag("https://aiforseo.ai/contact", "arrow-up-right-from-square", esc_html__("Contact SEO Expert now", "ai-for-seo"), "", "", "_blank")); … … 762 763 763 764 // Changes count 764 echo "<span class='ai4seo-changes-count'>(" . $ai4seo_this_changes_count . " change" . ($ai4seo_this_changes_count > 1 ? "s" : "") . ")</span>"; 765 echo "<span class='ai4seo-changes-count'>(" . sprintf( 766 /* translators: %d = number of changes */ 767 _n('%d change', '%d changes', $ai4seo_this_changes_count, 'ai-for-seo'), 768 esc_html($ai4seo_this_changes_count) 769 ) . ")</span>"; 765 770 766 771 // Caret icons - first entry expanded, others collapsed -
ai-for-seo/trunk/includes/pages/help.php
r3399672 r3408847 224 224 $ai4seo_this_accordion_content .= "<p><b>6.2.</b> " . __("<b>Settings -> Media attributes</b>: In this section you can change the settings for the media attributes generated by the *AI for SEO* plugin. This will affect media files.", "ai-for-seo") . "</p>"; 225 225 $ai4seo_this_accordion_content .= "<p><b>6.3.</b> " . __("<b>Settings -> User Management</b>: Here you can select the user groups that should be able to manage the *AI for SEO* plugin.", "ai-for-seo") . "</p>"; 226 $ai4seo_this_accordion_content .= "<p><b>6.4.</b> " . __("<b>Settings -> Troubleshooting </b>: In this section, you can define the duration of a single SEO Autopilot run. Adjusting this setting may help you process your site's content more efficiently in certain cases.", "ai-for-seo") . "</p>";226 $ai4seo_this_accordion_content .= "<p><b>6.4.</b> " . __("<b>Settings -> Troubleshooting -> SEO Autopilot (Bulk Generation) Duration</b>: In this section, you can define the duration of a single SEO Autopilot run. Adjusting this setting may help you process your site's content more efficiently in certain cases.", "ai-for-seo") . "</p>"; 227 227 228 228 // Content tabes … … 236 236 // Help & contact 237 237 $ai4seo_this_accordion_content .= "<p><b>11.</b> " . __("<b>Help</b>: You should also check out the \"Help\" page of the plugin. On this page we put together several helpful tips and tutorials for you. In addition you will find some troubleshooting functions as well as ways to get in touch with us.", "ai-for-seo") . "</p>"; 238 $ai4seo_this_accordion_content .= "<p><b>11.1.</b> " . __("<b>Help -> Troubleshooting</b>: In this section you have the option to reset some or all of the plugin date. P LEASE make sure to use these functions with caution. Feel free to reach prior to using these functions if you have any questions about them.", "ai-for-seo") . "</p>";238 $ai4seo_this_accordion_content .= "<p><b>11.1.</b> " . __("<b>Help -> Troubleshooting</b>: In this section you have the option to reset some or all of the plugin date. Please make sure to use these functions with CAUTION. Feel free to reach prior to using these functions if you have any questions about them.", "ai-for-seo") . "</p>"; 239 239 $ai4seo_this_accordion_content .= "<img src='" . esc_url(ai4seo_get_assets_images_url("help-screenshots/first-steps-7.jpg")) . "' style='width: 100%;' />"; 240 240 $ai4seo_this_accordion_content .= "<p><b>12.</b> " . __("<b>Contact us</b>: If you have any questions, suggestions, need further support or require a particularly large number of Credits, feel free to contact us via Help > Contact.", "ai-for-seo") . "</p>"; … … 439 439 echo ai4seo_wp_kses(ai4seo_get_accordion_element("> " . esc_html__("How can I use placeholders in metadata or media prefixes and suffixes?", "ai-for-seo"), $ai4seo_this_accordion_content)); 440 440 441 $ai4seo_this_accordion_content = __("Open *AI for SEO* > Settings and locate the \"Active Post Types\" checkboxes in the Metadata section. Uncheck any post types you want to hide. The plugin keeps their existing metadata untouched but removes them from dashboards, widgets,and SEO Autopilot queues until you re-enable them.", "ai-for-seo");442 echo ai4seo_wp_kses(ai4seo_get_accordion_element("> " . esc_html__("How do I hide certain post types from *AI for SEO* dashboards?", "ai-for-seo"), $ai4seo_this_accordion_content));441 $ai4seo_this_accordion_content = __("Open *AI for SEO* > Settings and locate the \"Active Post Types\" checkboxes in the Metadata section. Uncheck any post types you want to hide. The plugin keeps their existing metadata untouched but removes them from the dashboard, menu and SEO Autopilot queues until you re-enable them.", "ai-for-seo"); 442 echo ai4seo_wp_kses(ai4seo_get_accordion_element("> " . esc_html__("How do I hide certain post types from the *AI for SEO* dashboard?", "ai-for-seo"), $ai4seo_this_accordion_content)); 443 443 444 444 $ai4seo_this_accordion_content = __("Including image metadata helps search engines better interpret your images, potentially boosting your rankings in image search results and the overall quality of your website. In addition, it also shows your commitment to accessibility, catering to a wider audience and complying with accessibility standards.", "ai-for-seo"); … … 605 605 echo ai4seo_wp_kses(ai4seo_get_accordion_element("> " . esc_html__("Can I choose which media attributes the plugin should generate?", "ai-for-seo"), $ai4seo_this_accordion_content)); 606 606 607 $ai4seo_this_accordion_content = __("You can find the 'Retry all failed' button in any content pages (e.g. Pages, Posts or media) . This allows you to retry all failed metadata generations with a single click, saving you time and effort. Please note that this button will only be displayed if at least one error has occurred.", "ai-for-seo");607 $ai4seo_this_accordion_content = __("You can find the 'Retry all failed' button in any content pages (e.g. Pages, Posts or media) and on your AI for SEO dashboard. This allows you to retry all failed metadata generations with a single click, saving you time and effort. Please note that this button will only be displayed if at least one error has occurred.", "ai-for-seo"); 608 608 echo ai4seo_wp_kses(ai4seo_get_accordion_element("> " . esc_html__("How can I retry all failed metadata generations with just one click?", "ai-for-seo"), $ai4seo_this_accordion_content)); 609 609 … … 690 690 691 691 $ai4seo_this_accordion_content = sprintf( 692 __("Yes, the free plan provides you with %u Credits, allowing you to experiment with AI-generated SEO content without any cost. In addition, we provide you with %u free Credits every day if your balance falls below %s Credits. ", "ai-for-seo"),692 __("Yes, the free plan provides you with %u Credits, allowing you to experiment with AI-generated SEO content without any cost. In addition, we provide you with %u free Credits every day if your balance falls below %s Credits. However it does not include advanced features available in paid subscriptions.", "ai-for-seo"), 693 693 esc_html($ai4seo_free_plan_credits), 694 694 esc_html(AI4SEO_DAILY_FREE_CREDITS_AMOUNT), … … 698 698 699 699 $ai4seo_this_accordion_content = sprintf( 700 "- " . __("The Basic subscription grants you %u Credits per month, suitable for smaller websites or blogs. It covers approximately %u focus keyphrases, meta tags, or image attributes per month, depending on your selected settings. It also features improved entity recognition in images.", "ai-for-seo") . "<br />",700 "- " . __("The Basic subscription grants you %u Credits per month, suitable for smaller websites or blogs. It covers approximately %u focus keyphrases, meta tags, or image attributes per month, depending on your selected settings. Existing data can be used as reference for new data generations.", "ai-for-seo") . "<br />", 701 701 esc_html($ai4seo_s_plan_credits), 702 702 esc_html($ai4seo_s_plan_credits), … … 716 716 $ai4seo_this_accordion_content = __("You should choose a subscription based on the number of Credits and additional features you need for generating SEO content:", "ai-for-seo") . "<br />"; 717 717 $ai4seo_this_accordion_content .= sprintf( 718 "- " . __("The free plan is great for experimentation. It covers approximately %u focus keyphrases, meta tags, or image attributes per month, depending on your selected settings. ", "ai-for-seo") . "<br />",718 "- " . __("The free plan is great for experimentation. It covers approximately %u focus keyphrases, meta tags, or image attributes per month, depending on your selected settings. However it does not include advanced features available in paid subscriptions.", "ai-for-seo") . "<br />", 719 719 esc_html($ai4seo_free_plan_credits), 720 720 ); 721 721 $ai4seo_this_accordion_content .= sprintf( 722 "- " . __("The Basic subscription is suitable for smaller websites or blogs. It covers approximately %u focus keyphrases, meta tags, or image attributes per month, depending on your selected settings. It also features improved entity recognition in images.", "ai-for-seo") . "<br />",722 "- " . __("The Basic subscription is suitable for smaller websites or blogs. It covers approximately %u focus keyphrases, meta tags, or image attributes per month, depending on your selected settings. Existing data can be used as reference for new data generations.", "ai-for-seo") . "<br />", 723 723 esc_html($ai4seo_s_plan_credits), 724 724 ); … … 943 943 echo "</label>"; 944 944 945 $ai4seo_disable_heavy_db_operations_description = __("Temporarily stop heavy database refresh operations to reduce load while debugging. Coverage statistics and summaries will stop updating, and the plugin shows a warning until you turn this off again.", "ai-for-seo");946 $ai4seo_disable_heavy_db_operations_description .= "<br /><br />" . __("Only enable this when investigating issues and re-enable it afterwards so data stays up to date.", "ai-for-seo");945 $ai4seo_disable_heavy_db_operations_description = __("Temporarily stop heavy database refresh operations to reduce load. Coverage statistics and summaries will stop updating, and the plugin shows a warning until you turn this off again.", "ai-for-seo"); 946 $ai4seo_disable_heavy_db_operations_description .= "<br /><br />" . __("Only enable this when investigating issues and disable it afterwards so data stays up to date.", "ai-for-seo"); 947 947 948 948 echo "<p class='ai4seo-form-item-description'>"; -
ai-for-seo/trunk/includes/pages/settings.php
r3395515 r3408847 180 180 } else { 181 181 echo "<p class='ai4seo-form-item-description'>"; 182 echo esc_html__("No publicpost types detected.", "ai-for-seo");182 echo esc_html__("No supported post types detected.", "ai-for-seo"); 183 183 echo "</p>"; 184 184 } … … 497 497 echo "<select id='" . esc_attr($ai4seo_this_setting_input_name) . "' name='" . esc_attr($ai4seo_this_setting_input_name) . "' class='ai4seo-select'>"; 498 498 foreach ($ai4seo_this_setting_options as $ai4seo_option_value => $ai4seo_option_label) { 499 $ai4seo_is_selected = ($ai4seo_option_value === $ai4seo_this_setting_input_value) ? " selected='selected'" : ""; 500 echo "<option value='" . esc_attr($ai4seo_option_value) . "'" . $ai4seo_is_selected . ">" . esc_html($ai4seo_option_label) . "</option>"; 499 echo '<option value="' . esc_attr( $ai4seo_option_value ) . '" ' . 500 selected( $ai4seo_option_value, $ai4seo_this_setting_input_value, false ) . '>' . 501 esc_html( $ai4seo_option_label ) . 502 '</option>'; 501 503 } 502 504 echo "</select>"; … … 1118 1120 } 1119 1121 for ($ai4seo_this_duration = 60; $ai4seo_this_duration <= 300; $ai4seo_this_duration += 60) { 1120 echo "<option value='" . esc_attr($ai4seo_this_duration) . "' " . selected($ai4seo_this_setting_input_value, $ai4seo_this_duration, false) . ">" . sprintf(esc_html(_n("%s minute", "%s minutes", ($ai4seo_this_duration / 60), "ai-for-seo")), number_format_i18n($ai4seo_this_duration / 60)) . "</option>";1122 echo "<option value='" . esc_attr($ai4seo_this_duration) . "' " . selected($ai4seo_this_setting_input_value, $ai4seo_this_duration, false) . ">" . sprintf(esc_html(_n("%s minute", "%s minutes", ($ai4seo_this_duration / 60), "ai-for-seo")), esc_html(number_format_i18n($ai4seo_this_duration / 60))) . "</option>"; 1121 1123 } 1122 1124 echo "</select>"; … … 1173 1175 $ai4seo_potential_js_alt_text_setting_hidden_class = $ai4seo_alt_injection_enabled ? '' : ' ai4seo-js-alt-text-setting-hidden'; 1174 1176 1175 echo "<div class='ai4seo-form-item ai4seo-is-advanced-setting {$ai4seo_potential_js_alt_text_setting_hidden_class}' id='ai4seo-js-alt-text-injection-setting'>";1177 echo "<div class='ai4seo-form-item ai4seo-is-advanced-setting" . esc_attr($ai4seo_potential_js_alt_text_setting_hidden_class) . "' id='ai4seo-js-alt-text-injection-setting'>"; 1176 1178 echo "<label for='" . esc_attr($ai4seo_this_setting_input_name) . "'>"; 1177 1179 // new feature bubble # todo: remove bubble after some time … … 1214 1216 echo "<select id='" . esc_attr($ai4seo_this_setting_input_name) . "' name='" . esc_attr($ai4seo_this_setting_input_name) . "'>"; 1215 1217 foreach ($ai4seo_title_injection_options as $ai4seo_option_value => $ai4seo_option_label) { 1216 $ai4seo_is_selected = ($ai4seo_this_setting_input_value === $ai4seo_option_value) ? ' selected="selected"' : '';1217 echo "<option value='" . esc_attr($ai4seo_option_value) . "'" . $ai4seo_is_selected . ">";1218 echo esc_html($ai4seo_option_label);1219 echo "</option>";1218 echo '<option value="' . esc_attr( $ai4seo_option_value ) . '" ' . 1219 selected( $ai4seo_option_value, $ai4seo_this_setting_input_value, false ) . '>' . 1220 esc_html( $ai4seo_option_label ) . 1221 '</option>'; 1220 1222 } 1221 1223 echo "</select>"; … … 1255 1257 echo "<select id='" . esc_attr($ai4seo_this_setting_input_name) . "' name='" . esc_attr($ai4seo_this_setting_input_name) . "'>"; 1256 1258 foreach ($ai4seo_image_upload_method_options as $ai4seo_option_value => $ai4seo_option_label) { 1257 $ai4seo_is_selected = ($ai4seo_this_setting_input_value === $ai4seo_option_value) ? ' selected="selected"' : '';1258 echo "<option value='" . esc_attr($ai4seo_option_value) . "'" . $ai4seo_is_selected . ">";1259 echo esc_html($ai4seo_option_label);1260 echo "</option>";1259 echo '<option value="' . esc_attr( $ai4seo_option_value ) . '" ' . 1260 selected( $ai4seo_option_value, $ai4seo_this_setting_input_value, false ) . '>' . 1261 esc_html( $ai4seo_option_label ) . 1262 '</option>'; 1261 1263 } 1262 1264 echo "</select>"; -
ai-for-seo/trunk/readme.txt
r3399672 r3408847 1 === AI for SEO – Bulk Generate Focus Keyphrase , Metadata, Alt Text (SEO Autopilot) ===1 === AI for SEO – Bulk Generate Focus Keyphrases, Metadata, Alt Text (SEO Autopilot) === 2 2 Tags: seo, ai, google search console, alt text, bulk 3 3 Contributors: spacecodes … … 5 5 Requires at least: 4.7 6 6 Tested up to: 6.8.3 7 Stable tag: 2.2. 27 Stable tag: 2.2.3 8 8 Requires PHP: 7.4 9 9 License: GPLv2 or later (or compatible) 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 12 Lightweight SEO Autopilot compatible with Yoast SEO, RankMath, SEOPress, WooCommerce etc. to generate Keyphrase, meta tags, alt text and more in bulk.12 Lightweight SEO Autopilot that works with Yoast SEO, Rank Math, SEOPress, WooCommerce etc. to bulk-generate keyphrases, meta tags, alt text and more. 13 13 14 14 == Description == … … 184 184 Yes. In Settings, you can restrict access by user role. Agencies can also use Incognito and White-Label options in *AI for SEO* > Account. 185 185 186 = How do I hide certain post types from *AI for SEO* dashboards? =187 Go to *AI for SEO* > Settings and look for "Active Post Types." Uncheck any content types you want to hide. The plugin will keep their metadata untouched but removes them from dashboards, widgets, and SEO Autopilot queues until you re-enable them.188 189 186 = Is AI-generated SEO-content harmful to my SEO-ranking? = 190 187 Currently, Google's stance on the use of AI or automation in content creation is generally permissive, as indicated in a Google Developers blog post from February 2023. They state that appropriate use of AI or automation is not against their guidelines. More information can be found at [https://developers.google.com/search/blog/2023/02/google-search-and-ai-content](https://developers.google.com/search/blog/2023/02/google-search-and-ai-content). … … 200 197 201 198 == Changelog == 199 200 = 2.2.3 = 201 * Bug Fixes & Maintenance: Fixed 7 minor bugs and implemented 2 usability improvements. 202 202 203 203 = 2.2.2 =
Note: See TracChangeset
for help on using the changeset viewer.