HEX
Server: nginx/1.18.0
System: Linux test-ipsremont 5.4.0-214-generic #234-Ubuntu SMP Fri Mar 14 23:50:27 UTC 2025 x86_64
User: ips (1000)
PHP: 8.0.30
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/quadcodewordpressapi/public/wp-content/plugins/transtations/translations.php
<?php
/*
Plugin Name: API Post translations
Description: A plugin for storing translations data
Version:     1.0.10
*/

function create_translations_table()
{

    global $table_prefix, $wpdb;

    $charset_collate = $wpdb->get_charset_collate();

    $table_name = $table_prefix . 'translations';
    $sql = "CREATE TABLE IF NOT EXISTS " . $table_name . " ( 
    id INT(11) NOT NULL AUTO_INCREMENT,
    post_id BIGINT(20) UNSIGNED NOT NULL,
    lang VARCHAR(10) NOT NULL,
    post_title TEXT NOT NULL,
    post_content LONGTEXT NULL,
    post_attributes LONGTEXT NULL,
    date_created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    date_modified DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (post_id) REFERENCES {$table_prefix}posts(ID) ON DELETE CASCADE ON UPDATE CASCADE,
    UNIQUE KEY lang_post (lang, post_id),
    PRIMARY KEY (id));";

    $wpdb->query($sql);
}

function create_users_translations_table()
{
    global $table_prefix, $wpdb;

    $table_name = $table_prefix . 'user_translations';
    $sql = "CREATE TABLE IF NOT EXISTS " . $table_name . " ( 
    id INT(11) NOT NULL AUTO_INCREMENT,
    user_id BIGINT(20) UNSIGNED NOT NULL,
    lang VARCHAR(10) NOT NULL,
    description TEXT NOT NULL,
    position TEXT NOT NULL,
    education TEXT NOT NULL,
    experience TEXT NOT NULL,
    date_created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    date_modified DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES {$table_prefix}users(ID) ON DELETE CASCADE ON UPDATE CASCADE,
    UNIQUE KEY lang_post (lang, user_id),
    PRIMARY KEY (id));";

    $wpdb->query($sql);

    $table_name = $table_prefix . 'expertise_translations';
    $sql = "CREATE TABLE IF NOT EXISTS " . $table_name . " ( 
    id INT(11) NOT NULL AUTO_INCREMENT,
    expertise_id BIGINT(20) UNSIGNED NOT NULL,
    lang VARCHAR(10) NOT NULL,
    name TEXT NOT NULL,
    date_created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    date_modified DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY lang_post (lang, expertise_id),
    PRIMARY KEY (id));";

    $wpdb->query($sql);

    $table_name = $table_prefix . 'users';
    $sql = "ALTER TABLE $table_name
            ADD COLUMN date_modified DATETIME NULL ON UPDATE CURRENT_TIMESTAMP AFTER user_registered
        ";
    $wpdb->query($sql);
}


function create_tags_translations_table()
{
    global $table_prefix, $wpdb;

    $table_name = $table_prefix . 'tag_translations';
    $sql = "CREATE TABLE IF NOT EXISTS " . $table_name . " ( 
    id INT(11) NOT NULL AUTO_INCREMENT,
    tag_id BIGINT(20) UNSIGNED NOT NULL,
    lang VARCHAR(10) NOT NULL,
    name TEXT NOT NULL,
    date_created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    date_modified DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY lang_post (lang, tag_id),
    PRIMARY KEY (id));";

    $wpdb->query($sql);
}

