thomask
Forum Replies Created
-
Forum: Plugins
In reply to: [AI Translate For Polylang] Love it. Few small bugssolved for excerpt (new hook added and copied) and changeg prompt to textarea
<?php
/*
Plugin Name: AI Translate For Polylang
Plugin URI: https://wordpress.org/plugins/ai-translate-polylang/
Description: Add auto AI translation caperbility to Polylang
Version: 1.1.4
Author: James Low
Author URI: http://jameslow.com
License: MIT License
*/
/*
Next Version:
- Auto translate on publish post (publish/save as draft)
- Setting to only auto translate for certain categories/pages
*/
namespace AI_Translate_Polylang;
class AI_Translate_Polylang {
//Constants
public static $PROMPT = 'Translate the content from {FROM_CODE} to {TO_CODE} preserving html, formatting and embedded media. Only return the new content.';
public static $OPENAI_MODEL = 'gpt-4o';
public static $CLAUDE_MODEL = 'claude-3-5-sonnet-20240620';
public static $GEMINI_MODEL = 'gemini-2.5-flash';
//Variables
public static $meta_translate;
public static $meta_clear;
/* Helper functions */
public static function require_settings() {
if (class_exists('\PageApp')) {
\PageApp::require_settings();
} else {
require_once 'inc/settingslib.php';
}
}
public static function require_utils() {
if (class_exists('\PageApp')) {
\PageApp::require_utils();
} else {
require_once 'inc/utilslib.php';
}
}
public static function require_openai() {
require_once 'inc/open-ai/Url.php';
require_once 'inc/open-ai/OpenAi.php';
}
/* Hooks */
public static function add_hooks() {
add_action('init', array(static::class, 'init'), 11);
add_filter('default_title', array(static::class, 'default_title'), 10, 2);
add_filter('default_content', array(static::class, 'default_content'), 10, 2);
add_filter('default_excerpt', array(static::class, 'default_excerpt'), 10, 2);
//add_filter('pll_copy_post_metas', array(static::class, 'pll_copy_post_metas'), 11, 5); //This gets called, but doesn't clear out keys
add_filter('pll_translate_post_meta', array(static::class, 'pll_translate_post_meta'), 10, 3);
}
public static function init() {
self::require_settings();
$settings = new \SettingsLib(array(
array('id'=>'ai_translate_new_post', 'type'=>'boolean', 'title'=>'Auto Translate New Translation Posts', 'default'=>'1'),
array('id'=>'ai_translate_prompt', 'type'=>'text', 'title'=>'Custom Prompt', 'description'=>'{FROM_CODE} and {TO_CODE} will be replaced by from and to languages.', 'default'=>self::$PROMPT),
array('id'=>'ai_translate_llm', 'type'=>'select', 'title'=>'LLM Service', 'default'=>'OpenAI', 'values'=>array(
'OpenAI',
'Claude',
'Gemini'
)),
array('id'=>'ai_translate_openai', 'type'=>'title', 'title'=>'OpenAI', 'description'=>''),
array('id'=>'ai_translate_openai_key', 'type'=>'string', 'title'=>'OpenAI API Key', 'description'=>''),
array('id'=>'ai_translate_openai_org', 'type'=>'string', 'title'=>'OpenAI Organization', 'description'=>'(Optional)'),
array('id'=>'ai_translate_openai_model', 'type'=>'string', 'title'=>'OpenAI Model', 'default'=>self::$OPENAI_MODEL, 'values'=>array(
'gpt-4o',
'gpt-4o-mini',
'gpt-4-turbo',
'gpt-4',
'gpt-3.5-turbo'
)),
array('id'=>'ai_translate_claude', 'type'=>'title', 'title'=>'Claude', 'description'=>''),
array('id'=>'ai_translate_claude_key', 'type'=>'string', 'title'=>'Claude API Key', 'description'=>''),
array('id'=>'ai_translate_claude_model', 'type'=>'string', 'title'=>'OpenAI Model', 'default'=>self::$CLAUDE_MODEL, 'values'=>array(
'claude-3-5-sonnet-20240620',
'claude-3-opus-20240229',
'claude-3-sonnet-20240229',
'claude-3-haiku-20240307'
)),
array('id'=>'ai_translate_gemini', 'type'=>'title', 'title'=>'Gemini', 'description'=>''),
array('id'=>'ai_translate_gemini_key', 'type'=>'string', 'title'=>'Gemini API Key', 'description'=>''),
array('id'=>'ai_translate_gemini_model', 'type'=>'string', 'title'=>'Gemini Model', 'default'=>self::$GEMINI_MODEL, 'values'=>array(
'gemini-2.5-flash',
'gemini-2.0-flash',
'gemini-1.5-flash',
'gemini-1.5-pro'
)),
array('id'=>'ai_translate_meta', 'type'=>'title', 'title'=>'Meta', 'description'=>''),
array('id'=>'ai_translate_meta_clear', 'type'=>'text', 'title'=>'Meta keys to clear', 'description'=>'', 'default'=>''),
array('id'=>'ai_translate_meta_translate', 'type'=>'text', 'title'=>'Meta keys to translate', 'description'=>'', 'default'=>''),
), 'AI Translate', 'mlang', false, 'manage_options', null, null, '', '_');
}
public static function default_title($title, $post) {
$pattern = '/[^\p{L}\p{N}]+$/u'; //Remove trailing not alpha numeric characters
return preg_replace($pattern, '', wp_strip_all_tags(self::translate_field($title, 'post_title')));
}
public static function default_content($content, $post) {
return self::translate_field($content, 'post_content');
}
public static function default_excerpt($excerpt, $post) {
return self::translate_field($excerpt, 'post_excerpt');
}
public static function pll_copy_post_metas($keys, $sync, $from, $to, $lang) {
$keys = array_diff($keys, self::meta_clear());
return $keys;
}
public static function pll_translate_post_meta($value, $key, $lang) {
if (in_array($key, self::meta_clear())) {
$value = '';
} else if (in_array($key, self::meta_translate())) {
$value = self::translate_field($value, $key, true);
}
return $value;
}
private static function meta_keys($option) {
$clear = get_option($option);
if ($clear) {
return preg_split('/\s+/', $clear);
} else {
return array();
}
}
private static function meta_clear() {
if (!self::$meta_clear) {
self::$meta_clear = self::meta_keys('ai_translate_meta_clear');
}
return self::$meta_clear;
}
private static function meta_translate(){
if (!self::$meta_translate) {
self::$meta_translate = self::meta_keys('ai_translate_meta_translate');
}
return self::$meta_translate;
}
/* Translation */
public static function translate_field($original, $field = '', $meta = false) {
$translation = null;
if (get_option('ai_translate_new_post', '0') == '1' && isset($_GET['new_lang']) && $_GET['new_lang'] && isset($_GET['from_post'])) {
if (!$original || $original != '') {
$to = sanitize_key($_GET['new_lang']);
$post_id = sanitize_key($_GET['from_post']);
if ($field) {
if ($meta) {
$original = get_post_meta($post_id, $field, true);
} else {
$post = get_post($post_id);
$original = $post->$field;
}
}
}
$translation = self::translate($original, $to, pll_get_post_language($post_id));
} else {
$translation = $original;
}
return $translation;
}
public static function prompt($to, $from = 'en') {
return str_replace('{TO_CODE}', $to, str_replace('{FROM_CODE}', $from, get_option('ai_translate_prompt', self::$PROMPT)));
}
public static function translate($text, $to, $from = 'en') {
if ($text && trim($text) != '') {
$prompt = self::prompt($to, $from);
$service = get_option('ai_translate_llm', 'OpenAI');
if ($service == 'Claude') {
$result = self::claude_api($text, $prompt);
$body = json_decode($result['body'], true);
if ($result['response']['code'] == 200) {
return $body['content'][0]['text'];
} else {
return 'ERROR: '.$body['error']['message'];
}
} elseif ($service == 'Gemini') {
$result = self::gemini_api($text, $prompt);
$body = json_decode($result['body'], true);
if ($result['response']['code'] == 200) {
return $body['candidates'][0]['content']['parts'][0]['text'];
} else {
return 'ERROR: '.$body['error']['message'];
}
} else {
$result = self::openai_api($text, $prompt);
if (!isset($result['error'])) {
$translation = $result['choices'][0]['message']['content'];
} else {
$translation = 'ERROR: '.$result['error']['message'];
}
return $translation;
}
} else {
return '';
}
}
public static $author = 0;
public static function wp_insert_post_data($data , $postarr) {
$data['post_author'] = self::$author;
self::$author = 0;
remove_filter('wp_insert_post_data', array(static::class, 'wp_insert_post_data'), 99);
return $data;
}
public static function translate_post($post_id, $to, $status = 'publish', $overwrite = false) {
$from = pll_get_post_language($post_id);
$translation = pll_get_post($post_id, $to);
if (!$translation || get_post_status($translation) === false || $overwrite) {
$post = get_post($post_id);
self::$author = $post->post_author;
add_filter('wp_insert_post_data', array(static::class, 'wp_insert_post_data'), '99', 2);
if (!$translation) {
// Create a new post with the same content
$translation = wp_insert_post([
'post_status' => 'draft',
'post_title' => $post->post_title." ($to)",
'post_content' => ' ',
'post_type' => $post->post_type,
'post_author' => $post->post_author, //Wordpress overrides author to 0, hence hook
'post_date' => $post->post_date,
'post_date_gmt' => $post->post_date_gmt,
'post_modified' => $post->post_modified,
'post_modified_gmt' => $post->post_modified_gmt,
'post_excerpt' => $post->post_excerpt
]);
// Set the language for the new post first in case something goes wrong
pll_set_post_language($translation, $to);
}
wp_update_post(array(
'ID' => $translation,
'post_title' => self::translate($post->post_title, $to, $from),
'post_content' => self::translate($post->post_content, $to, $from),
'post_excerpt' => self::translate($post->post_excerpt, $to, $from),
'post_status' => $status
));
// Duplicate post meta
$meta = get_post_meta($post_id);
foreach ($meta as $key => $values) {
foreach ($values as $value) {
$value = maybe_unserialize($value);
if (in_array($key, self::meta_clear())) {
$value = '';
} else if (in_array($key, self::meta_translate())) {
$value = self::translate($value, $to, $from);
}
add_post_meta($translation, $key, $value);
}
}
//TODO: Should we use PLL_Sync_Tax->copy($from,$to, $lang)
// Get the translated term IDs for categories
$categories = get_the_category($post_id);
$translated_category_ids = [];
foreach ($categories as $category) {
$translated_category_id = pll_get_term($category->term_id, $to);
if ($translated_category_id) {
$translated_category_ids[] = $translated_category_id;
}
}
wp_set_post_categories($translation, $translated_category_ids);
// Get the translated term IDs for tags
$tags = wp_get_post_tags($post_id);
$translated_tag_ids = [];
foreach ($tags as $tag) {
$translated_tag_id = pll_get_term($tag->term_id, $to);
if ($translated_tag_id) {
$translated_tag_ids[] = $translated_tag_id;
}
}
wp_set_post_tags($translation, $translated_tag_ids);
// Link the new translation with the original post
pll_save_post_translations([
$to => $translation,
pll_get_post_language($post_id) => $post_id,
]);
}
return $translation;
}
/* APIs */
public static function claude_api($content, $role = 'You are a helpful assistant.', $tokens = 5000, $temp = 0) {
$request = new \WP_Http();
$headers = array(
'x-api-key' => get_option('ai_translate_claude_key', ''),
'anthropic-version' => '2023-06-01',
'Content-Type' => 'application/json',
);
$message = array(
'model' => get_option('ai_translate_claude_model', self::$CLAUDE_MODEL),
'max_tokens' => $tokens,
'temperature' => $temp,
'system' => $role,
'messages' => array(
array("role" => 'user', "content" => $content)
)
);
$args = array(
'method' => 'POST',
'headers' => $headers,
'body' => json_encode($message),
'timeout' => 60,
);
return $request->request('https://api.anthropic.com/v1/messages', $args);
//https://www.datacamp.com/tutorial/getting-started-with-claude-3-and-the-claude-3-api
//return $request->request('https://api.anthropic.com/v1/complete', $args);
}
public static function openai() {
self::require_openai();
//Create new open ai every time, otherwise it preserves conversation between calls and gets confused translating title/content
$openai = new \Orhanerday\OpenAi\OpenAi(get_option('ai_translate_openai_key', ''));
if ($org = get_option('ai_translate_openai_org')) {
$openai->setORG($org);
}
return $openai;
}
public static function openai_api($content, $role = 'You are a helpful assistant.', $tokens = 5000) {
//https://packagist.org/packages/orhanerday/open-ai
return json_decode(self::openai()->chat([
'model' => get_option('ai_translate_openai_model', self::$OPENAI_MODEL),
'messages' => [
[
"role" => "system",
"content" => "$role"
],
[
"role" => "user",
"content" => "$content"
]
],
'temperature' => 1.0,
'max_tokens' => $tokens,
'frequency_penalty' => 0,
'presence_penalty' => 0,
]), true);
}
public static function gemini_api($content, $role = 'You are a helpful assistant.') {
//https://ai.google.dev/gemini-api/docs/text-generation#rest_2
$request = new \WP_Http();
$headers = array(
'Content-Type' => 'application/json',
);
$message = array(
'system_instruction' => array(
'parts' => array(
array('text' => $role)
)
),
'contents' => array(
'parts' => array(
array('text' => $content)
)
)
);
$args = array(
'method' => 'POST',
'headers' => $headers,
'body' => json_encode($message),
'timeout' => 60,
);
return $request->request('https://generativelanguage.googleapis.com/v1beta/models/'.get_option('ai_translate_gemini_model', self::$GEMINI_MODEL).':generateContent?key='.get_option('ai_translate_gemini_key', ''), $args);
}
}
AI_Translate_Polylang::add_hooks();This issue is 5 months old and still not solved. Any news?
yep, i know, thank you, i just wanted you to be aware of them, no prob for me.
And some more
Deprecated: Creation of dynamic property Google\Site_Kit\Core\Authentication\Setup::$proxy_support_link_url is deprecated inΒ /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/google-site-kit/includes/Core/Authentication/Setup.phpΒ on lineΒ 94
Deprecated: stripos(): Passing null to parameter #1 ($haystack) of type string is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/google-site-kit/includes/Core/REST_API/REST_Routes.php on line 69
Deprecated: Creation of dynamic property Google\Site_Kit\Modules\Analytics\Web_Tag::$module_slug is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/google-site-kit/includes/Core/Modules/Tags/Module_Tag.php on line 42
Deprecated: Creation of dynamic property Google\Site_Kit\Modules\Analytics_4\Web_Tag::$module_slug is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/google-site-kit/includes/Core/Modules/Tags/Module_Tag.php on line 42
Forum: Plugins
In reply to: [Translation with DeepL API] deprecated error codesand
Deprecated: Creation of dynamic property DeepLApiUsage::$languages is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/wpdeepl/client/deeplapi.class.php on line 35
Deprecated: Creation of dynamic property DeepLApiUsage::$doing_cron is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/wpdeepl/client/deeplapi.class.php on line 41
Deprecated: Creation of dynamic property DeepLApiUsage::$instance is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/wpdeepl/client/deeplapi.class.php on line 44
Deprecated: Creation of dynamic property DeepLApiUsage::$result is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/wpdeepl/client/deeplapi.class.php on line 184
Deprecated: Creation of dynamic property DeepLApiUsage::$why_no_cache is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/wpdeepl/client/deepl-data.class.php on line 8
Deprecated: Creation of dynamic property DeepLApiUsage::$final_request is deprecated in /var/www/vhosts/powerbox.one/httpdocs/wp-content/plugins/wpdeepl/client/deeplapi.class.php on line 98
Forum: Fixing WordPress
In reply to: WordPress v6.2.1 Breaks the Shortcode Block in TemplatesWhat??? Give it back. Now.
There aare many reasons, why shortcodes should be allowed in templates. One of them for me is that they can be inline, something, what is not valid for any other blocks. So e. g. On one of my webs I have a short code [total], that shows total number of my listing’s, and I can then put it in any sentence, any paragraph, heading etc, like “we do have [total] listings. There is currently now other easy way how to do it in WordPress.
I uunderstand, that often plugin authors use shortcodes instead of blocks, and it destroys the block wysiwig feeling for the users. And I am totally open to find a solution for this problem. But not without discussing by breaking functionality on live websites in third level update. This is insane.
BTW you should ask, why plugin authors do it this way. You will probably find out, that they simply do not want to learn React, as they are whole live PHP programmers. Make an easy way, how to create wysiwig block, without need to use node, learning JS etc…. Simply the same way how they can do it now with shortcodes with one simple short function or with some admininstation, and they will not have to use shortcodes.
same for me. I guess some problems with the latest versions of PHP or WP
Forum: Plugins
In reply to: [WordPress Slider Block Gutenslider] fatal errorWP 5.8.1., PHP 8 (some latest)
The problem probably occurs, when user duplicate a block / one slide and then edits it. I need to watch the user what is he doing, it has never happened to me on the same instalation. But still a good plugin should never fire fatal errors on some post edit action of non-admin user (it was wired on both frontend and admin edit post, so i had to turn off the plugin and then delete the added slide to revert it).
Forum: Plugins
In reply to: [WordPress Slider Block Gutenslider] fatal errorand another one after he tried it again
Fatal error: Uncaught ArgumentCountError: 9 arguments are required, 3 given in /var/www/vhosts/sciencein.cz/httpdocs/wp-content/plugins/gutenslider/src/blocks/gutenslide/block-front.php:280 Stack trace: #0 /var/www/vhosts/sciencein.cz/httpdocs/wp-content/plugins/gutenslider/src/blocks/gutenslide/block-front.php(280): sprintf() #1 /var/www/vhosts/sciencein.cz/httpdocs/wp-content/plugins/gutenberg/lib/compat.php(132): eedee_gutenslide_dynamic_render_callback() #2 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/class-wp-block.php(221): {closure}() #3 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/class-wp-block.php(211): WP_Block->render() #4 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/blocks.php(868): WP_Block->render() #5 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/blocks.php(906): render_block() #6 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/class-wp-hook.php(303): do_blocks() #7 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/plugin.php(189): WP_Hook->apply_filters() #8 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/post-template.php(253): apply_filters() #9 /var/www/vhosts/sciencein.cz/httpdocs/wp-content/themes/generatepress/content-page.php(73): the_content() #10 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/template.php(772): require('...') #11 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/template.php(716): load_template() #12 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/general-template.php(204): locate_template() #13 /var/www/vhosts/sciencein.cz/httpdocs/wp-content/themes/generatepress/inc/theme-functions.php(568): get_template_part() #14 /var/www/vhosts/sciencein.cz/httpdocs/wp-content/themes/generatepress/page.php(34): generate_do_template_part() #15 /var/www/vhosts/sciencein.cz/httpdocs/wp-includes/template-loader.php(106): include('...') #16 /var/www/vhosts/sciencein.cz/httpdocs/wp-blog-header.php(19): require_once('...') #17 /var/www/vhosts/sciencein.cz/httpdocs/index.php(17): require('...') #18 {main} thrown in /var/www/vhosts/sciencein.cz/httpdocs/wp-content/plugins/gutenslider/src/blocks/gutenslide/block-front.php on line 280you clearly have many uncatched fatal errors, i will change the slider plugin
Dear Dave, i do not care HOW you do it, i can probably imagine how you could do it. What i say is that your methods are probably bugy or not working at all, when 100 % of typical bot visits you declare as human. If i would show even one bot in all the visits, i would say, that it just works sub-optimally, but when 100 % of thousands and thousands of visits are defined as human, than i guess it does not work at all.
P.S.: wordfence has no javascript on the page if user is not logged in, so I have no clue how you would be able to observe activity on page with javascript.
Oh @wfgerald, i see. The problem is, that i use the latest jQuery version (3.4.1), not that wp built-in from several years old ver. 1.2 branch. Interesting, you are the only plugin, that got problems with that.
P.S: this is my setup if you want to test it with modern jquery//Remove JQuery migrate function remove_jquery_migrate($scripts) { if (!is_admin() && isset($scripts->registered['jquery'])) { $script = $scripts->registered['jquery']; if ($script->deps) { // Check whether the script has any dependencies $script->deps = array_diff($script->deps, array( 'jquery-migrate' )); } } } add_action('wp_default_scripts', 'remove_jquery_migrate'); //Making jQuery to load from Google Library function replace_jquery() { if (!is_admin()) { // comment out the next two lines to load the local copy of jQuery wp_deregister_script('jquery'); wp_register_script('jquery', '//code.jquery.com/jquery-3.4.1.slim.min.js', false, NULL,true); wp_enqueue_script('jquery'); wp_script_add_data( 'jquery', 'integrity', 'sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8='); wp_script_add_data( 'jquery', 'crossorigin', 'anonymous' ); } } add_action('init', 'replace_jquery');Forum: Plugins
In reply to: [Gutenberg Block Editor Toolkit β EditorsKit] hide title conflictI have found more serious problem with this issue – the title is hidden also from admin area when adding the link to document with a hidden title.
It is strange and IMO a bug in WordPress, because you are calling !is_admin condition, but as i am googling it, the problem is, that that add link popup uses external json call, e.g. see
https://stackoverflow.com/questions/39115564/wordpress-hook-filter-to-process-link-inside-a-post/39176559
I will try to find the filter how to solve it, but maybe you will be quicker as you are clearly more experienced dev. πForum: Plugins
In reply to: [Gutenberg Block Editor Toolkit β EditorsKit] hide title conflictjust for info if someone has the same problem – sometimes it can be “solved” by turning the filter off before the other plugin call and then turn it on back. E.g. for my breadcrumb problem, you can use:
remove_filter( 'the_title', 'editorskit_hide_title', 10, 2 ); bcn_display(); add_filter( 'the_title', 'editorskit_hide_title', 10, 2 );Forum: Plugins
In reply to: [Custom Post Type UI] remove the metabox for CPT does not workyou are right Michael – the problem is in the gutenberg. I can turn it off for this CPT so problem solved for me now π
Thank you
Forum: Plugins
In reply to: [Custom Post Type UI] remove the metabox for CPT does not workAlso from uknown reason metabox of the taxonomy is not in metabox array so I do not know how to delete id via action
add_action( ‘add_meta_boxes’, function() {
global $wp_meta_boxes;
var_dump($wp_meta_boxes);}, 999);