Plugin Directory

Changeset 3408847


Ignore:
Timestamp:
12/03/2025 06:15:52 AM (4 months ago)
Author:
spacecodes
Message:

2.2.3

  • Bug Fixes & Maintenance: Fixed 7 minor bugs and implemented 2 usability improvements.
Location:
ai-for-seo
Files:
81 added
15 edited

Legend:

Unmodified
Added
Removed
  • ai-for-seo/trunk/ai-for-seo.php

    r3399672 r3408847  
    44Plugin URI: https://aiforseo.ai
    55Description: One-Click SEO solution. "AI for SEO" helps your website to rank higher in Web Search results.
    6 Version: 2.2.2
     6Version: 2.2.3
    77Author: spacecodes
    88Author URI: https://spa.ce.codes
     
    1616}
    1717
     18if(isset($_GET['deactivate-ai-for-seo'])) {
     19    deactivate_plugins( plugin_basename( __FILE__ ) );
     20    exit;
     21}
     22
     23
    1824// ___________________________________________________________________________________________ \\
    1925// === CONSTANTS AND VARIABLES =============================================================== \\
    2026// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
    2127
    22 const AI4SEO_PLUGIN_VERSION_NUMBER = "2.2.2";
     28const AI4SEO_PLUGIN_VERSION_NUMBER = "2.2.3";
    2329const AI4SEO_PLUGIN_NAME = "AI for SEO";
    2430const AI4SEO_PLUGIN_DESCRIPTION = 'One-Click SEO solution. "AI for SEO" helps your website to rank higher in Web Search results.';
     
    6874const AI4SEO_SEMAPHORE_POLL_INTERVAL_SECONDS = .1; // .1 seconds
    6975const 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
     76const AI4SEO_POST_TABLE_ANALYSIS_BATCH_SIZE = 5000; // number of posts to analyze per batch
     77const AI4SEO_POST_TABLE_ANALYSIS_MAX_EXECUTION_TIME = 4; // maximum execution time in seconds per batch
     78const AI4SEO_POST_TABLE_ANALYSIS_SLEEP_BETWEEN_RUNS = 100000; // microseconds to sleep between runs
     79const AI4SEO_POST_TABLE_ANALYSIS_PROCESSING_TIMEOUT = 30; // seconds
    7280
    7381const AI4SEO_CRON_JOBS_ENABLED = true; # set to true to enable cron jobs, false to disable them
     
    101109function ai4seo_get_change_log(): array {
    102110    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        ],
    103119        [
    104120            'date' => 'November 20th, 2025',
     
    888904
    889905$ai4seo_settings = AI4SEO_DEFAULT_SETTINGS;
     906$ai4seo_are_settings_initialized = false;
    890907
    891908$ai4seo_fallback_allowed_user_roles = array("administrator" => "Administrator");
     
    977994$ai4seo_scripts_version_number = isset($_GET["ai4seo_debug_uncached_assets"]) && $_GET["ai4seo_debug_uncached_assets"] ? time() : AI4SEO_PLUGIN_VERSION_NUMBER;
    978995$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;
    979997
    980998// used to store various details about all supported metadata fields to use it on many places throughout the plugin
     
    12731291        "select" => array(
    12741292            "id" => array(),
     1293            "name" => array(),
    12751294            "class" => array(),
    12761295            "style" => array(),
     
    13001319        "textarea" => array(
    13011320            "id" => array(),
     1321            "name" => array(),
    13021322            "class" => array(),
    13031323            "style" => array(),
     
    13251345        ),
    13261346        "em" => array(),
     1347        "form" => array(
     1348            "id" => array(),
     1349            "class" => array(),
     1350            "style" => array(),
     1351            "method" => array(),
     1352            "action" => array(),
     1353        ),
    13271354    );
    13281355
     
    14211448);
    14221449
    1423 // the robhub api communicator is used to communicate with the robhub api which handles all the ai stuff
     1450// the robhub api communicator is used to communicate with the robhub api which handles all the AI operations
    14241451$ai4seo_robhub_api = null;
    1425 $ai4seo_are_settings_currently_being_initialized = false;
    1426 $ai4seo_are_settings_initialized = false;
    14271452
    14281453
     
    14371462if (wp_doing_cron()) {
    14381463    // init cron jobs
    1439     add_action("init", "ai4seo_init_robhub_api", 9);
    14401464    add_action("init", "ai4seo_init_cron_jobs", 10);
    14411465
     
    15741598    // Make sure that the user is allowed to use this plugin
    15751599    if (!ai4seo_can_manage_this_plugin()) {
     1600        return;
     1601    }
     1602
     1603    if (!ai4seo_singleton(__FUNCTION__)) {
    15761604        return;
    15771605    }
     
    16271655    // ADMIN AREA ONLY (REST OF INIT CODE FROM HERE) ->
    16281656    // 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
    16371678        return;
    16381679    }
     
    16431684    // Make sure that the user is allowed to use this plugin
    16441685    if (!ai4seo_can_manage_this_plugin()) {
     1686        return;
     1687    }
     1688
     1689    if (!ai4seo_singleton(__FUNCTION__)) {
    16451690        return;
    16461691    }
     
    17021747    global $ai4seo_settings;
    17031748    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__);
    17081752        return;
    17091753    }
    17101754
    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;
    17391770}
    17401771
     
    17421773
    17431774function 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
    17441781    // Read settings from database
    17451782    $settings = ai4seo_get_option(AI4SEO_SETTINGS_OPTION_NAME);
     
    17761813 */
    17771814function ai4seo_on_activation() {
     1815    if (!ai4seo_singleton(__FUNCTION__)) {
     1816        return;
     1817    }
     1818
    17781819    // set AI4SEO_ENVIRONMENTAL_VARIABLE_PLUGIN_ACTIVATION_TIME
    17791820    if (!ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_PLUGIN_ACTIVATION_TIME)) {
     
    17921833 */
    17931834function ai4seo_on_deactivation() {
     1835    if (!ai4seo_singleton(__FUNCTION__)) {
     1836        return;
     1837    }
     1838
    17941839    // un schedule all cron jobs
    17951840    ai4seo_un_schedule_cron_jobs();
     
    18241869
    18251870function 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
    18261877    if (isset($_REQUEST["ai4seo_debug_bypass_incognito_mode"]) && $_REQUEST["ai4seo_debug_bypass_incognito_mode"]) {
    18271878        // If the debug bypass parameter is set, we can bypass the incognito mode
     
    18481899 */
    18491900function ai4seo_check_and_handle_plugin_update() {
     1901    if (!ai4seo_singleton(__FUNCTION__)) {
     1902        return;
     1903    }
     1904
    18501905    $last_known_plugin_version = strval(ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_KNOWN_PLUGIN_VERSION));
    18511906
     
    18891944 */
    18901945function ai4seo_tidy_up(string $last_known_plugin_version = AI4SEO_PLUGIN_VERSION_NUMBER) {
     1946    if (!ai4seo_singleton(__FUNCTION__)) {
     1947        return;
     1948    }
     1949
    18911950    // reestablish cron jobs
    18921951    ai4seo_un_schedule_cron_jobs();
     
    22252284 */
    22262285function 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) {
    22282295        return;
    22292296    }
     
    22442311    // schedule cron jobs if not already scheduled
    22452312    ai4seo_schedule_cron_jobs();
    2246 }
    2247 
    2248 // =========================================================================================== \\
    2249 
    2250 /**
    2251  * Function to init robhub-api-communicator
    2252  * @return bool true if the class is found, false if not
    2253 */
    2254 function ai4seo_init_robhub_api(): bool {
    2255     global $ai4seo_robhub_api;
    2256 
    2257     // Include RobHubApiCommunicator class
    2258     include_once(ai4seo_get_includes_api_path("class-robhub-api-communicator.php"));
    2259 
    2260     // return false if the class is not found
    2261     if (!class_exists("Ai4Seo_RobHubApiCommunicator")) {
    2262         return false;
    2263     }
    2264 
    2265     // check if $ai4seo_robhub_api_communicator is already set
    2266     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;
    22812313}
    22822314
     
    22952327 */
    22962328function ai4seo_filter_admin_title(string $admin_title, string $title ): string {
     2329    if (!ai4seo_singleton(__FUNCTION__)) {
     2330        return false;
     2331    }
     2332
    22972333    if ( ! ai4seo_is_user_inside_our_plugin_admin_pages() ) {
    22982334        return $admin_title;
     
    23432379 */
    23442380function 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
    23452386    // Static pages.
    23462387    $dashboard_slug = str_replace( '?page=', '', ai4seo_get_subpage_url( 'dashboard', array(), false ) ); // typically 'ai-for-seo'
     
    23982439 */
    23992440function ai4seo_add_menu_entries() {
     2441    if (!ai4seo_singleton(__FUNCTION__)) {
     2442        return;
     2443    }
     2444
    24002445    $svg_tags = ai4seo_get_svg_tags();
    24012446
     
    24082453    // Top-level title with notification bubble.
    24092454    $menu_title        = AI4SEO_PLUGIN_NAME;
    2410     $notification_count = (int) ai4seo_get_num_unread_notification();
     2455    $notification_count = ai4seo_get_num_unread_notification();
    24112456
    24122457    if ( $notification_count > 0 ) {
     
    25002545 */
    25012546function ai4seo_mark_parent_menu_active(?string $parent_file ): ?string {
     2547    if (!ai4seo_singleton(__FUNCTION__)) {
     2548        return "";
     2549    }
     2550
    25022551    if ( ai4seo_is_user_inside_our_plugin_admin_pages() ) {
    25032552        $parent_file = AI4SEO_PLUGIN_IDENTIFIER;
     
    25162565 */
    25172566function ai4seo_mark_submenu_active(?string $submenu_file): ?string {
     2567    if (!ai4seo_singleton(__FUNCTION__)) {
     2568        return '';
     2569    }
     2570
    25182571    if ( ai4seo_is_user_inside_our_plugin_admin_pages() ) {
    25192572        // Central registry for labels and slugs.
     
    25392592*/
    25402593function ai4seo_include_menu_frame_file() {
     2594    if (!ai4seo_singleton(__FUNCTION__)) {
     2595        return;
     2596    }
     2597
    25412598    include_once(ai4seo_get_plugin_dir_path("includes/menu-frame.php"));
    25422599}
     
    25492606 */
    25502607function ai4seo_include_modal_schemas_file() {
     2608    if (!ai4seo_singleton(__FUNCTION__)) {
     2609        return;
     2610    }
     2611
    25512612    include_once(ai4seo_get_includes_modal_schemas_path("autoload-modal-schemas.php"));
    25522613}
     
    25612622        return;
    25622623    }
    2563 
    25642624
    25652625    // check if we are outside the admin area
     
    26292689    global $ai4seo_scripts_version_number;
    26302690
     2691    if (!ai4seo_singleton(__FUNCTION__)) {
     2692        return;
     2693    }
     2694
    26312695    // === INITIALISATIONS ====================================================== \\
    26322696
    2633     $current_post_id                            = ai4seo_get_post_id();
     2697    $current_post_id                            = ai4seo_get_current_post_id();
    26342698    $ajax_nonce                                 = wp_create_nonce( AI4SEO_GLOBAL_NONCE_IDENTIFIER );
    26352699    $site_url                                   = site_url();
     
    26942758function ai4seo_add_metadata_editor_column_to_posts_table(array $columns): array {
    26952759    // 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();
    26982763    }
    26992764   
     
    27112776*/
    27122777function 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
    27132783    if ($column_name == AI4SEO_PLUGIN_IDENTIFIER) {
    27142784        echo ai4seo_wp_kses(ai4seo_get_edit_metadata_button($post_id));
     
    28602930 */
    28612931function 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
    28622936    // check if we are on a singular
    28632937    if (!is_singular()) {
     
    28662940
    28672941    // Define variable for the page- or post-id
    2868     $post_id = ai4seo_get_post_id();
     2942    $post_id = ai4seo_get_current_post_id();
    28692943
    28702944    // Stop function if no page- or post-id is defined
     
    32023276
    32033277function ai4seo_inject_image_attributes_for_gutenberg( $content, $block ) {
     3278    if (!ai4seo_singleton(__FUNCTION__)) {
     3279        return $content;
     3280    }
     3281
    32043282    // Skip admin, feeds, REST API and AJAX requests
    32053283    if (
     
    32223300 */
    32233301function ai4seo_inject_image_attributes_into_html($content) {
     3302    if (!ai4seo_singleton(__FUNCTION__)) {
     3303        return $content;
     3304    }
     3305
    32243306    $alt_enabled   = ai4seo_get_setting( AI4SEO_SETTING_ENABLE_RENDER_LEVEL_ALT_TEXT_INJECTION );
    32253307    $title_injection_mode = ai4seo_get_setting( AI4SEO_SETTING_IMAGE_TITLE_INJECTION_MODE );
     
    34163498
    34173499function ai4seo_filter_wp_image_attrs( $attr, $attachment, $size ) {
     3500    if (!ai4seo_singleton(__FUNCTION__)) {
     3501        return $attr;
     3502    }
     3503
    34183504    if ( empty( $attr['alt'] ) ) {
    34193505        $alt = get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true );
     
    34383524 */
    34393525function 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
    34403531    if (isset($our_metadata[$metadata_identifier]) && $our_metadata[$metadata_identifier]) {
    34413532        return;
     
    37743865 * @return string $text with placeholders replaced
    37753866 */
    3776 function ai4seo_replace_white_label_placeholders($text) {
     3867function ai4seo_replace_white_label_placeholders(string $text): string {
    37773868    $text = str_replace(
    37783869        ["{NAME}", "{VERSION}", "{WEBSITE}"],
     
    38523943 */
    38533944function 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
    38543950    $replacements = ai4seo_get_common_placeholder_replacements();
    38553951
     
    40764172 */
    40774173function 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
    40784179    // Define variable for the plugin-file
    40794180    $plugin_file = ai4seo_get_plugin_basename();
     
    41734274 */
    41744275function 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
    41754281    if (!defined('AI4SEO_METADATA_DETAILS')) {
    41764282        return array();
     
    42824388    // Make sure that the user is allowed to use this plugin
    42834389    if (!ai4seo_can_manage_this_plugin()) {
     4390        return;
     4391    }
     4392
     4393    if (!ai4seo_singleton(__FUNCTION__)) {
    42844394        return;
    42854395    }
     
    43234433    }
    43244434
     4435    if (!ai4seo_singleton(__FUNCTION__)) {
     4436        return;
     4437    }
     4438
    43254439    // check action
    43264440    $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( $_REQUEST['action'] ) : '';
     
    43734487    global $wpdb;
    43744488
     4489    if (!ai4seo_singleton(__FUNCTION__)) {
     4490        return;
     4491    }
     4492
    43754493    // compare cached and real count of posts
    43764494    $cached_num_posts_table_entries = (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_NUM_POSTS_TABLE_ENTRIES);
     
    44054523 */
    44064524function ai4seo_check_for_robhub_account_sync(): void {
     4525    if (!ai4seo_singleton(__FUNCTION__)) {
     4526        return;
     4527    }
     4528
    44074529    $active_subpage = ai4seo_get_active_subpage();
    44084530    $active_subpage_is_dashboard = $active_subpage == "dashboard";
     
    44614583    // use singleton to only call this function once
    44624584    if (!ai4seo_singleton(__FUNCTION__)) {
    4463         return true;
     4585        return false;
    44644586    }
    44654587
     
    45804702function ai4seo_get_all_possible_user_roles(): array {
    45814703    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    }
    45824709
    45834710    if (!function_exists('wp_roles')) {
     
    47154842    global $ai4seo_user_has_at_least_plan;
    47164843
     4844    if (ai4seo_prevent_loops(__FUNCTION__)) {
     4845        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     4846        return false;
     4847    }
     4848
    47174849    if (isset($ai4seo_user_has_at_least_plan[$required_plan])) {
    47184850        return $ai4seo_user_has_at_least_plan[$required_plan];
     
    47384870
    47394871function 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
    47404877    $css_class = 'ai4seo-plan-badge';
    47414878    $user_has_at_least_this_plan = ai4seo_user_has_at_least_plan($plan);
     
    48014938/**
    48024939 * Function to return the robhub api communicator
    4803  * @return Ai4Seo_RobHubApiCommunicator The robhub api communicator
    4804  */
    4805 function ai4seo_robhub_api(): Ai4Seo_RobHubApiCommunicator {
     4940 * @return Ai4Seo_RobHubApiCommunicator|null The robhub api communicator
     4941 */
     4942function ai4seo_robhub_api($init_only = false): ?Ai4Seo_RobHubApiCommunicator {
    48064943    global $ai4seo_robhub_api;
    48074944
     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
    48084951    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;
    48104981    }
    48114982
     
    48254996 */
    48264997function 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
    48275003    if (is_array($data)) {
    48285004        $sanitized_data = array();
     
    48625038    global $ai4seo_can_manage_this_plugin;
    48635039
     5040    if (ai4seo_prevent_loops(__FUNCTION__)) {
     5041        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     5042        return false;
     5043    }
     5044
    48645045    // use cache if available
    48655046    if ($ai4seo_can_manage_this_plugin !== null) {
     
    49615142 * Function to prevent recursive loops
    49625143 * @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)
    49655146 * @return bool True if the loop should be prevented, false otherwise
    49665147 */
    4967 function ai4seo_prevent_loops($function_name, $max_depth = 0, $max_calls = 999) {
     5148function ai4seo_prevent_loops(string $function_name, int $max_depth = 1, int $max_calls = 99999): bool {
    49685149    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    }
    49695158   
    49705159    // Initialize call count if not exists
     
    49795168    if ($call_counts[$function_name] > $max_calls) {
    49805169        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;
    49815175    }
    49825176   
     
    49935187   
    49945188    // 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 1 recursive 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
    49985192   
    4999     if ($depth > $max_depth + 1) {
     5193    if ($depth > $max_depth) {
    50005194        return true;
    50015195    }
    50025196   
    50035197    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 */
     5207function ai4seo_singleton($id): bool {
     5208    return !ai4seo_prevent_loops($id, 1, 1);
    50045209}
    50055210
     
    50285233
    50295234    return $text;
    5030 }
    5031 
    5032 // =========================================================================================== \\
    5033 
    5034 /**
    5035  * Function to simulate a singleton (only one call per function per id)
    5036  * @param $id
    5037  * @return bool
    5038  */
    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     }
    50475235}
    50485236
     
    51635351 */
    51645352function 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
    51655358    // Ensure the input length is within the limits.
    51665359    if ( ai4seo_mb_strlen( $input ) <= $soft_cap ) {
     
    52225415 */
    52235416function 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
    52245422    $sub_page = sanitize_key($sub_page);
    52255423
     
    52925490function ai4seo_get_post_type_page_url(string $post_type, int $current_page = 1, array $additional_parameter = array(), bool $return_full_path = true): string {
    52935491    $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    );
    52955498}
    52965499
     
    53315534 */
    53325535function 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
    53335541    $plugin_page = sanitize_key($plugin_page);
    53345542    $active_plugin_page = ai4seo_get_active_subpage();
     
    53705578 */
    53715579function ai4seo_get_active_subpage(): string {
     5580    if (ai4seo_prevent_loops(__FUNCTION__)) {
     5581        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     5582        return '';
     5583    }
     5584
    53725585    if (!ai4seo_is_user_inside_our_plugin_admin_pages()) {
    5373         return "";
     5586        return '';
    53745587    }
    53755588
     
    53915604 */
    53925605function 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
    53935611    if (!ai4seo_is_user_inside_our_plugin_admin_pages()) {
    53945612        return "";
     
    56225840        'public'   => true,
    56235841    );
     5842
    56245843    $post_types = get_post_types($args, 'objects');
    56255844    $publicly_accessible_post_types = array();
     
    57525971 */
    57535972function 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
    57545978    // Get the current timestamp in WordPress timezone
    57555979    $timezone = ai4seo_get_option('timezone_string');
     
    57876011 */
    57886012function 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
    57896018    $final_format = '';
    57906019
     
    58886117 */
    58896118function 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
    58906124    $timezone_string = '';
    58916125
     
    59396173 */
    59406174function 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
    59416180    // Get the WordPress timezone
    59426181    if ($timezone == 'auto') {
     
    59666205 */
    59676206function ai4seo_deactivate_plugin(): bool {
     6207    if (!ai4seo_singleton(__FUNCTION__)) {
     6208        return false;
     6209    }
     6210
    59686211    // Check if the user has the required permissions
    59696212    if (!current_user_can('activate_plugins')) {
     
    59876230 * @return string The clients ip
    59886231 */
    5989 function ai4seo_get_client_ip() {
     6232function ai4seo_get_client_ip(): string {
    59906233    if (isset($_SERVER['HTTP_CLIENT_IP'])) {
    59916234        $client_ip = sanitize_text_field($_SERVER['HTTP_CLIENT_IP']);
     
    60326275 */
    60336276function ai4seo_get_server_ip(): string {
     6277    if (ai4seo_prevent_loops(__FUNCTION__)) {
     6278        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     6279        return '';
     6280    }
     6281
    60346282    $server_ip_response = ai4seo_file_get_contents('https://api.ipify.org');
    60356283
     
    63236571 */
    63246572function 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
    63256578    if ( empty( $url ) ) {
    63266579        return null;
     
    63456598
    63466599function 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
    63476605    $attachment_post = get_post($attachment_post_id);
    63486606
     
    63946652 * @return string The formatted backtrace message.
    63956653 */
    6396 function ai4seo_get_backtrace_debug_message( $separator = '<br>' ): string {
     6654function ai4seo_get_backtrace_debug_message(string $separator = '<br>' ): string {
    63976655    $backtrace_array = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS );
    63986656    $formatted_backtrace = [];
     
    64356693
    64366694function 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
    64376700    $approximate_credits_needed = ai4seo_get_approximate_credits_needed();
    64386701    $credits_packs = ai4seo_get_credits_packs();
     
    64636726
    64646727function 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
    64656733    $approximate_credits_needed = 0;
    64666734
     
    64876755// =========================================================================================== \\
    64886756
    6489 function ai4seo_normalize_text($text) {
    6490     $text = trim($text);                      // remove leading/trailing whitespace
    6491     $text = preg_replace('/\s+/u', ' ', $text); // normalize all whitespace to a single space
    6492     $text = str_replace("\xC2\xA0", ' ', $text); // replace non-breaking spaces
    6493     $text = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', '', $text); // invisible zero-width spaces
    6494     $text = html_entity_decode($text);
    6495     return stripslashes($text);
    6496 }
    6497 
    6498 // =========================================================================================== \\
    6499 
    65006757function 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
    65016767    // Use wp_safe_remote_get instead of file_get_contents for fetching remote files
    65026768    try {
     
    65826848 */
    65836849function 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
    65846855    // Attempt 1: Standard remote fetch
    65856856    $response = wp_safe_remote_get($url, array(
     
    67327003 * @return string|false     File contents on success, false on failure.
    67337004 */
    6734 function ai4seo_file_get_contents(string $path, $context = null)
    6735 {
     7005function ai4seo_file_get_contents(string $path, $context = null) {
    67367006    if (ai4seo_is_function_usable('file_get_contents')) {
    67377007        try {
     
    68267096function ai4seo_get_option(string $option_name, $default = false ) {
    68277097    global $wpdb;
     7098
     7099    if (ai4seo_prevent_loops(__FUNCTION__, 2)) {
     7100        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     7101        return '';
     7102    }
    68287103
    68297104    if ( ! isset( $wpdb ) || ! $wpdb ) {
     
    68887163function ai4seo_update_option(string $option_name, $option_value, $autoload = null ): bool {
    68897164    global $wpdb;
     7165
     7166    if (ai4seo_prevent_loops(__FUNCTION__, 2)) {
     7167        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     7168        return false;
     7169    }
    68907170
    68917171    if ( ! isset( $wpdb ) || ! $wpdb ) {
     
    70247304    global $wpdb;
    70257305
     7306    if (ai4seo_prevent_loops(__FUNCTION__, 2)) {
     7307        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     7308        return false;
     7309    }
     7310
    70267311    if ( ! isset( $wpdb ) || ! $wpdb ) {
    70277312        return false;
     
    70777362 */
    70787363function 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
    70797369    ai4seo_register_semaphore_shutdown_handler();
    70807370
     
    71187408 */
    71197409function 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
    71207415    $option_key = ai4seo_get_semaphore_option_key( $critical_section_name );
    71217416
     
    71727467 */
    71737468function 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__)) {
    71777470        return;
    71787471    }
    7179 
    7180     $ai4seo_is_semaphore_shutdown_handler_registered = true;
    71817472
    71827473    register_shutdown_function(
     
    72077498 */
    72087499function 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
    72097505    $payload = array(
    72107506        'token'      => (string) $token,
     
    72147510    // Atomic when the option does not yet exist.
    72157511    // Do not autoload.
    7216     return add_option( $option_key, $payload, '', 'no' );
     7512    return aa_option( $option_key, $payload, '', 'no' );
    72177513}
    72187514
     
    72457541 */
    72467542function 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
    72477548    $existing = ai4seo_get_option( $option_key );
    72487549
     
    72887589 * @return int Post ID or 0.
    72897590 */
    7290 function ai4seo_get_post_id( array $args = array() ): int {
     7591function 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
    72917597    $args = wp_parse_args(
    72927598        $args,
     
    73037609    if ( ! empty( $ai4seo_post_context_stack ) ) {
    73047610        $override_id = (int) end( $ai4seo_post_context_stack );
     7611
    73057612        if ( $override_id > 0 ) {
    73067613            /**
     
    74537760    global $wpdb;
    74547761
     7762    if (ai4seo_prevent_loops(__FUNCTION__, 2)) {
     7763        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     7764        return array();
     7765    }
     7766
    74557767    $publicly_accessible_post_types = ai4seo_get_publicly_accessible_post_types();
    74567768
     
    75327844 */
    75337845function 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
    75347851    // Retrieve the post object
    75357852    $post = get_post($post_id);
     
    75627879 * @return false|string The post or page content or false if the post_id is empty
    75637880 */
    7564 function ai4seo_get_combined_post_content(int $post_id = 0, string $editor_identifier = "", $debug = false) {
     7881function 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
    75657887    // Define variables for the current theme and the parent theme
    75667888    $current_theme = wp_get_theme();
     
    75707892    if (empty($post_id)) {
    75717893        // Get post- or page-id
    7572         $post_id = ai4seo_get_post_id();
     7894        $post_id = ai4seo_get_current_post_id();
    75737895    }
    75747896
     
    77378059    global $shortcode_tags;
    77388060
     8061    if (ai4seo_prevent_loops(__FUNCTION__)) {
     8062        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     8063        return;
     8064    }
     8065
    77398066    // workaround for ACF blocks, as content for ACF blocks are defined inside <!-- wp:acf/... --> tags
    77408067    if (ai4seo_is_acf_content($content)) {
     
    78248151
    78258152function 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
    78268158    // ADD POST ENTRY CONTEXT
    78278159    $context = "POST/PAGE CONTEXT: ";
     
    81128444    }
    81138445
     8446    if (!ai4seo_singleton(__FUNCTION__)) {
     8447        return;
     8448    }
     8449
    81148450    // Insert post id into option to be analyzed AI4SEO_POSTS_TO_BE_ANALYZED_OPTION_NAME
    81158451    ai4seo_add_post_ids_to_option(AI4SEO_POSTS_TO_BE_ANALYZED_OPTION_NAME, $post_id);
     
    81248460 */
    81258461function 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
    81268467    if (!is_numeric($post_id)) {
    81278468        return;
     
    81718512    $cache_key   = 'ai4seo_url_exposed_taxonomies_v1';
    81728513    $cached      = get_transient( $cache_key );
     8514
    81738515    if ( is_array( $cached ) ) {
    81748516        return $cached;
     8517    }
     8518
     8519    if (ai4seo_prevent_loops(__FUNCTION__)) {
     8520        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     8521        return array();
    81758522    }
    81768523
     
    87219068    // Array of language codes and their corresponding names
    87229069    $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'),
    87689115    );
    87699116
     
    87789125 * @return string
    87799126 */
    8780 function ai4seo_get_chart_legend_translation($legend_identifier): string {
     9127function ai4seo_get_chart_legend_translation(string $legend_identifier): string {
    87819128    $legend_identifier_original = $legend_identifier;
    87829129    $legend_identifier = strtolower($legend_identifier);
     
    87849131    switch ($legend_identifier) {
    87859132        case "done":
    8786             return __("Done", "ai-for-seo");
     9133            return esc_html__("Done", "ai-for-seo");
    87879134        case "processing":
    8788             return __("Processing", "ai-for-seo");
     9135            return esc_html__("Processing", "ai-for-seo");
    87899136        case "missing":
    8790             return __("Missing SEO / Pending", "ai-for-seo");
     9137            return esc_html__("Missing SEO / Pending", "ai-for-seo");
    87919138        case "failed":
    8792             return __("Failed (please check details)", "ai-for-seo");
     9139            return esc_html__("Failed (please check details)", "ai-for-seo");
    87939140        default:
    87949141            return $legend_identifier_original;
     
    88009147function ai4seo_get_select_all_checkbox($target_checkbox_name, $label = "auto"): string {
    88019148    if ($label === "auto") {
    8802         $label = __("Select All / Unselect All", "ai-for-seo");
     9149        $label = esc_html__("Select All / Unselect All", "ai-for-seo");
    88039150    }
    88049151
     
    88489195 */
    88499196function 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
    88509202    $accepted_time = ai4seo_read_environmental_variable($environmental_variable_name);
    88519203
     
    88559207        $readable_accepted_time = ai4seo_format_unix_timestamp($accepted_time);
    88569208        $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);
    88589210    } else {
    88599211        //$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");
    88619213    }
    88629214    return $content;
     
    88719223 */
    88729224function 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
    88739230    $ai4seo_seo_autopilot_start_time = (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_SEO_AUTOPILOT_SET_UP_TIME);
    88749231
     
    89859342        $voucher_code_output .= "</div>";
    89869343    $voucher_code_output .= "</div>";
     9344
    89879345    return $voucher_code_output;
    89889346}
     
    89979355 */
    89989356function ai4seo_get_dashicon_tag(string $icon_name, string $css_class = ""): string {
    8999 
    9000 
    90019357    return '<i class="dashicons dashicons-' . esc_attr($icon_name) . ' ' . esc_attr($css_class) . '"></i>';
    90029358}
     
    90419397 */
    90429398function 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
    90439404    $active_supported_third_party_seo_plugin_details = array();
    90449405
     
    90629423 */
    90639424function 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
    90649430    $active_supported_third_party_seo_plugins = ai4seo_get_active_third_party_seo_plugin_details();
    90659431
     
    90929458    global $wpdb;
    90939459
     9460    if (ai4seo_prevent_loops(__FUNCTION__)) {
     9461        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     9462        return array();
     9463    }
     9464
    90949465    if (!$post_ids) {
    90959466        return array();
     
    91779548    global $wpdb;
    91789549
     9550    if (ai4seo_prevent_loops(__FUNCTION__)) {
     9551        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     9552        return array();
     9553    }
     9554
    91799555    # todo: make this whole function dynamic
    91809556
     
    92459621function ai4seo_is_plugin_or_theme_active($identifier): bool {
    92469622    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    }
    92479628
    92489629    // try use cache first
     
    94259806 */
    94269807function ai4seo_schedule_cron_jobs() {
     9808    if (!ai4seo_singleton(__FUNCTION__)) {
     9809        return;
     9810    }
     9811
    94279812    // add custom cron schedule for automated metadata generation
    94289813    if (!wp_next_scheduled(AI4SEO_BULK_GENERATION_CRON_JOB_NAME)) {
     
    94439828 */
    94449829function ai4seo_un_schedule_cron_jobs() {
     9830    if (!ai4seo_singleton(__FUNCTION__)) {
     9831        return;
     9832    }
     9833
    94459834    wp_clear_scheduled_hook(AI4SEO_BULK_GENERATION_CRON_JOB_NAME);
    94469835    wp_clear_scheduled_hook(AI4SEO_ANALYSE_PLUGIN_PERFORMANCE_CRON_JOB_NAME);
     
    94559844 */
    94569845function 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
    94579851    // is the cron job enabled?
    94589852    if (!AI4SEO_CRON_JOBS_ENABLED && wp_doing_cron()) {
     
    95199913 */
    95209914function 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
    95219920    if (!wp_doing_cron()) {
    95229921        return false;
     
    95349933    $last_specific_cronjob_calls[$cron_job_name] = $time;
    95359934    ai4seo_update_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_SPECIFIC_CRON_JOB_CALLS, $last_specific_cronjob_calls);
     9935
    95369936    return true;
    95379937}
     
    95459945 */
    95469946function 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
    95479952    if ($cron_job_name) {
    95489953        $cron_job_name = sanitize_key($cron_job_name);
     
    95649969 */
    95659970function 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
    95669976    $all_cronjob_job_status = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_CRON_JOB_STATUS_LIST);
     9977
    95679978    return $all_cronjob_job_status[$cron_job_name] ?? "unknown";
    95689979}
     
    95779988 */
    95789989function 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
    95799995    if (!wp_doing_cron()) {
    95809996        return false;
     
    959910015 */
    960010016function 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
    960110022    $all_cronjob_job_status_time = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_CRON_JOB_STATUS_LAST_UPDATE_TIMES);
    960210023    $all_cronjob_job_status_time[$cron_job_name] = time();
     10024
    960310025    return ai4seo_update_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_CRON_JOB_STATUS_LAST_UPDATE_TIMES, $all_cronjob_job_status_time);
    960410026}
     
    961210034 */
    961310035function 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
    961410041    $all_cronjob_job_status_time = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_CRON_JOB_STATUS_LAST_UPDATE_TIMES);
     10042
    961510043    return $all_cronjob_job_status_time[$cron_job_name] ?? 0;
    961610044}
     
    962310051 */
    962410052function ai4seo_automated_generation_cron_job($debug = false): bool {
     10053    if (!ai4seo_singleton(__FUNCTION__)) {
     10054        return true;
     10055    }
     10056
    962510057    // is the cron job enabled?
    962610058    if (!AI4SEO_CRON_JOBS_ENABLED && wp_doing_cron()) {
     
    979010222 */
    979110223function 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
    979210229    $active_meta_tags = ai4seo_get_active_meta_tags();
    979310230
     
    1020510642    global $ai4seo_allowed_attachment_mime_types;
    1020610643
     10644    if (ai4seo_prevent_loops(__FUNCTION__)) {
     10645        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     10646        return false;
     10647    }
     10648
    1020710649    $active_attachment_attributes = ai4seo_get_active_attachment_attributes();
    1020810650    $supported_attachment_post_types = ai4seo_get_supported_attachment_post_types();
     
    1054210984
    1054310985function 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
    1054410995    $base64_from_image_file_response = ai4seo_get_base64_from_image_file($attachment_url);
    1054510996
     
    1059811049function ai4seo_excavate_post_entries_with_missing_metadata(bool $debug = false): bool {
    1059911050    global $wpdb;
     11051
     11052    if (ai4seo_prevent_loops(__FUNCTION__)) {
     11053        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     11054        return false;
     11055    }
    1060011056
    1060111057    $metadata_credits_costs_per_post = ai4seo_calculate_metadata_credits_cost_per_post();
     
    1076111217    global $ai4seo_allowed_attachment_mime_types;
    1076211218
     11219    if (ai4seo_prevent_loops(__FUNCTION__)) {
     11220        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     11221        return false;
     11222    }
     11223
    1076311224    $supported_attachment_post_types = ai4seo_get_supported_attachment_post_types();
    1076411225
     
    1092611387 */
    1092711388function ai4seo_analyze_plugin_performance(bool $debug = false): bool {
     11389    if (!ai4seo_singleton(__FUNCTION__)) {
     11390        return false;
     11391    }
     11392
    1092811393    // check if disable heavy db operations parameter is set
    1092911394    ai4seo_check_for_disable_heavy_db_operations_parameter();
    10930 
    1093111395    ai4seo_set_cron_job_status(AI4SEO_ANALYSE_PLUGIN_PERFORMANCE_CRON_JOB_NAME, "processing");
    1093211396
     
    1095111415
    1095211416function 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
    1095311422    if ( !isset( $_GET['ai4seo_disable_heavy_db_operations'] ) ) {
    1095411423        return;
     
    1097511444 */
    1097611445function 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
    1097711455    ai4seo_run_with_ignore_user_abort(
    1097811456        'ai4seo_run_posts_table_analysis_task',
     
    1098411462
    1098511463function 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
    1098611475    if (ai4seo_get_setting(AI4SEO_SETTING_DISABLE_HEAVY_DB_OPERATIONS) && (!isset($_GET["ai4seo_debug_posts_table_analysis"]) || !$_GET["ai4seo_debug_posts_table_analysis"] || !$debug)) {
    1098711476        if ($debug) {
     
    1100311492        $posts_table_analysis_start_time = (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_POSTS_TABLE_ANALYSIS_START_TIME, false);
    1100411493        $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
    1100811505
    1100911506        // first, check if the state is "in-progress" and if the last start time was longer ago than $timeout -> restart
     
    1101611513                }
    1101711514
    11018                 error_log("AI4SEO: Posts table analysis timed out -> restarting");
     11515                //error_log("AI4SEO: Posts table analysis timed out -> restarting");
    1101911516            } else {
    1102011517                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>";
    1102211519                }
    1102311520
     
    1106111558    $previous_posts_table_analysis_last_post_id = -1;
    1106211559    $run_counter = 0;
    11063     $max_run_counter = 10;
    1106411560    $is_finished = false;
    1106511561
    1106611562    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) {
    1106811564            $run_counter++;
    1106911565
     
    1111311609            }
    1111411610        }
     11611
     11612        $ai4seo_did_run_post_table_analysis = true;
    1111511613    }
    1111611614
     
    1113211630    global $ai4seo_allowed_attachment_mime_types;
    1113311631
     11632    if (ai4seo_prevent_loops(__FUNCTION__)) {
     11633        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     11634        return true;
     11635    }
     11636
    1113411637    $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    }
    1113511643
    1113611644    // Cursor-based pagination query
     
    1125611764            // check if fully covered
    1125711765            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]++;
    1126211777                }
    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) {
    1127711781                $new_post_ids_by_option[AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME][] = $this_post_id;
    1127811782
     
    1134311847            $this_post_was_generated = in_array($this_post_id, $generated_data_post_ids);
    1134411848            $this_attachment_post_type = 'attachment';
     11849            $is_fully_covered = ($num_fields_covered >= $num_total_attachment_attributes_fields);
    1134511850
    1134611851            // 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]++;
    1135211864                }
    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) {
    1136711868                $new_post_ids_by_option[AI4SEO_MISSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME][] = (int) $this_post_id;
    1136811869
     
    1145211953
    1145311954function 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
    1145411960    // read AI4SEO_GENERATION_STATUS_SUMMARY_OPTION_NAME
    1145511961    $generation_status_summary = ai4seo_get_option( AI4SEO_GENERATION_STATUS_SUMMARY_OPTION_NAME, '{}' );
     
    1151512021
    1151612022function ai4seo_reset_posts_table_analysis() {
     12023    if (ai4seo_prevent_loops(__FUNCTION__)) {
     12024        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     12025        return;
     12026    }
     12027
    1151712028    ai4seo_update_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_POSTS_TABLE_ANALYSIS_LAST_POST_ID, 0, false);
    1151812029    ai4seo_update_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_POSTS_TABLE_ANALYSIS_STATE, "idle", false);
     
    1156312074    $num_missing_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME);
    1156412075    $num_missing_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_MISSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME);
     12076
    1156512077    return array_merge($num_missing_metadata_by_post_type, $num_missing_attachment_attributes);
    1156612078}
     
    1157512087    $num_fully_covered_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME);
    1157612088    $num_fully_covered_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_FULLY_COVERED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME);
     12089
    1157712090    return array_merge($num_fully_covered_metadata_by_post_type, $num_fully_covered_attachment_attributes);
    1157812091}
     
    1158712100    $num_generated_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_GENERATED_METADATA_POST_IDS_OPTION_NAME);
    1158812101    $num_generated_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_GENERATED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME);
     12102
    1158912103    return array_merge($num_generated_metadata_by_post_type, $num_generated_attachment_attributes);
    1159012104}
     
    1159712111 */
    1159812112function 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);
    1161412115
    1161512116    return array_merge($num_finished_metadata_by_post_type, $num_finished_attachment_attributes);
     
    1162612127    $num_failed_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_FAILED_METADATA_POST_IDS_OPTION_NAME);
    1162712128    $num_failed_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_FAILED_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME);
     12129
    1162812130    return array_merge($num_failed_metadata_by_post_type, $num_failed_attachment_attributes);
    1162912131}
     
    1163812140    $num_pending_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_PENDING_METADATA_POST_IDS_OPTION_NAME);
    1163912141    $num_pending_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_PENDING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME);
     12142
    1164012143    return array_merge($num_pending_metadata_by_post_type, $num_pending_attachment_attributes);
    1164112144}
     
    1165012153    $num_processing_metadata_by_post_type = ai4seo_get_generation_status_summary_entry(AI4SEO_PROCESSING_METADATA_POST_IDS_OPTION_NAME);
    1165112154    $num_processing_attachment_attributes = ai4seo_get_generation_status_summary_entry(AI4SEO_PROCESSING_ATTACHMENT_ATTRIBUTES_POST_IDS_OPTION_NAME);
     12155
    1165212156    return array_merge($num_processing_metadata_by_post_type, $num_processing_attachment_attributes);
    1165312157}
     
    1171912223function ai4seo_read_our_plugins_metadata_by_post_ids( array $post_ids ): array {
    1172012224    global $wpdb;
     12225
     12226    if (ai4seo_prevent_loops(__FUNCTION__)) {
     12227        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     12228        return array();
     12229    }
    1172112230
    1172212231    $active_meta_tags = ai4seo_get_active_meta_tags();
     
    1177312282            continue;
    1177412283        }
    11775 
     12284       
    1177612285        foreach ( $this_rows as $this_row ) {
    1177712286            $this_post_id            = absint( $this_row['post_id'] );
     
    1180212311function ai4seo_read_third_party_seo_plugin_metadata_by_post_ids($third_party_plugin_name, array $post_ids): array {
    1180312312    global $wpdb;
     12313
     12314    if (ai4seo_prevent_loops(__FUNCTION__)) {
     12315        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     12316        return array();
     12317    }
    1180412318
    1180512319    // make sure all entries of post_ids are numeric
     
    1207212586 * @return array the metadata by post-ids, using metadata-identifier keys
    1207312587 */
    12074 function ai4seo_read_all_in_one_seo_metadata_by_post_ids($post_ids): array {
     12588function ai4seo_read_all_in_one_seo_metadata_by_post_ids(array $post_ids): array {
    1207512589    // check table "wp_aioseo_posts" for the post id. Columns are "title", "description", "og_title", "og_description", "twitter_title", "twitter_description"
    1207612590    $metadata_identifier_mapping = array(
     
    1214412658 */
    1214512659function 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
    1214612665    // make sure post_ids is not empty
    1214712666    if (empty($post_ids)) {
     
    1224612765 */
    1224712766function 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
    1224812772    if (!defined('AI4SEO_METADATA_DETAILS')) {
    1224912773        return array();
     
    1228112805 */
    1228212806function 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
    1228312812    $active_meta_tags = ai4seo_get_active_meta_tags();
    1228412813
     
    1239012919function ai4seo_read_is_posts_metadata_fully_covered(int $post_id): bool {
    1239112920    $percentage_of_active_metadata_by_post_ids = ai4seo_read_percentage_of_available_metadata_by_post_ids(array($post_id));
     12921
    1239212922    return (($percentage_of_active_metadata_by_post_ids[$post_id] ?? 0) == 100);
    1239312923}
     
    1257313103
    1257413104/**
    12575  * Compares two post content summaries. Returns true if they share a XX% similarity
    12576  * @param $post_content_summary_1 string the first post content summary
    12577  * @param $post_content_summary_2 string the second post content summary
    12578  * @param $min_similarity_percentage int the percentage of similarity
    12579  * @return bool
    12580  */
    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 100
    12583     $min_similarity_percentage = max(0, min(100, $min_similarity_percentage));
    12584 
    12585     // compare the two strings
    12586     $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 percentage
    12590     return ($similarity_percentage >= $min_similarity_percentage);
    12591 }
    12592 
    12593 // =========================================================================================== \\
    12594 
    12595 /**
    1259613105 * Returns the configured maximum length for the given editor field identifier.
    1259713106 *
     
    1265413163
    1265513164    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 string
    12667  */
    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_limit
    12676         );
    12677 
    12678         ai4seo_send_json_error($ai4seo_error_message, 5011221025);
    12679         wp_die();
    12680     }
    12681 
    12682     return $ai4seo_value;
    1268313165}
    1268413166
     
    1271113193function ai4seo_update_active_metadata(int $post_id, array $metadata_updates, bool $overwrite_existing_data = false): bool {
    1271213194    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__);
    1271313200        return false;
    1271413201    }
     
    1278813275 **/
    1278913276function 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
    1279013282    $we_should_not_save_our_own_metadata = false;
    1279113283
     
    1311313605 */
    1311413606function 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
    1311513612    $metadata_generation_language = sanitize_text_field(ai4seo_get_setting(AI4SEO_SETTING_METADATA_GENERATION_LANGUAGE));
    1311613613
     
    1313813635 */
    1313913636function 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
    1314013642    $active_meta_tags = ai4seo_get_setting(AI4SEO_SETTING_ACTIVE_META_TAGS);
    1314113643
     
    1317813680    global $wpdb;
    1317913681
     13682    if (ai4seo_prevent_loops(__FUNCTION__)) {
     13683        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     13684        return array();
     13685    }
     13686
    1318013687    // allow single ID
    1318113688    if ( ! is_array( $attachment_post_ids ) ) {
     
    1320913716    $chunks = array_chunk( $attachment_post_ids, 10000 );
    1321013717    $first_chunk = true;
     13718
    1321113719
    1321213720    // --- TITLE / CAPTION / DESCRIPTION / GUID ----------------------------------------- \\
     
    1325613764    }
    1325713765
     13766
    1325813767    // --- ALT TEXT --------------------------------------------------------------------- \\
     13768
    1325913769    if ( in_array( 'alt-text', $active_attachment_attributes, true ) ) {
    1326013770        $postmeta_table = esc_sql( $wpdb->postmeta );
     
    1333013840 */
    1333113841function 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
    1333213847    // make sure all entries of post_ids are numeric
    1333313848    foreach ($attachment_post_ids as $attachment_post_id) {
     
    1337813893 */
    1337913894function 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
    1338013900    // get the total amount of attachment attributes
    1338113901    $active_attachment_attributes = ai4seo_get_active_attachment_attributes();
     
    1344013960 */
    1344113961function 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
    1344213967    if (!is_numeric($attachment_post_id)) {
    1344313968        return;
     
    1347313998    global $ai4seo_allowed_attachment_mime_types;
    1347413999
     14000    if (ai4seo_prevent_loops(__FUNCTION__)) {
     14001        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     14002        return false;
     14003    }
     14004
    1347514005    if (!is_numeric($attachment_post_id)) {
    1347614006        return false;
     
    1351814048 */
    1351914049function 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
    1352014055    // Set the file size limit to 1 MB.
    1352114056    $max_file_size = 100000; // 1 MB in bytes.
     
    1360914144function ai4seo_update_attachment_attributes(int $attachment_post_id, array $attachment_attribute_updates = array(), bool $force_overwrite_all_existing_data = false): bool {
    1361014145    global $wpdb;
     14146
     14147    if (ai4seo_prevent_loops(__FUNCTION__)) {
     14148        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     14149        return false;
     14150    }
    1361114151
    1361214152    // sanitize
     
    1374114281 */
    1374214282function 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
    1374314288    $attachment_attributes_generation_language = sanitize_text_field(ai4seo_get_setting(AI4SEO_SETTING_ATTACHMENT_ATTRIBUTES_GENERATION_LANGUAGE));
    1374414289
     
    1378514330 * @return array the supported attachment post types
    1378614331 */
    13787 function ai4seo_get_supported_attachment_post_types() {
     14332function 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
    1378814338    $ai4seo_active_attachment_attributes = ai4seo_get_active_attachment_attributes();
    1378914339
     
    1385814408    return $active_attachment_attributes_names;
    1385914409}
    13860 
    1386114410
    1386214411
     
    1387214421 * @return bool True if the post meta was updated, false if not
    1387314422 */
    13874 function ai4seo_update_postmeta_if_empty($post_id, $meta_key, $meta_value): bool {
     14423function ai4seo_update_postmeta_if_empty(int $post_id, string $meta_key, string $meta_value): bool {
    1387514424    $post_id = sanitize_key($post_id);
    1387614425    $meta_key = sanitize_key($meta_key);
     
    1390914458 */
    1391014459function 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
    1391114465    $option = sanitize_key($option);
    1391214466
     
    1395314507 */
    1395414508function 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
    1395514514    $option = sanitize_key($option);
    1395614515
     
    1399114550 */
    1399214551function 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
    1399314557    switch ($add_to_this_option) {
    1399414558        // now missing -> remove from fully covered and generated
     
    1400214566            break;
    1400314567
    14004         // now fully covered and/or generated -> remove from missing
     14568        // now fully covered -> remove from missing
    1400514569        case AI4SEO_FULLY_COVERED_METADATA_POST_IDS_OPTION_NAME:
    1400614570            ai4seo_remove_post_ids_from_option(AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME, $post_ids);
     
    1403214596/**
    1403314597 * Remove post ids from an option that is saved as json
    14034  * @param $ai4seo_option
    14035  * @param $post_ids
     14598 * @param string $remove_from_this_option
     14599 * @param int|array $post_ids
    1403614600 * @return bool
    1403714601 */
    14038 function ai4seo_remove_post_ids_from_option($ai4seo_option, $post_ids): bool {
    14039     $ai4seo_option = sanitize_key($ai4seo_option);
     14602function 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);
    1404014609
    1404114610    if (!is_array($post_ids)) {
     
    1405114620
    1405214621    // 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);
    1405414623
    1405514624    // remove the new post ids from the old ones
     
    1407414643
    1407514644    // 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);
    1407714646}
    1407814647
     
    1412814697 */
    1412914698function ai4seo_send_json_success($response = [], $status_code = null) {
     14699    if (!ai4seo_singleton(__FUNCTION__)) {
     14700        return;
     14701    }
     14702
    1413014703    $noise = '';
    1413114704
     
    1415714730 */
    1415814731function 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
    1415914736    $clear_buffer = apply_filters('ai4seo_clear_buffer_on_error', true);
    1416014737
     
    1417714754
    1417814755function ai4seo_normalize_ajax_response_data(&$data) {
     14756    if (!ai4seo_singleton(__FUNCTION__)) {
     14757        return;
     14758    }
     14759
    1417914760    if (is_array($data)) {
    1418014761        array_walk_recursive($data, function (&$item) {
     
    1419414775
    1419514776function 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
    1419614782    if (!is_string($item)) {
    1419714783        return;
     
    1420714793 */
    1420814794function ai4seo_print_ajax_nonce_field(): void {
     14795    if (!ai4seo_singleton(__FUNCTION__)) {
     14796        return;
     14797    }
     14798
    1420914799    // Only when our menu/page is active.
    1421014800    if ( !ai4seo_is_user_inside_our_plugin_admin_pages() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     
    1422714817 */
    1422814818function ai4seo_save_anything($additional_upcoming_updates = array()) {
     14819    if (!ai4seo_singleton(__FUNCTION__)) {
     14820        return;
     14821    }
     14822
    1422914823    // add $_POST to the updates
    1423014824    if (!is_array($additional_upcoming_updates)) {
     
    1470415298    // Make sure that this function is only called once
    1470515299    if (!ai4seo_singleton(__FUNCTION__)) {
    14706         return;
    14707     }
    14708 
    14709     // Check if user has permission to manage this plugin
    14710     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);
    1471215300        return;
    1471315301    }
     
    1492615514    global $wpdb;
    1492715515
     15516    // Make sure that this function is only called once
     15517    if (!ai4seo_singleton(__FUNCTION__)) {
     15518        return;
     15519    }
     15520
    1492815521    // read all pid's of wp_ngg_pictures
    1492915522    $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);
     
    1507215665    global $ai4seo_are_settings_initialized;
    1507315666
     15667    if (ai4seo_prevent_loops(__FUNCTION__, 5)) {
     15668        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     15669        return '';
     15670    }
     15671
    1507415672    if (!$ai4seo_are_settings_initialized) {
    1507515673        ai4seo_init_settings();
     
    1507815676    if (!$ai4seo_are_settings_initialized) {
    1507915677        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");
    1508115679        return "";
    1508215680    }
     
    1510615704    global $ai4seo_are_settings_initialized;
    1510715705
     15706    if (ai4seo_prevent_loops(__FUNCTION__, 5)) {
     15707        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     15708        return false;
     15709    }
     15710
    1510815711    if (!$ai4seo_are_settings_initialized) {
    1510915712        ai4seo_init_settings();
     
    1513515738
    1513615739/**
    15137  * Function to update the wp_options table with the current settings, by removing default values
    15138  * @return bool
    15139  */
    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 overhead
    15147         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 database
    15153     return ai4seo_update_option(AI4SEO_SETTINGS_OPTION_NAME, $ai4seo_settings_copy, true);
    15154 }
    15155 
    15156 // =========================================================================================== \\
    15157 
    15158 /**
    1515915740 * Update values of given settings
    1516015741 * @param $setting_changes array An array of settings to update
     
    1516315744function ai4seo_bulk_update_settings(array $setting_changes): bool {
    1516415745    global $ai4seo_settings;
     15746
     15747    if (ai4seo_prevent_loops(__FUNCTION__)) {
     15748        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     15749        return false;
     15750    }
    1516515751
    1516615752    $ai4seo_new_settings = $ai4seo_settings;
     
    1518515771
    1518615772/**
     15773 * Function to update the wp_options table with the current settings, by removing default values
     15774 * @return bool
     15775 */
     15776function 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/**
    1518715800 * Validate value of a setting
    1518815801 * @return bool True if the value is valid, false if not
    1518915802 */
    1519015803function 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
    1519115809    switch ($setting_name) {
    1519215810        case AI4SEO_SETTING_BULK_GENERATION_DURATION:
     
    1562416242function ai4seo_is_bulk_generation_enabled(string $post_type): bool {
    1562516243    $enabled_bulk_generation_post_types = ai4seo_get_setting(AI4SEO_SETTING_ENABLED_BULK_GENERATION_POST_TYPES) ?: array();
     16244
    1562616245    return is_array($enabled_bulk_generation_post_types) && in_array($post_type, $enabled_bulk_generation_post_types);
    1562716246}
     
    1563516254function ai4seo_is_any_bulk_generation_enabled(): bool {
    1563616255    $enabled_bulk_generations_post_types = ai4seo_get_setting(AI4SEO_SETTING_ENABLED_BULK_GENERATION_POST_TYPES) ?: array();
     16256
    1563716257    return count($enabled_bulk_generations_post_types) > 0;
    1563816258}
     
    1565016270function ai4seo_read_all_environmental_variables(bool $use_cache = true): array {
    1565116271    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    }
    1565216277
    1565316278    if (!isset($ai4seo_environmental_variables) || !$ai4seo_environmental_variables) {
     
    1569616321 */
    1569716322function 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
    1569816328    // Make sure that $environmental_variable_name-parameter has content
    1569916329    if (!$environmental_variable_name) {
     
    1572916359function ai4seo_update_environmental_variable(string $environmental_variable_name, $new_environmental_variable_value, bool $use_cache = true): bool {
    1573016360    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    }
    1573116366
    1573216367    if (!isset(AI4SEO_DEFAULT_ENVIRONMENTAL_VARIABLES[$environmental_variable_name])) {
     
    1579516430function ai4seo_delete_environmental_variable(string $environmental_variable_name): bool {
    1579616431    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    }
    1579716437
    1579816438    // Make sure that $environmental_variable_name-parameter has content
     
    1586116501    );
    1586216502
     16503    if (ai4seo_prevent_loops(__FUNCTION__)) {
     16504        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     16505        return $result;
     16506    }
     16507
    1586316508    // Nothing to do.
    1586416509    if ( empty( $environmental_variable_updates ) ) {
     
    1593816583 */
    1593916584function 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
    1594016590    switch ($environmental_variable_name) {
    1594116591        case AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_KNOWN_PLUGIN_VERSION:
     
    1605716707 */
    1605816708function 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
    1605916714    // get the constant value
    1606016715    $constant_value = ai4seo_get_robhub_environmental_variable_constant_value($environmental_variable_constant_name);
     
    1607216727 */
    1607316728function 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
    1607416734    // get the constant value
    1607516735    $constant_value = ai4seo_get_robhub_environmental_variable_constant_value($environmental_variable_constant_name);
     
    1610916769 */
    1611016770function 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
    1611116776    if (empty($notification_index)) {
    1611216777        return false;
     
    1620416869/**
    1620516870 * 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
    1620716872 * @return array Array of notifications that should be displayed (not dismissed and not expired)
    1620816873 */
    1620916874function 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
    1621016880    $notifications = ai4seo_get_option(AI4SEO_NOTIFICATIONS_OPTION_NAME, array());
    1621116881
     
    1628616956 */
    1628716957function 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
    1628816963    if (empty($notification_index) || empty($notification) || empty($notification['message'])) {
    1628916964        return;
     
    1638217057 */
    1638317058function 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
    1638417064    $show_dismiss_button = !(isset($notification['is_permanent']) && $notification['is_permanent']);
    1638517065    $show_not_now_button = (bool) ($notification['not_now_button'] ?? false); # replaces dismiss button if set
     
    1651517195
    1651617196function 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
    1651717202    $conditions = array();
    1651817203    $debug = false; # set to true to enable debug logging
     
    1671117396 */
    1671217397function ai4seo_refresh_unread_notifications_count() {
     17398    if (ai4seo_prevent_loops(__FUNCTION__)) {
     17399        error_log('AI4SEO: Prevented infinite loop in ' . __FUNCTION__);
     17400        return;
     17401    }
     17402
    1671317403    $displayable_notifications = ai4seo_get_displayable_notifications();
    1671417404
     
    1675217442 */
    1675317443function 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
    1675417449    return (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_UNREAD_NOTIFICATIONS_COUNT);
    1675517450}
     
    1676217457 */
    1676317458function 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
    1676417464    $displayable_notifications = ai4seo_get_displayable_notifications();
    1676517465    $all_notifications = ai4seo_get_option(AI4SEO_NOTIFICATIONS_OPTION_NAME, array());
     
    1680817508 */
    1680917509function 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
    1681017515    if (empty($notification_index)) {
    1681117516        return false;
     
    1684517550 */
    1684617551function 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
    1684717557    if (empty($index)) {
    1684817558        return false;
     
    1689117601 */
    1689217602function 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
    1689317608    if (empty($notification_index)) {
    1689417609        return false;
     
    1692217637
    1692317638function ai4seo_check_for_new_notifications() {
     17639    if (!ai4seo_singleton(__FUNCTION__)) {
     17640        return;
     17641    }
     17642
    1692417643    $is_user_on_our_dashboard = ai4seo_is_plugin_page_active("dashboard");
    1692517644
     
    1694917668function ai4seo_check_for_unfinished_posts_table_analysis_notification($force = false) {
    1695017669    global $wpdb;
     17670
     17671    if (!ai4seo_singleton(__FUNCTION__)) {
     17672        return;
     17673    }
     17674
    1695117675    $notification_index = "unfinished-posts-table-analysis";
    1695217676
     
    1699817722
    1699917723function ai4seo_check_for_plugin_update_notification($last_known_plugin_version, $force = false) {
     17724    if (!ai4seo_singleton(__FUNCTION__)) {
     17725        return;
     17726    }
     17727
    1700017728    $notification_index = "plugin-update";
    1700117729
     
    1709417822
    1709517823function ai4seo_check_for_heavy_db_operations_disabled_notification( bool $force = false ) {
     17824    if (!ai4seo_singleton(__FUNCTION__)) {
     17825        return;
     17826    }
     17827
    1709617828    $notification_index = 'heavy-db-operations-disabled';
    1709717829
     
    1712617858
    1712717859function ai4seo_check_for_wpml_heads_up_notification($force = false) {
     17860    if (!ai4seo_singleton(__FUNCTION__)) {
     17861        return;
     17862    }
     17863
    1712817864    $notification_index = "wpml-heads-up";
    1712917865
     
    1715917895
    1716017896function ai4seo_check_for_rate_us_notification($force = false) {
     17897    if (!ai4seo_singleton(__FUNCTION__)) {
     17898        return;
     17899    }
     17900
    1716117901    $notification_index = "rate-us";
    1716217902
     
    1719417934
    1719517935function ai4seo_check_for_low_credits_balance_notification($force = false) {
     17936    if (!ai4seo_singleton(__FUNCTION__)) {
     17937        return;
     17938    }
     17939
    1719617940    $notification_index = "low-credits-balance";
    1719717941
     
    1724617990 */
    1724717991function ai4seo_check_for_missing_entries_notification($force = false) {
     17992    if (!ai4seo_singleton(__FUNCTION__)) {
     17993        return;
     17994    }
     17995
    1724817996    $notification_index = "missing-entries";
    1724917997
     
    1737918127
    1738018128function ai4seo_check_for_robhub_account_error_notification($api_response, $force = false) {
     18129    if (!ai4seo_singleton(__FUNCTION__)) {
     18130        return;
     18131    }
     18132
    1738118133    $notification_index = "robhub-account-error";
    1738218134
     
    1742418176
    1742518177function ai4seo_check_for_plugin_update_available($latest_plugin_version, $force = false) {
     18178    if (!ai4seo_singleton(__FUNCTION__)) {
     18179        return;
     18180    }
     18181
    1742618182    $notification_index = "plugin-update-available";
    1742718183
     
    1746418220
    1746518221function ai4seo_check_for_payg_status_errors($payg_status, $force = false) {
     18222    if (!ai4seo_singleton(__FUNCTION__)) {
     18223        return;
     18224    }
     18225
    1746618226    $notification_index = "payg-status-error";
    1746718227
     
    1753618296 */
    1753718297function ai4seo_check_for_inefficient_cron_jobs_notification($force = false) {
     18298    if (!ai4seo_singleton(__FUNCTION__)) {
     18299        return;
     18300    }
     18301
    1753818302    $notification_index = "inefficient-cron-jobs";
    1753918303
     
    1761718381 */
    1761818382function ai4seo_check_for_finished_seo_autopilot_notification($force = false) {
     18383    if (!ai4seo_singleton(__FUNCTION__)) {
     18384        return;
     18385    }
     18386
    1761918387    $notification_index = "seo-autopilot-finished";
    1762018388
     
    1775118519
    1775218520function ai4seo_check_discount_notification($discount, $allow_notification_force = false) {
     18521    if (!ai4seo_singleton(__FUNCTION__)) {
     18522        return;
     18523    }
     18524
    1775318525    $notification_index = "discount";
    1775418526    $discount_name = $discount['name'] ?? '';
     
    1787718649 */
    1787818650function 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
    1787918656    // check the last known aiforseo.ai's terms update
    1788018657    $last_website_toc_and_pp_update_time = (int) ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_LAST_WEBSITE_TOC_AND_PP_UPDATE_TIME);
     
    1807318850 */
    1807418851function 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
    1807518857    // collect additional data and put it into the wp_option "AI4SEO_ADDITIONAL_TOS_ACCEPT_DETAILS"
    1807618858    $additional_tos_accept_details = array(
     
    1810418886    }
    1810518887
     18888    if (!ai4seo_singleton(__FUNCTION__)) {
     18889        return;
     18890    }
     18891
    1810618892    // check in wp_options if we have additional tos accept details
    1810718893    $additional_tos_accept_details = ai4seo_get_option(AI4SEO_ADDITIONAL_TOS_ACCEPT_DETAILS_OPTION_NAME);
     
    1816518951 */
    1816618952function 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
    1816718958    $new_entry["timestamp"] = time();
    1816818959
     
    1826319054// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
    1826419055
    18265 function ai4seo_send_pay_as_you_go_settings() {
     19056function ai4seo_send_pay_as_you_go_settings(): bool {
     19057    if (!ai4seo_singleton(__FUNCTION__)) {
     19058        return false;
     19059    }
     19060
    1826619061    // call robhub api endpoint "payg-settings" with current payg settings
    1826719062    $robhub_endpoint = "client/payg-settings";
  • ai-for-seo/trunk/assets/js/ai-for-seo-scripts.js

    r3399672 r3408847  
    78277827    hidden_mode_triggers: 0,
    78287828    full_reload_triggers: 0,
    7829     user_interaction_locks: 0
     7829    user_interaction_locks: 0,
     7830    last_ajax_response_duration_ms: 0
    78307831};
    78317832
     
    82798280    }
    82808281
     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
    82818289    ai4seo_perform_ajax_call('ai4seo_get_dashboard_html', {}, false) // auto_check_response = false
    82828290        .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            }
    82838297
    82848298            // Check if this request was cancelled (idempotent discard)
  • ai-for-seo/trunk/changelog.txt

    r3399672 r3408847  
    11== Changelog ==
     2
     3= 2.2.3 =
     4* Bug Fixes & Maintenance: Fixed 7 minor bugs and implemented 2 usability improvements.
    25
    36= 2.2.2 =
  • ai-for-seo/trunk/includes/ajax/process/generate-attachment-attributes.php

    r3399672 r3408847  
    232232
    233233if (!$ai4seo_this_success) {
    234     #error_log("AI for SEO: Could not save attachment attributes: " . print_r($ai4seo_new_attachment_attributes, true));
    235     ai4seo_send_json_error(esc_html__("Could not save attachment attributes.", "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);
    236236}
    237237
     
    242242
    243243    if (!$ai4seo_this_success) {
    244         #error_log("AI for SEO: Could not save attachment alt text: " . print_r($ai4seo_new_attachment_attributes, true));
    245         ai4seo_send_json_error(esc_html__("Could not save attachment alt 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);
    246246    }
    247247}
  • ai-for-seo/trunk/includes/api/class-robhub-api-communicator.php

    r3399672 r3408847  
    1111
    1212class Ai4Seo_RobHubApiCommunicator {
     13    public bool $is_initialized = false;
    1314    private string $version = "v1";
    1415    private string $api_url = "https://api.robhub.ai";
  • ai-for-seo/trunk/includes/menu-frame.php

    r3395515 r3408847  
    3535    $ai4seo_unread_notifications_count = 0;
    3636} else {
    37     $ai4seo_unread_notifications_count = (int) ai4seo_get_num_unread_notification();
     37    $ai4seo_unread_notifications_count = ai4seo_get_num_unread_notification();
    3838}
    3939
  • ai-for-seo/trunk/includes/modal_schemas/customize-pay-as-you-go.php

    r3395515 r3408847  
    176176                echo "<li>";
    177177                    echo sprintf(
    178                         esc_html__("I will automatically purchase %s Credits for %s whenever my Credits 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"),
    179179                        "<strong><span id='ai4seo-payg-summary-credits-amount'>" . esc_html($ai4seo_credits_packs[$ai4seo_payg_stripe_price_id]["credits_amount"] ?? 0) . "</span></strong>",
    180180                        ($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  
    118118        echo "<div class='ai4seo-get-more-credits-section-left'>";
    119119            echo "<div class='ai4seo-get-more-credits-section-big-number'>";
    120                 echo $ai4seo_section_number;
     120                echo esc_html($ai4seo_section_number);
    121121            echo "</div>";
    122122        echo "</div>";
     
    154154                // PAID PLAN
    155155                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") . " ");
    157157                    echo sprintf(
    158158                        esc_html__("Subscribed to %s.", "ai-for-seo"),
     
    226226            echo "<div class='ai4seo-get-more-credits-section-left'>";
    227227                echo "<div class='ai4seo-get-more-credits-section-big-number'>";
    228                     echo $ai4seo_section_number;
     228                    echo esc_html($ai4seo_section_number);
    229229                echo "</div>";
    230230            echo "</div>";
     
    261261            echo "<div class='ai4seo-get-more-credits-section-left'>";
    262262                echo "<div class='ai4seo-get-more-credits-section-big-number'>";
    263                     echo $ai4seo_section_number;
     263                    echo esc_html($ai4seo_section_number);
    264264                echo "</div>";
    265265            echo "</div>";
     
    367367            echo "<div class='ai4seo-get-more-credits-section-left'>";
    368368                echo "<div class='ai4seo-get-more-credits-section-big-number'>";
    369                     echo $ai4seo_section_number;
     369                    echo esc_html($ai4seo_section_number);
    370370                echo "</div>";
    371371            echo "</div>";
  • ai-for-seo/trunk/includes/pages/content_types/attachment.php

    r3399672 r3408847  
    261261}
    262262
    263 echo $ai4seo_filter_form_html;
     263echo ai4seo_wp_kses($ai4seo_filter_form_html);
    264264
    265265// Stop script if no posts have been found -> show message and stop page rendering
     
    523523
    524524    echo "<div class='ai4seo-pagination'>";
    525         echo $ai4seo_pagination_links;
     525        echo ai4seo_wp_kses($ai4seo_pagination_links);
    526526    echo "</div>";
    527527}
  • ai-for-seo/trunk/includes/pages/content_types/post.php

    r3399672 r3408847  
    6464$ai4seo_all_failed_metadata_post_ids = ai4seo_get_post_ids_from_option(AI4SEO_FAILED_METADATA_POST_IDS_OPTION_NAME);
    6565
     66$ai4seo_missing_metadata_post_ids = ai4seo_get_post_ids_from_option(AI4SEO_MISSING_METADATA_POST_IDS_OPTION_NAME);
    6667$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);
    6769
    6870if ($ai4seo_generate_metadata_for_fully_covered_entries) {
    6971    $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));
    7175} else {
    7276    $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);
    7477}
    7578
     
    127130
    128131$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,
    131134    'failed' => $ai4seo_all_failed_metadata_post_ids,
    132135    'processing' => array_merge($ai4seo_pending_metadata_post_ids, $ai4seo_processing_metadata_post_ids),
     
    226229// === TABLE WITH ALL POSTS ================================================================== \\
    227230
    228 echo $ai4seo_filter_form_html;
     231echo ai4seo_wp_kses($ai4seo_filter_form_html);
    229232
    230233// Stop script if no posts have been found -> show message and stop page rendering
     
    293296        }
    294297
    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);
    301299        $ai4seo_this_post_is_failed_to_fill = in_array($ai4seo_this_post_id, $ai4seo_current_page_failed_to_fill_post_ids);
    302300
     
    478476
    479477    echo "<div class='ai4seo-pagination'>";
    480         echo $ai4seo_pagination_links;
     478        echo ai4seo_wp_kses($ai4seo_pagination_links);
    481479    echo "</div>";
    482480}
  • ai-for-seo/trunk/includes/pages/dashboard.php

    r3399672 r3408847  
    1919// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
    2020
     21global $ai4seo_did_run_post_table_analysis;
    2122$ai4seo_current_utc_hour = (int)gmdate("H");
    2223$ai4seo_posts_table_analysis_state = ai4seo_read_environmental_variable(AI4SEO_ENVIRONMENTAL_VARIABLE_POSTS_TABLE_ANALYSIS_STATE);
     
    133134                echo "</div>";
    134135
    135                 if (!$ai4seo_heavy_db_operations_disabled) {
     136                if (!$ai4seo_heavy_db_operations_disabled && $ai4seo_did_run_post_table_analysis) {
    136137                    echo "<div id='ai4seo-no-dashboard-refresh-delay'></div>";
    137138                }
     
    291292            echo "<div class='ai4seo-credits-number'>";
    292293                if ($ai4seo_is_robhub_account_synced) {
    293                     echo $ai4seo_current_credits_balance;
     294                    echo esc_html($ai4seo_current_credits_balance);
    294295                } else {
    295296                    echo "<span class='ai4seo-red-message'>";
     
    415416                    echo "</div>";
    416417                } 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'>";
    418419
    419420                    echo "<div class='ai4seo-bulk-generation-status-text'>";
     
    600601                            // if details given, output them else the action
    601602                            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));
    603604                            } else {
    604605                                // metadata-manually-generated", "metadata-bulk-generated", "attachment-attributes-manually-generated", "attachment-attributes-bulk-generated
     
    692693
    693694                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>";
    695695                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>";
    697698
    698699                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"));
     
    762763
    763764                    // 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>";
    765770
    766771                    // Caret icons - first entry expanded, others collapsed
  • ai-for-seo/trunk/includes/pages/help.php

    r3399672 r3408847  
    224224    $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>";
    225225    $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>";
    227227
    228228    // Content tabes
     
    236236    // Help & contact
    237237    $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. 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>";
     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>";
    239239    $ai4seo_this_accordion_content .= "<img src='" . esc_url(ai4seo_get_assets_images_url("help-screenshots/first-steps-7.jpg")) . "' style='width: 100%;' />";
    240240    $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>";
     
    439439        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));
    440440
    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));
    443443
    444444        $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");
     
    605605        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));
    606606
    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");
    608608        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));
    609609
     
    690690
    691691        $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"),
    693693            esc_html($ai4seo_free_plan_credits),
    694694            esc_html(AI4SEO_DAILY_FREE_CREDITS_AMOUNT),
     
    698698
    699699        $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 />",
    701701            esc_html($ai4seo_s_plan_credits),
    702702            esc_html($ai4seo_s_plan_credits),
     
    716716        $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 />";
    717717        $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 />",
    719719            esc_html($ai4seo_free_plan_credits),
    720720        );
    721721        $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 />",
    723723            esc_html($ai4seo_s_plan_credits),
    724724        );
     
    943943                    echo "</label>";
    944944
    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");
    947947
    948948                    echo "<p class='ai4seo-form-item-description'>";
  • ai-for-seo/trunk/includes/pages/settings.php

    r3395515 r3408847  
    180180                } else {
    181181                    echo "<p class='ai4seo-form-item-description'>";
    182                         echo esc_html__("No public post types detected.", "ai-for-seo");
     182                        echo esc_html__("No supported post types detected.", "ai-for-seo");
    183183                    echo "</p>";
    184184                }
     
    497497                    echo "<select id='" . esc_attr($ai4seo_this_setting_input_name) . "' name='" . esc_attr($ai4seo_this_setting_input_name) . "' class='ai4seo-select'>";
    498498                        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>';
    501503                        }
    502504                    echo "</select>";
     
    11181120                    }
    11191121                    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>";
    11211123                    }
    11221124                echo "</select>";
     
    11731175        $ai4seo_potential_js_alt_text_setting_hidden_class = $ai4seo_alt_injection_enabled ? '' : ' ai4seo-js-alt-text-setting-hidden';
    11741176
    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'>";
    11761178            echo "<label for='" . esc_attr($ai4seo_this_setting_input_name) . "'>";
    11771179                // new feature bubble # todo: remove bubble after some time
     
    12141216                echo "<select id='" . esc_attr($ai4seo_this_setting_input_name) . "' name='" . esc_attr($ai4seo_this_setting_input_name) . "'>";
    12151217                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>';
    12201222                }
    12211223                echo "</select>";
     
    12551257                echo "<select id='" . esc_attr($ai4seo_this_setting_input_name) . "' name='" . esc_attr($ai4seo_this_setting_input_name) . "'>";
    12561258                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>';
    12611263                }
    12621264                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) ===
    22Tags: seo, ai, google search console, alt text, bulk
    33Contributors: spacecodes
     
    55Requires at least: 4.7
    66Tested up to: 6.8.3
    7 Stable tag: 2.2.2
     7Stable tag: 2.2.3
    88Requires PHP: 7.4
    99License: GPLv2 or later (or compatible)
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1111
    12 Lightweight SEO Autopilot compatible with Yoast SEO, RankMath, SEOPress, WooCommerce etc. to generate Keyphrase, meta tags, alt text and more in bulk.
     12Lightweight SEO Autopilot that works with Yoast SEO, Rank Math, SEOPress, WooCommerce etc. to bulk-generate keyphrases, meta tags, alt text and more.
    1313
    1414== Description ==
     
    184184Yes. In Settings, you can restrict access by user role. Agencies can also use Incognito and White-Label options in *AI for SEO* > Account.
    185185
    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 
    189186= Is AI-generated SEO-content harmful to my SEO-ranking? =
    190187Currently, 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).
     
    200197
    201198== Changelog ==
     199
     200= 2.2.3 =
     201* Bug Fixes & Maintenance: Fixed 7 minor bugs and implemented 2 usability improvements.
    202202
    203203= 2.2.2 =
Note: See TracChangeset for help on using the changeset viewer.