function plugin_updates()
{
    global $wpdb, $table_prefix;

    $pluginVersion = get_site_option('translationsPluginVersion');
    $table_name = $table_prefix . 'translations';

    switch ($pluginVersion) {
        case '':
            $wpdb->query(
                "ALTER TABLE $table_name
                ADD COLUMN post_excerpt TEXT NOT NULL AFTER post_title
            "
            );

            update_option('translationsPluginVersion', '1.0.1');
            break;
        case '1.0.1':
            $wpdb->query(
                "ALTER TABLE $table_name
                ADD COLUMN yoast_title TEXT NULL
            "
            );

            $wpdb->query(
                "ALTER TABLE $table_name
                ADD COLUMN yoast_description MEDIUMTEXT NULL
            "
            );

            update_option('translationsPluginVersion', '1.0.2');
            break;
        case '1.0.2':
            $wpdb->query(
                "ALTER TABLE $table_name
                ADD COLUMN is_active TINYINT DEFAULT 1 NOT NULL AFTER yoast_description
            "
            );

            update_option('translationsPluginVersion', '1.0.3');
            break;
        case '1.0.3':
            $wpdb->query(
                "ALTER TABLE $table_name
                ADD COLUMN ai_summary TEXT NULL AFTER post_content
            "
            );

            update_option('translationsPluginVersion', '1.0.4');
            break;
        case '1.0.4':
            $table_name = $table_prefix . 'translations';

            $sql = "UPDATE $table_name 
                    SET post_content = REPLACE(REPLACE(post_content, '<a href', ' <a href'), '</a>', '</a> ')
                    WHERE post_content like '%</a>%'";
            $wpdb->query($sql);

            update_option('translationsPluginVersion', '1.0.5');
            break;
        case '1.0.5':
            $table_name = $table_prefix . 'translations';

            $sql = "ALTER TABLE $table_name DROP COLUMN ai_summary";
            $wpdb->query($sql);

            $sql = "DELETE FROM wp_postmeta 
                    WHERE meta_key = 'ai_summary' OR meta_key = '_ai_summary'";
            $wpdb->query($sql);

            update_option('translationsPluginVersion', '1.0.6');
            break;
        case '1.0.6':
            create_users_translations_table();

            update_option('translationsPluginVersion', '1.0.7');
            break;
        case '1.0.7':
            create_tags_translations_table();

            update_option('translationsPluginVersion', '1.0.8');
            break;
        case '1.0.8':
            $table_name = $table_prefix . 'translations';

            $sql = "UPDATE $table_name 
                    SET post_attributes = '', is_active = 0
                    WHERE post_id IN (SELECT DISTINCT post_id FROM {$table_prefix}postmeta WHERE meta_key like '%youtube_video%title' AND meta_value != '');";
            $wpdb->query($sql);

            update_option('translationsPluginVersion', '1.0.9');
            break;
        case '1.0.9':
            $table_name = $table_prefix . 'user_translations';

            $sql = "DELETE FROM $table_name;";
            $wpdb->query($sql);

            update_option('translationsPluginVersion', '1.0.10');
            break;
        default:
            break;
    }

}

add_action('plugins_loaded', 'plugin_updates');
register_activation_hook(__FILE__, 'create_translations_table');

add_action('profile_update', 'userUpdated');
add_action('edit_user_profile_update', 'userUpdated');
function userUpdated($userId, $old_user_data = [])
{
    global $wpdb, $table_prefix;

    $updateTime = date('Y-m-d H:i:s');
    $sql = "UPDATE $table_prefix" . "users SET date_modified = '$updateTime' WHERE ID = $userId";
    $wpdb->query($sql);
}

function getPostTranslation($postId, $lang, $isActive = 1, $isCron = false)
{
    global $table_prefix,
           $wpdb;

    if (!is_array($postId)) {
        // For single post, if we are swithcihng from existed translated post to non-existed, we should find English post id first
        $translatedPostIds = pll_get_post_translations($postId);
        foreach ($translatedPostIds as $translatedPostLang => $translatedPostId) {
            if ($translatedPostLang === DEFAULT_LANG) {
                $postId = $translatedPostId;
            }
        }

        $postId = [$postId];
    }

    $table_name = $table_prefix . 'translations';

    $sql = "SELECT * FROM $table_name WHERE post_id IN (" . implode(',', $postId) . ") AND lang = '$lang' AND is_active = " . $isActive;
    $result = $wpdb->get_results($sql);
    // Return data for English post if no translation found and call is not from cron job
    if (empty($result[0]) && !$isCron) {
        $englishPostId = $postId[0];
        $englishPost = get_post($englishPostId);

        $result = [
            json_decode(json_encode([
                'post_title' => $englishPost->post_title,
                'post_content' => $englishPost->post_content,
                'post_excerpt' => $englishPost->post_excerpt,
                'post_attributes' => serialize(get_fields($englishPostId)),
                'yoast_title' => get_post_meta($englishPostId, '_yoast_wpseo_title', true),
                'yoast_description' => get_post_meta($englishPostId, '_yoast_wpseo_metadesc', true),
            ])),
        ];
    } else {
        if (!empty($result[0])) {
            // Clean tags generated by GPT
            $result[0]->post_title = cleanHtmlTags($result[0]->post_title);
        }
    }

    return $result;
}

