Improve Performance get_post_meta WordPress - php

I created a logic inside my WordPress, where for every CPT shop_oder, there is a post_meta called "luck_number". The problem is that on average, each post in this CPT has had more than 1000 sales, and then when I create a while to go through and get the post_meta with get_post_meta, the page takes about 15 minutes to load, specifically inside the While do loop.
I did a test, and when there are few post_meta the speed is ok, but when we get to the thousand place, it gets pretty slow.
What would be the best way, so that even in this scenario of thousands of post_meta per POST, I still have a good performance?
The section where the laugh is like this:
<?php
$loop = new WP_Query( $args );
while ( $loop->have_posts() ) : $loop->the_post();
$numbers = get_post_meta(get_the_ID(),"luck_number",false);
$a = 0;
while($a<count($numbers)):
// DO SOMETHING WITH THE NUMBER
$a++;
endwhile;
endwhile;
wp_reset_query();
While each post has an average of 0 to 500 numbers, everything works fine. Above 1000, it's very slow.
My 'post_per_page' averages 25, but even changing it to a smaller number doesn't change the result much. The infrastructure like the server (a good VPS) or the memory_limit of PHP (currently with 1024M) hasn't interfered much

If you need only one custom_field from CPT, you don't need to use WP_Query
You can just take all fields in the array through $wpdb
Try this code
function get_luck_numbers_from_db( $key = '', $type = 'post', $status = 'publish' ) {
global $wpdb;
if( empty( $key ) )
return;
$r = $wpdb->get_results( $wpdb->prepare( "
SELECT pm.post_id, pm.meta_value FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = '%s'
AND p.post_status = '%s'
AND p.post_type = '%s'
", $key, $status, $type ) );
foreach($r as $value) {
if($value !="") {
$luck_numbers_array[$value->post_id] = $value->meta_value;
}
}
return $luck_numbers_array;
}
$array = get_luck_numbers_from_db('luck_number','shop_oder', 'publish');
print_r($array);

Related

Best performance for mySQL and PHP: Lots of small queries, or a large one?

So i have this technicall dilemma:
I have a Wordpress site, with more than 50 categories (and rising). I want to get just the six categories with the newest article in them, and -if i get this right- i can't do it with a single typical WP Query, i'll have to improvise:
a) get from the database the last 200-300 posts, and by elimination in PHP filter by category, and show the six last updated categories, or
b) get from the database all my categories, and for each category ask again the database the last article by date - and show the six last updated categories.
i know that the first solution is not very safe, and will strech a little my server calculating with PHP, but the second will have more than 50+1 queries (and rising).
Considering that the site i'm working on has a couple of million visitors each month, which one do you think will give, from a technical point of view, the less stress to my servers?
Or i'm missing an more obvius solution?
Thanks for your time! :)
Every time you post something, you could save the taxonomy's name/slug/id in a transient on publishing/updating through set_transient() and save_post action hook.
Saving the data as an array and slicing it once it reach 6... array_search before updating...
To display them you would simply loop through the transient's value and get_taxonomy() or build a custom WP_Query looping through each of them.
Which would avoid you a lot of queries. Pretty much none...
here is an untested demo:
<?php
//Retieve the transient through get_transient( '_transient_wpso_73900689' );
add_action( 'save_post', function ( $post_ID, $post, $update ) {
$transient = '_transient_wpso_73900689';
$expiration = 0;
$prev_value = ! empty( get_transient( $transient ) ) ? get_transient( $transient ) : array();
$next_value = get_post_taxonomies( $post_ID );
$buffer = array();
if ( ! empty( $next_value ) ) {
foreach ( $next_value as $taxonomy ) {
//Search the previous transient array.
if ( array_search( $taxonomy, $prev_value ) === false ) {
//If the value doesn't exist, push the value to the array.
array_push( $buffer, $taxonomy );
};
};
$value = array_merge( $prev_value, $buffer );
//Reverse the array.
$value = array_reverse( $value );
//Retrieve the last 6.
$value = array_slice( $value, 0, 6 );
//Set the transient.
set_transient( $transient, $value, 0 );
};
}, 10, 3 );
Can,t you just join the articles and sort by the joined table ordered by date desc?
Something like:
Select from categories c
Left join articles a on a.category = c.id
Order by a.date desc group by c.id limit 6

Sum custom post meta values

