Why does looping through an API create duplciate wordpress posts - php

I'm trying to iterate over some API data and turn it into custom WordPress posts.
My function works, but I can't stop it from creating duplicate posts.
I am checking a unique unitproperty_id field on each iteration to avoid creating duplicates, but it only works in a sporadic way.
Sometimes there will be no duplicate posts, sometimes there will be two, and sometimes three.
It also only happens on the first pass, after the posts have been created (duplicates and non-duplicates) it won't keep creating them each time. The duplicates only appear on the first load.
When this function is working correctly I'll be calling it on a regular basis using wp_cron job, but until then I am calling it using the shutdown action hook that runs at the very end of WordPress loading.
Can you see why I this is generating duplicate posts?
add_action( 'shutdown', 'build_units_from_api', 10 );
function build_units_from_api() {
//Get all the unit IDs currently inside wordpress
$current_property_ids = array();
$wp_units = get_all_wp_posts('unit');
foreach ($wp_units as $wp_unit) {
$current_property_ids[] = (int)get_post_meta( $wp_unit->ID, 'unitproperty_id', true );
}
//Get all the api data
$property_units = get_all_property_units();
$num_of_property_pages = $property_units['pages'];
//Loop through the pages of the API data
for ( $i=0; $i <= $num_of_property_pages; ++$i ) {
$page_of_units = get_property_units_by_page($i);
$num_of_units_on_page = count($page_of_units['results']);
//Loop through the results of each page
for ( $x=0; $x <= $num_of_units_on_page; ++$x ) {
$property_profile = $page_of_units['results'][$x];
//Check if we already have that unit property ID
if ( in_array($property_profile['id'], $current_property_ids, true) || $property_profile['id'] == null ) {
//Do nothing and exit the current iteration
continue;
} else {
//Get the individual profile info from unit property API
$api = new unitpropertyAPI();
$api->getunit($property_profile['id']);
$property_profile_data = json_decode($api->result, true);
$post_arr = array(
'post_type' => 'unit',
'post_title' => $property_profile_data['name'],
'post_content' => '',
'post_status' => 'publish',
'comment_status' => 'closed',
'ping_status' => 'closed',
'meta_input' => array(
'unitproperty_id' => $property_profile_data['id'],
'unit_name' => $property_profile_data['name'],
'unitproperty_rating' => $property_profile_data['rating'],
),
);
//Put those fields into a new 'unit' custom post
$new_id = wp_insert_post( $post_arr );
//Stop from adding this property again
$current_property_ids[] = $property_profile_data['id'];
}
}
}
}

It was the action hook!
I found a solution thanks to this answer: https://wordpress.stackexchange.com/questions/287599/wp-insert-post-creates-duplicates-with-post-status-publish
Changing the action hook to admin_notices fixes the problem because it won't re-fire during the wp_insert_post call.

Related

Real time post display on home page (as soon as they are published)