function cleanHtmlTags($text)
{
    // if text is like <html> <head> <title>Prueba</title> </head> <body> <h1>Prueba</h1> </body> </html> need to split by html tags and get only unique text parts
    $textParts = preg_split('/<[^>]+>/', $text);
    $cleanParts = [];
    foreach ($textParts as $part) {
        $part = trim($part);
        if (!empty($part) && !in_array($part, $cleanParts)) {
            $cleanParts[] = $part;
        }
    }

    return implode(' ', $cleanParts);
}

function add_translation($postData, $lang)
{
    $postTranslation = getPostTranslation($postData['id'], $lang);
    if (!empty($postTranslation[0])) {
        $translation = $postTranslation[0];

        if (!empty($postData['yoast_head_json'])) {
            if ($postData['yoast_head_json']['title'] == $postData['title']['rendered']) {
                $postData['yoast_head_json']['title'] = do_shortcode($translation->post_title);
            } else {
                $postData['yoast_head_json']['title'] = do_shortcode($translation->yoast_title);
            }

            $postData['yoast_head_json']['description'] = do_shortcode($translation->yoast_description);

            $postData['yoast_head_json']['og_title'] = $postData['yoast_head_json']['title'];
            $postData['yoast_head_json']['og_description'] = $postData['yoast_head_json']['description'];
        }

        if (!empty($postData['title']) && !empty($postData['title']['rendered'])) {
            $postData['title']['rendered'] = do_shortcode($translation->post_title);
        } elseif (!empty($postData['title'])) {
            $postData['title'] = do_shortcode($translation->post_title);
        }
        if (!empty($postData['excerpt']) && !empty($postData['excerpt']['rendered'])) {
            $postData['excerpt']['rendered'] = do_shortcode($translation->post_excerpt);
        } elseif (!empty($postData['excerpt'])) {
            $postData['excerpt'] = do_shortcode($translation->post_excerpt);
        }

        // Images url fix
        $translation->post_content = str_replace('http:/wp-content/', 'https://wp.quadcode.com/wp-content/', $translation->post_content);

        if (!empty($postData['content']) && !empty($postData['content']['rendered'])) {
            $postData['content']['initial'] = $postData['content']['rendered'];
            $postData['content']['rendered'] = do_shortcode($translation->post_content);
        } elseif (!empty($postData['content'])) {
            $postData['content'] = do_shortcode($translation->post_content);
        }

        if ($translation->post_attributes && !empty($postData['acf']) && !empty($postData['acf']['faq'])) {
            if (is_base64($translation->post_attributes)) {
                $translation->post_attributes = base64_decode($translation->post_attributes);
            }

            $attributes = unserialize($translation->post_attributes);
            if (!empty($attributes) && !empty($attributes['faq'])) {
                $postData['acf']['faq'] = $attributes['faq'];
            }
        }

        if (!empty($postData['tag']) && !empty($postData['tagId'])) {
            $tagsTranslations = getTagTranslations($lang);
            if (!empty($tagsTranslations[$postData['tagId']])) {
                $postData['tag'] = $tagsTranslations[$postData['tagId']];
            }
        }
    }

    return $postData;
}

function is_base64($data) {
    return base64_encode(base64_decode($data, true)) === $data;
}

add_filter(
    'wp_post_revision_meta_keys',
    function ($keys, $post_type) {
        if (in_array($post_type, [POST_TYPE, EVENT_TYPE, GLOSSARY_TYPE])) {
            $keys[] = '_yoast_wpseo_title';
            $keys[] = '_yoast_wpseo_metadesc';
        }

        return $keys;
    },
    10, 2
);

