Polylang automatic sync for posts - wordpress

I'm working on a multilingual wordpress website using the Polylang plug-in on pages and custom post types.
What I'm looking for is a way to have every post synch automatically, without user input. When creating a new post type, a translation would be automatically created and all contents copied.
So the user wouldn't see this panel at all, or at least not have the chance to edit the translation or (especially) turn the sync off. I guess this could be done by changing user roles privileges but the post would definitely have to automatically sync.
I checked this article but it didn't do anything.

Needed something similar, dug up this undocumented function:
global $polylang;
// third parameter sets synchronisation
$polylang->sync_post_model->copy_post($post_id, $lang, true);
This duplicates the content to the chosen language and enables synchronisation.
Example using 'save_post:
function auto_translate($post_id, $post, $update)
{
if (!$update) {
return;
}
// prevent recursion when publishing translations
remove_action('save_post', 'auto_translate', 999, 3);
global $polylang;
$langs = ['nb', 'se', 'dk'];
$current_translations = pll_get_post_translations($post_id);
foreach ($langs as $lang) {
if (!isset($current_translations[$lang])) {
$polylang->sync_post_model->copy_post($post_id, $lang, true);
}
}
}
// needs low priority or the synchronisation option wont be saved
add_action('save_post', 'auto_translate', 999, 3);

The solution offered by steinoy is good but generates an additional unwanted draft post. Here's an improved solution:
function auto_translate($post_id, $post, $update)
{
if (!$update) {
return;
}
// prevent creation of additional duplicate draft posts
if ( $post->post_status == 'draft' ){
return;
}
// prevent recursion when publishing translations
remove_action('save_post', 'auto_translate', 999, 3);
global $polylang;
$langs = ['nb', 'se', 'dk'];
$current_translations = pll_get_post_translations($post_id);
$post_type = get_post_type($post_id);
if ($post_type == 'post') {
foreach ($langs as $lang) {
if (!isset($current_translations[$lang])) {
$polylang->sync_post_model->copy_post($post_id, $lang, true);
}
}
}
}
// needs low priority or the synchronisation option wont be saved
add_action('save_post', 'auto_translate', 999, 3);

Related

WordPress prevent delete taxonomy

I would like to prevent that some categories are accidentally deleted. For this I use a meta entry for the category to be protected.
I use the following code for this:
// edit: wrong hook! ** add_action( 'delete_term_taxonomy', 'taxonomy_delete_protection', 10, 1 );
add_action( 'pre_delete_term', 'taxonomy_delete_protection', 10, 1 );
function taxonomy_delete_protection ( $term_id )
{
if (get_term_meta ($term_id, 'delete-protect', true) === true)
{
wp_die('Cannot delete this category');
}
}
Unfortunately, instead of my error message, only "Something went wrong" is displayed. Why?
Edit: The `delete_term_taxonomy` is the wrong hook for my code, because it deleted the meta before i can check the meta entry. `pre_delete_term` does fire before anything happens with the category.
The "Why" is because of the following JavaScript that ships with WordPress:
$.post(ajaxurl, data, function(r){
if ( '1' == r ) {
$('#ajax-response').empty();
tr.fadeOut('normal', function(){ tr.remove(); });
/**
* Removes the term from the parent box and the tag cloud.
*
* `data.match(/tag_ID=(\d+)/)[1]` matches the term ID from the data variable.
* This term ID is then used to select the relevant HTML elements:
* The parent box and the tag cloud.
*/
$('select#parent option[value="' + data.match(/tag_ID=(\d+)/)[1] + '"]').remove();
$('a.tag-link-' + data.match(/tag_ID=(\d+)/)[1]).remove();
} else if ( '-1' == r ) {
$('#ajax-response').empty().append('<div class="error"><p>' + wp.i18n.__( 'Sorry, you are not allowed to do that.' ) + '</p></div>');
tr.children().css('backgroundColor', '');
} else {
$('#ajax-response').empty().append('<div class="error"><p>' + wp.i18n.__( 'Something went wrong.' ) + '</p></div>');
tr.children().css('backgroundColor', '');
}
});
The expected response to this POST request is:
'1' if the term was deleted
'-1' if your user doesn't have permission to delete the term.
For all other cases, "Something went wrong" is displayed.
You are terminating the script early with wp_die, yielding an unexpected response, which comes under "other cases".
There isn't a way to provide a custom error message in the notice box here without writing some JavaScript of your own.
This is my current solution, not perfect but it works.
The "Something went wrong" message show up if you delete the taxonomy with the row action. So i unset the "delete" action so it couldn't be triggered this way.
add_filter ('category_row_actions', 'unset_taxonomy_row_actions', 10, 2);
function unset_taxonomy_row_actions ($actions, $term)
{
$delete_protected = get_term_meta ($term->term_id, 'delete-protect', true);
if ($delete_protected)
{
unset ($actions['delete']);
}
return $actions;
}
Then i hide the "Delete" Link in the taxonomy edit form with css. It's still could be triggered if you inspect the site and it's link, but there is no hook to remove this action otherwise.
add_action( 'category_edit_form', 'remove_delete_edit_term_form', 10, 2 );
function remove_delete_edit_term_form ($term, $taxonomy)
{
$delete_protected = get_term_meta ($term->term_id, 'delete-protect', true);
if ($delete_protected)
{
// insert css
echo '<style type="text/css">#delete-link {display: none !important;}</style>';
}
}
Finally the check before deleting the taxonomy. This should catch all other ways, like the bulk action "delete". I didn't found another way yet to stop the script from deleting the taxonomy.
add_action ('pre_delete_term', 'taxonomy_delete_protection', 10, 1 );
function taxonomy_delete_protection ( $term_id )
{
$delete_protected = get_term_meta ($term_id, 'delete-protect', true);
if ($delete_protected)
{
$term = get_term ($term_id);
$error = new WP_Error ();
$error->add (1, '<h2>Delete Protection Active!</h2>You cannot delete "' . $term->name . '"!');
wp_die ($error);
}
}
This solution provides a way to disable all categories from being deleted by a non Admin. This is for anyone like myself who's been searching.
function disable_delete_cat() {
global $wp_taxonomies;
if(!current_user_can('administrator')){
$wp_taxonomies[ 'category' ]->cap->delete_terms = 'do_not_allow';
}
}
add_action('init','disable_delete_cat');
The easiest solution (that will automatically take care of all different places where you can possibly delete the category/term) and in my opinion the most flexible one is using the user_has_cap hook:
function maybeDoNotAllowDeletion($allcaps, $caps, array $args, $user)
{
if ($args[0] !== 'delete_term') return $allcaps;
// you can skip protection for any user here
// let's say that for the default admin with id === 1
if ($args[1] === 1) return $allcaps;
$termId = $args[2];
$term = get_term($termId);
// you can skip protection for all taxonomies except
// some special one - let's say it is called 'sections'
if ($term->taxonomy !== 'sections') return $allcaps;
// you can protect only selected set of terms from
// the 'sections' taxonomy here
$protectedTermIds = [23, 122, 3234];
if (in_array($termId, $protectedTermIds )) {
$allcaps['delete_categories'] = false;
// if you have some custom caps set
$allcaps['delete_sections'] = false;
}
return $allcaps;
}
add_filter('user_has_cap', 'maybeDoNotAllowDeletion', 10, 4);