I have noticed this becoming a trend with comments and now posts. I'm sure many sites (especially community ones) will implement this.
Here's exactly what I mean, click here: https://stackexchange.com/questions?tab=realtime
Posts appear in real time as soon as they are published.
There are also another version of this where the expand link appears with (1 question with the new activity). You can see this in action here if you wait for several seconds: https://superuser.com/
I'm wondering is there any way to do this on Wordpress? With my loop?
Right now my loop looks like this:
<?php
// Limit query posts to the current day
//
$period = $_GET["period"];
$day = $_GET["d"];
$year = $_GET["y"];
$args = array(
'year' => (int) date($year),
'monthnum' => (int) date( 'M' ),
'day' => (int) date($day),
'date_query' => array(
array(
'after' => ($period),
)
)
);
$query = new WP_Query( $args );
// The Loop
while ( $query->have_posts() ) :
$query->the_post();
get_template_part( 'template-parts/content', get_post_type() );
endwhile;
the_posts_navigation();
?>
I think this would help a lot of people. Thanks.
There are many ways to do that, you can use WebSockets (which is the best option) to send an event to each client when a new post is made, and they will update the HTML on the page.
The other option is to implement with AJAX (not the best option, but is easier). Basically, it will send a request each X time to check if there's any update from the last request. If there are new posts, update the HTML with the content. I'll say below why this can not be the best option.
Through WebSockets
WebSockets is a huge area (support chart), and you should search some articles to understand how they work to implement your own logic. A very simple example would make use of Ratchet library to make the PHP part and Socket.io to the server.
You can use wp_insert_post hook to send a message with post details to all clients every time a new post is inserted:
function send_new_post( $post_id, $post, $update ) {
foreach ($clients as $client) {
// Sending title and url to each client connected
$client->send(json_encode(array('title' => $post->post_title, 'url' => get_permalink( $post_id ))));
}
}
add_action( 'wp_insert_post', 'send_new_post', 10, 3 );
Take a look at the Example Chat implementation with Ratchet.
Through AJAX
First of all, I do not recommend using AJAX to do this on a site with many (even not that many) clients, because you can easily overload your server making those requests every X seconds.
let timestamp = 0;
function get_updates(){
let url = '?real_time=' + timestamp;
let update = $.ajax({
type: 'POST',
url: url,
async: false,
dataType: 'json',
}).complete(function(data){
// Get the current timestamp to make the next call
timestamp = data.timestamp;
if (data.posts){
$.each(data.posts, function(index, element) {
$('.posts').append($('<div>', {
text: element.title
}));
});
}
setTimeout(function(){get_updates();}, 10000); // 30 seconds
});
}
get_updates();
The timestamp will be synchronized on the first call, to prevent a client with a different timezone from getting a different timestamp, preventing old posts from being shown twice.
Here's one basic example to return the posts. You probably should make some extra validation on the timestamp to prevent from showing a very older post or even add posts_per_page to do not show a lot of posts, causing a lot of stress to your server:
function get_real_time_posts(){
if (!isset($_GET['real_time'])){
return;
}
// Check if it is a valid timestamp
if ( !((string) (int) $_GET['real_time'] === $_GET['real_time'])
|| !($_GET['real_time'] <= PHP_INT_MAX)
|| !($_GET['real_time'] >= ~PHP_INT_MAX)) {
return;
}
$time = intval($_GET['real_time']);
if ($time == 0) {
// First call to sync timestamps
echo json_encode(array(
'posts' => [],
'timestamp' => time()
));
die;
}
$args = array(
'post_type' => 'post',
'date_query' => array(
'after' => date('Y-m-d H:i:s', $time)
)
);
$query = new WP_Query( $args );
$posts = array();
foreach ($query->posts as $key => $value) {
$post = array(
'ID' => $value->ID,
'title' => $value->post_title,
'excerpt' => $value->post_excerpt
);
$posts[] = $post;
}
$result = array(
'posts' => $posts,
'timestamp' => time()
);
echo json_encode($result);
die;
}

How to execute script inside function that is triggered

function when_a_review_gets_submitted ($review, $post_id) {
// Do something
}
add_action('rwp_after_saving_review', 'when_a_review_gets_submitted', 11, 2);
I have this WP_Query and loop to update a custom field of every childpage of the parentpage were somebody has left a review.
// Set up the objects needed
$hosting_provider_query = new WP_Query();
global $post;
$hosting_pagina_titel = $post->post_title;
$search_all_pages = $hosting_provider_query->query(array(
'post_type' => 'page',
'post_status' => 'publish',
//Only get pages where custom field 'name_hosting_provider' equals Page title
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'naam_hosting_provivder',
'value' => $hosting_pagina_titel,
'compare' => '='
),
),
));
// Loop through all pages and find Page's children
$loop_through_all_child_pages = get_page_children( get_the_ID(), $search_all_pages );
// Loop through everything we got back
if(!empty($loop_through_all_child_pages)){
foreach($loop_through_all_child_pages as $child_page){
// get the ID of each childpage
$get_child_page_ID = $child_page->ID;
// get the ID of each parent page of the child pages
$get_parent_page_ID = get_queried_object_id();
// Get the average score from post_meta of the parent page
$get_average_score = get_post_meta( $get_parent_page_ID, 'rwp_user_score', true );
// update each custom field of the childs with the average score data from the post meta of parent page
update_field('gemiddelde_score_hosting_provider', $get_average_score, $get_child_page_ID);
}
}
How can I correctly execute this code within the above function?
EDIT
Ok I figured this out, the function is working properly when I remove the parameters $review and $post_id. And when I remove the add action:
add_action('rwp_after_saving_review', 'when_a_review_gets_submitted', 11, 2);
and replace it with:
add_action( 'template_redirect', 'when_a_review_gets_submitted' );
but I think this is a bit overkill for what I am trying to achieve. If I understand template_redirect it executes the function everytime a page is visited with that template? Am I correct?
So when I have 900 child pages it updates every custom field "gemiddelde_score_hosting_provider" when a page with that template is visited? So 900 times every single time a page with that template is visited? Am I correct in this one?
Is there a way to only update the custom field of the childs from the parent page where the review is submitted?

Function to auto-delete wordpress media after period of time