//detect when a revision is being saved, then copy relevant meta from the parent:
add_action(
    '_wp_put_post_revision',
    function ($revision_id, $parent_id) {
        // Copy desired meta keys from parent → revision
        $keys = ['_yoast_wpseo_title', '_yoast_wpseo_metadesc'];
        foreach ($keys as $meta_key) {
            delete_metadata('post', $revision_id, $meta_key);
            $values = get_post_meta($parent_id, $meta_key, false);
            foreach ($values as $val) {
                add_metadata('post', $revision_id, $meta_key, $val);
            }
        }
    },
    10, 2
);

//to restore meta into the parent when restoring:
add_action('wp_restore_post_revision', function ($post_id, $revision_id) {
    $keys = ['_yoast_wpseo_title', '_yoast_wpseo_metadesc'];
    foreach ($keys as $meta_key) {
        $meta = get_metadata('post', $revision_id, $meta_key, true);
        if (false !== $meta) {
            update_post_meta($post_id, $meta_key, $meta);
        } else {
            delete_post_meta($post_id, $meta_key);
        }
    }
}, 10, 2);

//To display meta data on the revision comparison screen:
add_filter('_wp_post_revision_fields', function ($fields) {
    $fields['_yoast_wpseo_title'] = 'Meta Title';
    $fields['_yoast_wpseo_metadesc'] = 'Meta Description';

    return $fields;
});

add_filter('_wp_post_revision_field_yoast_wpseo_title', function ($value, $revision) {
    return get_metadata('post', $revision->ID, '_yoast_wpseo_title', true);
}, 10, 2);
add_filter('_wp_post_revision_field_yoast_wpseo_metadesc', function ($value, $revision) {
    return get_metadata('post', $revision->ID, '_yoast_wpseo_metadesc', true);
}, 10, 2);

add_action('save_post', 'resetTranslationsAfterPostUpdated', 10, 3);