woocommerce_update_product action – fire only once for every product update

Which workaround is there for making the woocommerce_update_product action fire only once?
I've read that it fires twice because it needs to save once internally to retrieve an ID for images/variation saves.
But from a developer experience, this is really not what most need, I guess.
The only workaround I've found so far is to add the action and remove it in the hook directly:
add_action('woocommerce_update_product', 'my_product_update', 10, 2);
function my_product_update($product_id, $product){
remove_action('woocommerce_update_product');
// We'll get here only once!
}
However, this breaks when trying to do bulk edits, making it so that the hook only fires for the first product (because it gets removed afterwards!).
Which other way is there to work around this issue?
Thanks!
Maybe using WordPress Transient can help.
add_action('woocommerce_update_product', 'my_product_update', 10, 2);
function my_product_update($product_id, $product) {
$updating_product_id = 'update_product_' . $product_id;
if ( false === ( $updating_product = get_transient( $updating_product_id ) ) ) {
// We'll get here only once! within 2 seconds for each product id;
// run your code here!
set_transient( $updating_product_id , $product_id, 2 ); // change 2 seconds if not enough
}
}
Use a global variable to do this in memory, no database entry required:
add_action('woocommerce_update_product', 'my_product_update', 10, 2);
function my_product_update($product_id, $product){
global $previous_product_id;
if ($previous_product_id === $product_id){
// We'll get here only once (per product)!
}
$previous_product_id = $product_id;
}
i ran into the same issue (actually my hook was firing 5 times). I found a solution on the following page in the last comment.
Namely, the use of:
$times = did_action('woocommerce_update_product');
if( $times === 1){
// Do some stuff
}
A simpler solution would be to set a $_POST variable and check at the start of the function.
add_action( 'woocommerce_update_product', 'my_action', 10, 1 );
function my_action(){
if( isset($_POST['action_performed']) ){
//Prevent running the action twice
return;
}
// do you stuff here
$_POST['action_performed'] = true;
}

WooCommerce product update – check if a field's value has changed