This should be really straightforward..
I have a custom post type: "shipment" with a custom field "shipment_cost"
I want to sum the total cost of all shipments.
I've managed to get it to output the correct amount with a single post:
$totalcost = 0;
$post_ids = [2583];
foreach( $post_ids as $post_id )
{
$totalcost += (int) get_post_meta( $post_id, 'shipment_cost', true );
}
echo $totalcost;
But when I try to use all shipment CPTs in an array, I get a result of zero.
$shipments = array(
'post_type' => 'shipment'
);
$totalcost = 0;
$post_ids = $shipments;
foreach( $post_ids as $post_id )
{
$totalcost += (int) get_post_meta( $post_id, 'shipment_cost', true );
}
echo $totalcost;
Is my shipments array wrong, or am I just going about this the wrong way in general?
Thanks!
Right now it seems that you are using $shipments as it was an array of post ids? You first need to retrieve all post ids:
$shipments = get_posts(array(
'post_type' => 'shipment',
'post_status' => 'publish'
));
Since the above would return post object you need to modify your loop:
foreach ($shipments as $post) {
$totalcost += (int) get_post_meta($post->ID, 'shipment_cost', true);
}
A better and faster way might be to use a raw database query to get the sum for all meta values:
$totalcost = $wpdb->get_col("SELECT SUM(pm.meta_value) FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = 'shipment_cost'
AND p.post_status = 'publish'
AND p.post_type = 'shipment'");
This just saved my life!! Been looking for a solution to total inputs from a Pods Custom Post Type which has a Custom Field that users can enter a number into. I then wrapped the code in a shortcode to display on front end
function jobs_shortcode () {
$jobs = get_posts(array(
'post_type' => 'pledged_job',
'post_status' => 'publish'
));
$total = 0;
$post_ids = $jobs;
foreach ($jobs as $post) {
$total += (int) get_post_meta($post->ID, 'jobs_pledged', true);
}
echo $total;
}
add_shortcode( 'jobs', 'jobs_shortcode' );
THANK YOU!

Display a category only when it has posts

For many different reasons I was forced to deactivate the sticky posts function on WordPress. Still I need this function. This means that I need a workaround. I need to show a post on the top of the feed and I need it to be made as easily as possible for the user that is writing the post.
NOTE: I am using Visual Composer.
I thought that a workaround for this would be to add a new container via Visual Composer or a sidebar and call for a category. This new sidebar/container would then only be visible if there were any posts on that category. I have been searching for a function, query, plugin, etc to do this, but with no success.
I have found Featured Post Widget and Featured Category Widget but I don't think that they are what I need.
Hook with get_terms will display a terms/category only when it has posts
Add this code in WP theme's functions.php
E.g (domain.com/wp-content/themes/yourThemeName/functions.php )
add_filter('get_terms', 'get_terms_filter', 10, 3);
function get_terms_filter( $terms, $taxonomies, $args )
{
global $wpdb;
$taxonomy = $taxonomies[0];
if ( ! is_array($terms) && count($terms) < 1 )
return $terms;
$filtered_terms = array();
foreach ( $terms as $term )
{
$result = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts p JOIN $wpdb->term_relationships rl ON p.ID = rl.object_id WHERE rl.term_taxonomy_id = $term->term_id AND p.post_status = 'publish' LIMIT 1");
if ( intval($result) > 0 )
$filtered_terms[] = $term;
}
return $filtered_terms;
}
For ignore sticky posts on frontend set ignore_sticky_posts to true in main query
add_action('pre_get_posts', '_ignore_sticky');
function _ignore_sticky($query)
{
// Only for Front end
if (!is_admin() && $query->is_main_query())
$query->set('ignore_sticky_posts', true);
}

How can I sort posts by featured image(no image posts at the bottom) in wordpress using PHP?

I am working with the Wordpress Query Object using the WP Types/Views Toolset.
http://wp-types.com
We built out the parametric search which allows the user to search through posts using various taxonomies. It works fine, it display search results as needed. BUT..
Most of the posts don't have featured images, we want to display posts with featured images at the top, and the featured imageless posts would go below.
I have a good head start on this as far as logic goes, just need a bit of help.
The code below allows me manipulate the query before rendering it to the user.
add_filter( 'wpv_filter_query_post_process', 'prefix_rearrange_by_thumbnail', 10, 3 );
function prefix_rearrange_by_thumbnail( $query, $view_settings, $view_id ) {
// sort posts by thumbnail
return $query;
}
How can I sort through the query->posts and rearrange them so the ones that do have featured images show up before those without.
All of the WP_Post objects are found in an array under $query->posts. You can sort the array using usort() based on whatever criteria you want. Keep in mind that this will only sort each page of results as that is what is returned.
add_filter( 'wpv_filter_query_post_process', 'prefix_rearrange_by_thumbnail' );
function prefix_rearrange_by_thumbnail( $query ) {
$posts = $query->posts;
usort( $posts, 'sort_by_thumbnail' );
return $query;
}
// function used to sort the posts array
function sort_by_thumbnail( $a, $b ){
// the the featured image id so we can sort by it
$a_thumb = get_post_meta( $a->ID, '_thumbnail_id', true );
$b_thumb = get_post_meta( $b->ID, '_thumbnail_id', true );
if ( empty( $a_thumb ) && ! empty( $b_thumb ) ) return 1;
if ( ! empty( $a_thumb ) && empty( $b_thumb ) ) return -1;
return 0;
}
To sort all the pages of the results you need to modify the arguments that are passed to WP_Query using the wpv_filter_query filter. There is a meta_key called "_thumbnail_id" set for each post/page that has a featured image, but the issue is that this meta_key is not set at all for posts/pages without a featured image, so if you use it the results will be sorted by the ID of the featured images, but if it is missing they will be omitted. One option is to set another flag based on that meta_key to use in your sort. A one time cleaned would be needed then you could use a hook on save_post
One time SQL (adjust for your db prefix):
INSERT INTO wp_postmeta( post_id, meta_key, meta_value )
SELECT p.ID, '_has_featured_image', IF ( meta_value IS NULL, 0, 1 ) AS _has_featured_image
FROM wp_posts p
LEFT JOIN wp_postmeta m ON p.ID = m.post_id AND meta_key = '_thumbnail_id'
WHERE p.post_status = 'publish'
AND p.post_type IN ('post','page')
Hook on save_post to keep "_has_featured_image" clean going forward:
add_action( 'save_post', 'set_has_featured_image' );
function set_has_featured_image( $post_id ){
$post = get_post( $post_id );
switch ( $post->post_type ){
case 'post': case 'page':
$thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true );
update_post_meta( $post_id, '_has_featured_image', ! empty( $thumbnail_id ) );
break;
}
}
Now you can filter on the "_has_featured_image" meta_key:
add_filter( 'wpv_filter_query', 'prefix_rearrange_by_thumbnail' );
function prefix_rearrange_by_thumbnail( $args ){
$args['orderby'] = 'meta_value';
$args['meta_key'] = '_has_featured_image';
return $args;
}
I managed to do it in an easier way...
add_filter( 'wpv_filter_query_post_process', 'prefix_rearrange_by_thumbnail', 10, 3 );
function prefix_rearrange_by_thumbnail( $query, $view_settings, $view_id ) {
if ( !empty( $query->posts ) ) {
$sorted_posts_top = array();
$sorted_posts_bottom = array();
$all_posts = $query->posts;
foreach ($all_posts as $post) {
if ( has_post_thumbnail($post->ID) ) {
$sorted_posts_top[] = $post;
}
else {
$sorted_posts_bottom[] = $post;
}
$query->posts = array_merge((array)$sorted_posts_top, (array)$sorted_posts_bottom);
}
}
return $query;
}
However, your answer is still VERY helpful. Thank you SO MUCH for sharing!!!!!!!!!!!!!!!!