function resetTranslationsAfterPostUpdated($post_id, WP_Post $post, $update)
{
    global $wpdb;

    if (wp_is_post_revision($post_id)) {
        return;
    }
    // Optional: restrict to a specific post type
    if (!in_array($post->post_type, [POST_TYPE, EVENT_TYPE, GLOSSARY_TYPE])) {
        return;
    }

    $revisions = wp_get_post_revisions($post_id);
    $prevRevision = null;

    $postLang = pll_get_post_language($post_id);

    if ($revisions && count($revisions) > 1) {
        $prevRevision = array_values($revisions)[1];

        $fieldsChanged = [];
        if (isset($prevRevision)) {
            $post_has_changed = false;

            $meta = get_post_meta($post->ID);
            if (!empty($meta)) {
                if (!empty($meta['_yoast_wpseo_title'])) {
                    $metaTitle = $meta['_yoast_wpseo_title'][0];
                }
                if (!empty($meta['_yoast_wpseo_metadesc'])) {
                    $metaDescription = $meta['_yoast_wpseo_metadesc'][0];
                }
            }

            $revisionMeta = get_post_meta($prevRevision->ID);
            if (!empty($revisionMeta)) {
                if (!empty($revisionMeta['_yoast_wpseo_title'])) {
                    $revisionMetaTitle = $revisionMeta['_yoast_wpseo_title'][0];
                }
                if (!empty($revisionMeta['_yoast_wpseo_metadesc'])) {
                    $revisionMetaDescription = $revisionMeta['_yoast_wpseo_metadesc'][0];
                }
            }

            $checkFields = [
                'post_title',
                'post_content',
                'post_excerpt',
            ];

            foreach ($checkFields as $field) {
                if (normalize_whitespace($post->$field) !== normalize_whitespace($prevRevision->$field)) {
                    $post_has_changed = true;
                    $fieldsChanged[] = $field;
                }
            }

            $attributes = get_fields($post->ID);
            $revisionAttributes = get_fields($prevRevision->ID);

            foreach (array_keys($attributes) as $attributeKey => $field) {
                if (!empty($attributes[$attributeKey]) && is_string($attributes[$attributeKey]) && !is_numeric($attributes[$attributeKey])) {
                    if (normalize_whitespace($attributes[$attributeKey]) !== normalize_whitespace($revisionAttributes[$attributeKey])) {
                        $post_has_changed = true;
                        $fieldsChanged[] = $field;
                    }
                }
            }

            if (!empty($attributes['faq'])) {
                if ($attributes['faq'] !== $revisionAttributes['faq']) {
                    $post_has_changed = true;
                    $fieldsChanged[] = 'faq';
                }
            }

            if (!empty($attributes['youtube_video'])) {
                if ($attributes['youtube_video'] !== $revisionAttributes['youtube_video']) {
                    $post_has_changed = true;
                    $fieldsChanged[] = 'youtube_video';
                }
            }

            if ($metaTitle !== $revisionMetaTitle) {
                $post_has_changed = true;
                $fieldsChanged[] = 'yoast_title';
            }
            if ($metaDescription !== $revisionMetaDescription) {
                $post_has_changed = true;
                $fieldsChanged[] = 'yoast_description';
            }

            /**
             * Filters whether a post has changed.
             *
             * By default a revision is saved only if one of the revisioned fields has changed.
             * This filter allows for additional checks to determine if there were changes.
             */
            $post_has_changed = (bool) apply_filters('wp_save_post_revision_post_has_changed', $post_has_changed, $prevRevision, $post);
            if ($post_has_changed && !empty($fieldsChanged)) {
                // Clean updated fields in translations table, to translate them again
                $table_name = $wpdb->prefix . 'translations';

                $sql = "UPDATE $table_name SET is_active = 0 WHERE post_id = $post_id";
                $wpdb->query($sql);

                $sql = "UPDATE $table_name SET ";
                if (in_array('faq', $fieldsChanged)) {
                    unset($fieldsChanged[array_search('faq', $fieldsChanged)]);
                    $sql .= "post_attributes = '', ";
                }
                if (in_array('youtube_video', $fieldsChanged)) {
                    unset($fieldsChanged[array_search('youtube_video', $fieldsChanged)]);
                    $sql .= "post_attributes = '', ";
                }

                foreach ($fieldsChanged as $field) {
                    $sql .= "$field = '', ";
                }

                $sql = rtrim($sql, ', ');
                $sql .= " WHERE post_id = $post_id";

                $wpdb->query($sql);
            }
        }
    }
}

add_action('admin_menu', 'custom_translations_menu');
function custom_translations_menu()
{
    add_menu_page('Custom Posts Translations', 'Custom Posts Translations', 'manage_options', 'custom-translations', 'custom_translations_page');
}

function custom_translations_page()
{
    ?>
    <div class="wrap">
        <h1>Custom Posts Translations</h1>

        <?php
        // Display data
        global $wpdb;
        $table_name = $wpdb->prefix . 'translations';
        $results = $wpdb->get_results("SELECT * FROM $table_name");
        if ($results) {
            echo '<table class="widefat"><thead><tr><th>Post ID</th><th>Lang</th><th>Updated At</th><th>Title</th><th>Is Active</th></tr></thead><tbody>';
            foreach ($results as $row) {
                echo "<tr><td><a href='/wp-admin/post.php?post={$row->post_id}&action=edit'>{$row->post_id}</a></td><td>{$row->lang}</td><td>{$row->date_modified}</td><td>{$row->post_title}</td><th>{$row->is_active}</th></tr>";
            }
            echo '</tbody></table>';
        } else {
            echo '<p>No data found.</p>';
        }
        ?>
    </div>
    <?php
}

add_action('admin_menu', 'translations_add_admin_menu');
add_action('admin_init', 'translations_settings_init');

function translations_add_admin_menu()
{
    add_options_page(
        'Translations Settings',
        'Translations',
        'manage_options',
        'translations-settings-form',
        'translation_settings_page'
    );
}

function translation_settings_page()
{
    ?>
    <form action="options.php" method="post">
        <h2>Translation Settings</h2>
        <?php
        settings_fields('translations_settings');
        do_settings_sections('translations-settings-form');
        submit_button();
        ?>
    </form>
    <?php
}