I'm writing what I thought was a simple series of functions to assign a "deletion date" to media, then auto-delete those expired media when the site is visited.
The post_meta for deletion date is set for the images via an online form when the images are uploaded (using formidable pro forms and the "after_create_entry" hook. I can confirm that the meta field is created successfully, and the deletion date is assigned properly (called 'mtp_deletiondate'. To test, I did a wp_query on a custom page and each image has a properly set deletion date.
Next, I wanted to run a function that checked for expired images (set to be 21 days after date of upload), and if they are expired, to delete them. I want to trigger this function whenever I access the admin, because I figure I get in there at least once a month to run updates, and it's a good time to clean out the old images. For this situation, I decided not to do a cron job since I can reliably visit the site once a month anyway.
The issue is the function doesn't seem to be triggering, and I don't know if it's because the function is broken or because I'm using the wrong action, or both. Some research tells me to use the admin_init, but I've also tried "wp", and "wp-footer". It might be that I'm just misunderstanding the process. My function (in my theme's functions.php):
function drick_delete_expired_uploads() {
// WP_Query arguments
$args = array (
'post_status' => 'any',
'post_type' => array( 'Attachment' ),
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'mtp_deletiondate',
),
),
);
// The Query
$mediaquery = new WP_Query( $args );
// The Loop
if ( $mediaquery->have_posts() ) {
while ( $mediaquery->have_posts() ) {
$mediaquery->the_post();
date_default_timezone_set('America/Denver');
$CurrentDate = date('Y-m-d h:i');
$CurrentDateStr = strtotime($CurrentDate);
$DeletionDate = get_post_meta( $post->ID, 'mtp_deletiondate', true );
$DeletionDateStr = strtotime($DeletionDate);
if ( isset($DeletionDateStr) ) {
if ( $DeletionDateStr < $CurrentDateStr ) {
wp_delete_attachment( $post->ID, true );
}
}
}
} else {
// no posts found
} // IF HAVE POSTS
// Restore original Post Data
wp_reset_postdata();
}
add_action('admin_init', 'drick_delete_expired_uploads');
If I save my functions.php, then reload the Wordpress dashboard, then check my media, the expired images are still there. HOWEVER, if I add this function to an actual page then visit the page, it does work. So I believe the function is doing what it's supposed to, it's just not getting triggered properly? I also added a quick wp_mail() to the function in my functions.php, and when I visited the admin it did trigger the email, so I guess the function is firing.
I would appreciate any insight, thank you!
So I think I figured it out, but I don't know that I have an explanation as to why it works. Essentially it looks like wp_delete_attachment wasn't working, but wp_delete_post DOES work. I've tested and confirmed with three additional images that the function auto delete is triggered when accessing the admin dashboard. I also changed the way the query arguments are structured by only querying the expired images, rather than querying ALL images that have a mtp_deletiondate meta then running a conditional statement within the query. Don't know if that's related or not. The final working function:
function drick_delete_expired_uploads() {
// WP_Query arguments
date_default_timezone_set('America/Denver');
$CurrentDate = date('Y-m-d h:i');
$args = array (
'post_status' => 'any',
'post_type' => array( 'Attachment' ),
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'mtp_deletiondate',
'value' => $CurrentDate,
'compare' => '<',
'type' => 'DATE',
),
),
);
// The Query
$mediaquery = new WP_Query( $args );
// The Loop
if ( $mediaquery->have_posts() ) {
while ( $mediaquery->have_posts() ) {
$mediaquery->the_post();
wp_delete_post(get_the_ID());
}
} else {
// no posts found
} // IF HAVE POSTS
// Restore original Post Data
wp_reset_postdata();
}
add_action('admin_init', 'drick_delete_expired_uploads');
Still look forward to any feedback from someone in the know that can tell me why the previous version wasn't working (looking to learn here). Thanks!

Add incremental parameter value to each post in wordpress query loop

I'm trying to combine several loops into one and sort the final results by relevance.
For the former part, I did this:
// set the variables
$author_id = get_the_author_meta('ID');
$tags_id = wp_get_post_tags($post->ID);
$first_tag = $tags_id[0]->term_id;
$categories_id = wp_get_post_categories($post->ID);
// loop for same author
$by_author = new WP_Query (array(
'author' => $author_id,
'posts_per_page' => '5'
));
// add ids to array
if ($by_author->have_posts()) {
while ($by_author->have_posts()) {
$by_author->the_post();
$add[] = get_the_id();
}
}
// loop for same tag
$by_tag = new WP_Query(array(
'tag__in' => $first_tag,
'posts_per_page' => '5'
));
// add ids to array
if ($by_tag->have_posts()) {
while ($by_tag->have_posts()) {
$by_tag->the_post();
$add[] = get_the_id();
}
}
// loop for same category
$by_category = new WP_Query(array(
'category__in' => $categories_id,
'posts_per_page' => '5'
));
// add ids to array
if ($by_category->have_posts()) {
while ($by_category->have_posts()) {
$by_category->the_post();
$add[] = get_the_id();
}
}
// loop array of combined results
$related = new WP_Query(array(
'post__in' => $add,
'post__not_in' => array($post->ID),
'posts_per_page' => '10',
'orderby' => $weight[$post->ID],
'order' => 'DESC'
));
// show them
if ($related->have_posts()) {
while ($related->have_posts()) {
$related->the_post();
// [template]
}
}
This is working nicely combining the loops into one. For the latter part, what I'm trying to do next is to add an incremental "weight" value to each post as they come up so as to later sort them with something like 'orderby' => $weight,.
For example, if a post comes up in "same author" it gets 3 points, if another one comes up in same tag it gets 2 points and so on. If it comes up in more than one loop, it should get the combined points i.e. 3+2+1=6 hence be boosted to the top of the final query.
I have tried to add a counter to each preliminary loop, like $weight = +3 etc, but this only adds everything up for every post, not individually.
I also tried inserting something like this at the end of each preliminary loop...
$weight = 0;
if ($by_author){
foreach ($by_author as $post){
setup_postdata($post);
$weight = +10;
add_post_meta($post->ID, 'incr_number', $weight, true);
update_post_meta($post->ID, 'incr_number', $weight);
}
}
... and this to the final one
echo get_post_meta($post->ID,'incr_number',true);
But it still doesnt do it right. It assigns a global value, while I want them to be different depending on the actual main posts one is reading.
So is there a way to do this?
If I understand your question right, I think your last solution was close. Instead of a global $weight parameter, however, I think you need to build an array of $weights that's unique to each post:
$weights[$post.id] += 10;
Then, you could sort that array and get your most heavily weighted post IDs from there.