PHP Warning: Missing argument 2 for wpdb::prepare() WPML Plugin

Suddenly I am getting an error on a specific backend page on my wordpress project:
Warning: Missing argument 2 for wpdb::prepare(), called in .../wp-content/plugins/wpml-media/inc/wpml_media.class.php on line 463 and defined in .../wp-includes/wp-db.php on line 992
Which would be:
wpml_media.class.php
// get language of their parents
if(!empty($missing_langs)){
$results = $wpdb->get_results($wpdb->prepare("
SELECT p.ID, t.language_code
FROM {$wpdb->posts} p JOIN {$wpdb->prefix}icl_translations t ON p.ID = t.element_id AND t.element_type = CONCAT('post_', p.post_type)
WHERE p.ID IN(".join(',', $missing_langs).")
"));
foreach($results as $row){
$parent_langs[$row->ID] = $row->language_code;
}
}
and wp-db.php
function prepare( $query, $args ) {
if ( is_null( $query ) )
return;
$args = func_get_args();
array_shift( $args );
// If args were passed as an array (as in vsprintf), move them up
if ( isset( $args[0] ) && is_array($args[0]) )
$args = $args[0];
$query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it
$query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting
$query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware
$query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s
array_walk( $args, array( $this, 'escape_by_ref' ) );
return #vsprintf( $query, $args );
}
I have no clue whats happening here, any ideas? Unfortunately the author's support has expired.
Thanks
$wpdb->prepare needs at least two params http://codex.wordpress.org/Class_Reference/wpdb#Protect_Queries_Against_SQL_Injection_Attacks
try something like that:
$wpdb->prepare("
SELECT p.ID, t.language_code
FROM {$wpdb->posts} p JOIN {$wpdb->prefix}icl_translations t ON p.ID = t.element_id AND t.element_type = CONCAT('post_', p.post_type)
WHERE p.ID IN(%s)", join(',', $missing_langs))
There's some more info on this problem here. Basically, don't panic because it's just a warning so nothing is going to actually break.
If you're looking to modify the wpml-media plugin to get rid of this error, the developer info in the above link should help. If you just want the warning to go away, then I quote:
First, if you’re a user and you want to get rid of these errors, you should turn off the displaying of errors in PHP. There are many ways to do this, such as in php.ini, .htaccess, etc. For this, you can just put this in wp-config.php. (Note that hiding errors on production sites is good practice anyway.)
#ini_set('display_errors', 0);

Categories