I'm using the following code to hook a product update in woocommerce:
add_action('woocommerce_update_product', 'on_update_product', 10, 2);
function on_update_product($product_id, $product){
// code here
}
Is there a way to check if certain fields have changed, compared to the previously stored version of the product?
Thanks!
The best way to do this that I know is with hashes.
add_action('woocommerce_update_product', 'on_update_product', 10, 2);
function on_update_product($product_id, $product){
//create a hash from data you want to track
$hash = md5(json_encode([
$product->get_name(),
$product->get_price(),
"etc....."
]));
//get the hash before the product update
$hashBefore = get_post_meta( $product_id, "hashKey", true );
//check if de hash is diffrend
if ($hash !== $hashBefore) {
// Store the new hash
add_post_meta($product_id, "hashKey", $hash);
// exicute your code
// .....
}
// you can duplicate this process if you want to track individual fields
$hash2 = md5(json_encode([
$product->get_sku(),
]));
$hashBefore2 = get_post_meta( $product_id, "hashKey2", true );
if ($hash2 !== $hashBefore2) {
add_post_meta($product_id, "hashKey2", $hash2);
}
}
To get data out of the product object check this resource:
https://businessbloomer.com/woocommerce-easily-get-product-info-title-sku-desc-product-object/
I hope this suits your situation
I would recommend hooking to another action. I use it to identify changes in orders, but it actually can use for any woocomercce related object types (orders, products, coupons, subscriptions etc.)
woocommerce_before_[objectName]_object_save
for your purpose you can use:
add_action('woocommerce_before_product_object_save', 'identify_product_change', 100, 2);
function identify_product_change($product, $data){
$posted_info = $_POST; // Use this to get the new information
$price = $product->get_price(); //Example of getting the "old" product information
}
Having that said, you need to be careful, since this hook may be initiated from different triggers (some background processes etc). You may want to have some caution measurements:
use $_POST['action'] == 'editpost' to make sure the action is an
actual "Update" click from the admin edit page.
use (is_admin()) to limit it only to admin area
you can use (!defined('DOING_CRON')) to make sure it won't run on any cron execution
and you can use (!defined('DOING_AJAX')) to make sure it won't run on ajax calls
this way you can limit it only to the exact action you wish to catch.

wordpress remove post status count from cms

I want to remove the post status count from WordPress edit.php.
My WordPress CMS have more than 500,000 posts. The publish count is loaded every time you open the page. The following query is fired every time.
This makes my Wordpress CMS loading very slow.
SELECT post_status, COUNT( * ) AS num_posts FROM wp_posts WHERE post_type = 'post' GROUP BY post_status
By tracing the code, I've worked up the only solution that I can see.
The filter bulk_post_updated_messages is ONLY called on the edit.php screen.
The counts are not calculated (in class-wp-posts-list-table.php, get_views method) if the global variable $locked_post_status is not empty.
By gluing these two pieces of information together, I've got a solution that you can use by dropping it into your theme's functions.php file:
// Hook this filter, only called on the `edit.php` screen.
// It's not the "correct" filter, but it's the only one we can leverage
// so we're hijacking it a bit.
add_filter('bulk_post_updated_messages', 'suppress_counts', 10, 2);
// We need to let the function "pass through" the intended filter content, so accept the variable $bulk_messages
function suppress_counts($bulk_messages) {
// If the GET "post_type" is not set, then it's the "posts" type
$post_type = (isset($_GET['post_type'])) ? $_GET['post_type'] : 'post';
// List any post types you would like to KEEP counts for in this array
$exclude_post_types = array('page');
// Global in the variable so we can modify it
global $locked_post_status;
// If the post type is not in the "Exclude" list, then set the $locked variable
if ( ! in_array($post_type, $exclude_post_types)) {
$locked_post_status = TRUE;
}
// Don't forget to return this so the filtered content still displays!
return $bulk_messages;
}
i came up with this solution.
//Disable Article Counter - query runs for about 1-2 seconds
add_filter('admin_init', function () {
foreach (get_post_types() as $type) {
$cache_key = _count_posts_cache_key($type, "readable");
$counts = array_fill_keys(get_post_stati(), 1);
wp_cache_set($cache_key, (object)$counts, 'counts');
}
}, -1);
add_action('admin_head', function () {
$css = '<style>';
$css .= '.subsubsub a .count { display: none; }';
$css .= '</style>';
echo $css;
});
the post counter uses the wp-cache, the idea behind this approach is, to prefill the cache with the "correct" object containing 1's (0 would skip the status from being clickable) - at the earliest moment.
it results in all stati being displayed - with 1's and the query is not run et-all
in addition it returns a css snippet to hide the (1)

Wordpress filter on adding meta?

In wordpress, I need to program it such that anytime someone enters or updates a post meta called "start_date", a bit of code is run on what is entered before it is saved.
I need to take what is entered and convert it to a unix timestamp.
Is there a way to do this?
If not, is there a way to add the code on publish or update of the post such that it checks for that meta and updates it if needed?
Assuming you're creating the metaboxes and custom fields with your plugin, you can do the following. Otherwise, it depends on how their saving the data as it could overwrite yours.
Here's something to get you started though depending on what the case is.
add_action('save_post', 'update_the_post_meta', 100, 2);
function update_the_post_meta($post_id, $post) {
if ( defined('DOING_AJAX') && DOING_AJAX ) { return; }
if ( defined('DOING_CRON') && DOING_CRON ) { return; }
if ($post->post_type == 'revision') { return; }
if ( isset($_REQUEST['start_date']) ) :
//do your timestamp code here and save it in $timestamp
add_post_meta($post_id, 'start_date', $timestamp, true) or update_post_meta($post_id, 'start_date', $timestamp);
else :
delete_post_meta($post_id, 'start_date');
endif;
}
Right now the priority of the add_action is set to 100 (the higher the number, the less priority it has). So, if you're trying to override someone else's function, you may need to increase the priority number. Also, this is assuming the name of the input field is "start_date".

Resources