function translations_settings_init()
{
    register_setting('translations_settings', 'translation_settings');

    add_settings_section(
        'translations_section_main',
        '',
        null,
        'translations-settings-form'
    );

    add_settings_field(
        'translations_token',
        'GPT Token',
        'translations_token_render',
        'translations-settings-form',
        'translations_section_main'
    );

    add_settings_field(
        'translations_prompt',
        'GPT Prompt for generating Summary',
        'translations_prompt_render',
        'translations-settings-form',
        'translations_section_main'
    );

    add_settings_field(
        'translations_prompt_fulltext',
        'GPT Prompt for generating Summary after Translation',
        'translations_prompt_fulltext_render',
        'translations-settings-form',
        'translations_section_main'
    );
}

function translations_prompt_render()
{
    $options = get_option('translation_settings');
    ?>
    <textarea name="translation_settings[gpt_prompt]" rows="5" cols="50"><?php echo esc_attr($options['gpt_prompt'] ?? ''); ?></textarea>
    <p class="description">
        GPT prompt used for generating summary. Placeholders available: {url} - for page url, {lang} for page lang.
        <br> Ex. Generate a concise summary for the following article in {lang} language. Article URL: {url} <br/> The summary should be no more than 400 characters and should accurately reflect the content of the
        article. Return only summary text.
    </p>
    <?php
}


function translations_prompt_fulltext_render()
{
    $options = get_option('translation_settings');
    ?>
    <textarea name="translation_settings[gpt_prompt_fulltext]" rows="5" cols="50"><?php echo esc_attr($options['gpt_prompt_fulltext'] ?? ''); ?></textarea>
    <p class="description">
        GPT prompt used for generating summary after new translation. Placeholders available: {lang} for page lang.
        <br> Ex. Generate a concise summary for the following article in {lang} language. <br/> The summary should be no more than 400 characters and should accurately reflect the content of the article. Return only
        summary text.
    </p>
    <?php
}

function translations_token_render()
{
    $options = get_option('translation_settings');
    ?>
    <input type="text" name="translation_settings[gpt_token]" value="<?php echo esc_attr($options['gpt_token'] ?? ''); ?>" size="50">
    <p class="description">OpenAI API Token</p>
    <?php
}

add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'translation_settings_link');

function translation_settings_link($links)
{
    $settings_link = '<a href="options-general.php?page=translations-settings-form">' . __('Settings') . '</a>';
    array_unshift($links, $settings_link);

    return $links;
}

function sendOpenAiRequest($prompt, $token)
{
    $headers = [
        'Authorization: Bearer ' . $token,
    ];

    $messages = [
        ['role' => 'user', 'content' => $prompt],
    ];

    $url = 'https://api.openai.com/v1/chat/completions';
    $data = [
        'model' => 'gpt-4o-mini-2024-07-18',
        'messages' => $messages,
        'temperature' => 0.7,
    ];

    return sendCurl($url, $data, $headers);
}

function sendCurl($url, $body = [], $headers = [])
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);

    if (empty($body)) {
        curl_setopt($ch, CURLOPT_POST, false);
    } else {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
        $headers[] = "Content-Type: application/json";
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    }

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    $errors = curl_error($ch);
    if (!empty($errors)) {
        print_r($errors);
        saveLog(json_encode($errors));
    }

    return $response;
}