Bulk rewrite post slugs based on custom field value in Wordpress

Basically I have a custom post type setup called "Parts" with over 5,000 posts currently in it. There are a number of custom fields associated with each part, including a "part number". Currently, the URL for each part is:
http://site.com/parts/name-of-part/
What I would rather have is:
http://site.com/parts/XXXX-608-AB/ (That's a part number, stored as a custom field "partno".)
I believe I need to do two things:
1) Make a script to bulk edit all the slugs for each existing part, based on the custom field "partno".
2) Hook into a Wordpress function to trigger it to always create the slug for new parts based on the custom field "partno".
Does anyone have any knowledge on how to accomplish one or both of these aspects?
UPDATE: Below is the code I ended up using for changing existing posts
// Set max posts per query
$max = 500;
$total = 5000;
for($i=0;$i<=$total;$i+=$max) {
$parts = get_posts(array('post_type' => 'parts', 'numberposts' => $max, 'offset' => $i));
// loop through every part
foreach ( $parts as $part ) {
// get part number
$partno = get_post_meta( $part->ID, 'partno', true );
$updated_post = array();
$updated_post['ID'] = $part->ID;
$updated_post['post_name'] = $partno;
wp_update_post( $updated_post ); // update existing posts
echo $part->ID;
}
}
UPDATE: Below is the code I used in functions.php to change ongoing posts
(thanks in part to https://wordpress.stackexchange.com/questions/51363/how-to-avoid-infinite-loop-in-save-post-callback)
add_action('save_post', 'my_custom_slug');
function my_custom_slug($post_id) {
//Check it's not an auto save routine
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
return;
//Perform permission checks! For example:
if ( !current_user_can('edit_post', $post_id) )
return;
//If calling wp_update_post, unhook this function so it doesn't loop infinitely
remove_action('save_post', 'my_custom_slug');
//call wp_update_post update, which calls save_post again. E.g:
if($partno != '')
wp_update_post(array('ID' => $post_id, 'post_name' =>get_post_meta($post_id,'partno',true)));
// re-hook this function
add_action('save_post', 'my_custom_slug');
}
1) Create a new page and assign a new page template to it, lets say site.com/update and update.php. Inside of update.php write you bulk mechanism:
<?php // grab all your posts
$parts = get_posts(array('post_type' => 'parts', 'numberposts' => -1,))
// loop through every part
foreach ( $parts as $part ) {
// get part number
$partno = get_post_meta( $part->ID, 'parto', true );
$updated_post = array();
$updated_post['ID'] = $part->ID;
$updated_post['post_name'] = $partno;
wp_update_post( $updated_post ); // update existing posts
} ?>
You could place this anywhere in your theme but I like to create a page for that so I can easily run a cron job with it.
Next the function to change the slug of every newly created post:
<?php function change_default_slug($id) {
// get part number
$partno = get_post_meta( $id, 'parto', true );
$post_to_update = get_post( $id );
// prevent empty slug, running at every post_type and infinite loop
if ( $partno == '' || $post_to_update['post_type'] != 'parts' || $post_to_update['post_name'] == $partno )
return;
$updated_post = array();
$updated_post['ID'] = $id;
$updated_post['post_name'] = $partno;
wp_update_post( $updated_post ); // update newly created post
}
add_action('save_post', 'change_default_slug'); ?>
The code above runs every time a post gets saved (e.g. when published for the first time) and sets a new post_name to the part no.

Categories