function addAuthorTranslations($user, $data, $lang)
{
    $pluginVersion = get_site_option('translationsPluginVersion');

    if (version_compare($pluginVersion, '1.0.7', '<')) {
        return $data;
    }

    $expertise = $user->expertise;
    $expertiseArray = [];
    $translatedExpertiseList = getTranslatedExpertiseList($lang);
    $translatedAuthorData = getTranslatedAuthorData($user->ID, $lang);

    if (!empty($expertise)) {
        foreach ($expertise as $exp) {
            $term = get_term_by('id', $exp, 'expertise');
            if (!empty($translatedExpertiseList[$term->term_id])) {
                $expertiseArray[] = $translatedExpertiseList[$term->term_id];
            } else {
                $expertiseArray[] = $term->name;
            }
        }

        $data['expertise'] = $expertiseArray;
    }

    if (!empty($translatedAuthorData)) {
        if (is_array($data['description']) &&
            (empty($data['description'][$lang]) || (!empty($data['description']['en']) && $data['description']['en'] == $data['description'][$lang]))
        ) {
            $data['description']['en'] = $translatedAuthorData->description;
            $data['description'][$lang] = $translatedAuthorData->description;
        } elseif (empty($data['description']) || (!empty($data['descriptionEn']) && $data['descriptionEn'] == $data['description'])) {
            $data['description'] = $translatedAuthorData->description;
        } elseif (is_array($data['description']) && !empty($data['description'][$lang])) {
            $data['description']['en'] = $data['description'][$lang];
        }

        $data['position'] = $translatedAuthorData->position;

        if (property_exists($translatedAuthorData, 'experience') && !empty($data['experience'])) {
            $translatedExpertise = json_decode($translatedAuthorData->experience);
            foreach ($data['experience'] as $key => $experience) {
                if (!empty($data['experience'][$key])) {
                    $data['experience'][$key]['years'] = $translatedExpertise[$key]->years;
                    $data['experience'][$key]['position'] = $translatedExpertise[$key]->position;
                    $data['experience'][$key]['description'] = $translatedExpertise[$key]->description;
                }
            }
        }

        if (property_exists($translatedAuthorData, 'education') && !empty($data['education'])) {
            $translatedEducation = json_decode($translatedAuthorData->education);
            foreach ($data['education'] as $key => $education) {
                if (!empty($data['education'][$key])) {
                    $data['education'][$key]['title'] = $translatedEducation[$key]->title;
                    $data['education'][$key]['subtitle'] = $translatedEducation[$key]->subtitle;
                }
            }
        }
    }

    return $data;
}

function getTranslatedExpertiseList($lang)
{
    global $wpdb, $table_prefix;

    $pluginVersion = get_site_option('translationsPluginVersion');

    if (version_compare($pluginVersion, '1.0.7', '<')) {
        return [];
    }

    $rows = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT expertise_id, name from {$table_prefix}expertise_translations WHERE lang = %s",
            $lang
        )
    );

    return array_column($rows, 'name', 'expertise_id');
}

function getTranslatedAuthorData($userId, $lang)
{
    global $wpdb, $table_prefix;

    $pluginVersion = get_site_option('translationsPluginVersion');

    if (version_compare($pluginVersion, '1.0.7', '<')) {
        return [];
    }

    $data = $wpdb->get_row(
        $wpdb->prepare(
            "SELECT * from {$table_prefix}user_translations WHERE user_id = %d AND lang = %s",
            $userId,
            $lang
        )
    );

    return $data;
}

function getTagTranslations($lang)
{
    global $wpdb, $table_prefix;

    $pluginVersion = get_site_option('translationsPluginVersion');

    if (version_compare($pluginVersion, '1.0.7', '<')) {
        return [];
    }

    $rows = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT tag_id, name from {$table_prefix}tag_translations WHERE lang = %s",
            $lang
        )
    );

    return array_column($rows, 'name', 'tag_id');
}

function saveLog($text = '')
{
    $text .= PHP_EOL;
    echo $text;
    // WP Log save but not only error
    $file = wp_upload_dir()['basedir'] . '/translations-log.txt';
    $file = fopen($file, 'a');
    fwrite($file, date('Y-m-d H:i:s') . ' - ' . $text . PHP_EOL);
    fclose($file);
}

function sendAlert($text = '') {
    $telegramToken = '8201724315:AAHJaMFNER4i14pdMniuml9bu0oU7d90K0g';
    $userIds = [203456552, 517650898];
    $iniUrl = "https://api.telegram.org/bot{$telegramToken}";
    $finalUrl = $iniUrl . '/sendMessage';

    foreach ($userIds as $userId) {
        $body = [
            "chat_id" => $userId,
            "text" => $text,
        ];

        sendCurl($finalUrl, $body);
    }
}