Wordpress search optimization: limit search result (not post_per_page) - wordpress

I have searched stackoverflow. none of them apply to my question. Most of the answer just about limit per page (how many posts to show per page, not how many result to search)
I'm trying to speed up wordpress search.
For example I have 10millions post to search. and user type "a". It will take very long to search all of that because wordpress is using SQL_CALC_FOUND_ROWS.
The fact is that there is no need to search milions of rows (posts) and the answer "1000+" is enough for users. So we need to stop search after we found 1000 results.
The only way i can think of is to use:
'no_found_rows' => true
But then we dont have pagination functionality.
Or maybe we can use a separate query to get the result and to count how many posts found (with limiter)?
I dont understand how can wordpress doesnt have this functionality. Even big site like amazon limit the search result. So if you search "a" or "usb", it will return just the first 20 pages.

Try this code, its works on my wordpress with 1M posts
If you use search for custom post type, change
post_type = 'post'
to your post type name
add_filter('pre_get_posts', 'fix_search_pagination_with_nfr', 1000);
function fix_search_pagination_with_nfr() {
global $wp_query, $wpdb;
if ( ! is_admin() && $wp_query->is_main_query() && $wp_query->is_search() ) {
$wp_query->query_vars['no_found_rows'] = 1;
$wp_query->found_posts = $wpdb->get_var( "SELECT COUNT(*) FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')" );
$wp_query->found_posts = apply_filters_ref_array( 'found_posts', array( $wp_query->found_posts, &$wp_query ) );
if($wp_query->query_vars['posts_per_page'] > 0) {
$wp_query->max_num_pages = ceil($wp_query->found_posts / $wp_query->query_vars['posts_per_page']);
} else {
$wp_query->max_num_pages = 0;
}
}
return $wp_query;
}

Related

Assign orders from guest customers to particular user

I am using the following code to solve this but this is not working for orders from guest customers . However this is working fine for the orders belonging to some registered user/customer but not for the orders belonging to guest customers.
Solution credit to LoicTheAztec for answer
function cristmas_bulk_editing_orders(){
if(!is_admin()) return; // Will work only from Admin Backed.
else {
$order_id = 9458;
$new_customer_id = 479;
// Getting the postmeta customer ID for 'order' post-type
$customer_id = get_post_meta( $order_id, '_customer_user', true );
var_dump($customer_id);
// If it's an existing order and doesn't have already this user ID
// It update the customer ID
if( !empty($customer_id) && $new_customer_id != $customer_id )
update_post_meta($order_id, '_customer_user', $new_customer_id,0);
echo 'order updated';
}
}
cristmas_bulk_editing_orders();
ORIGINAL ISSUE
We imported the orders via woocommerce order export & import plugin from woocommerce team ..
But in the process something went wrong.. Most of the orders were not assigned any customer ..
So now when ever a new customer registers he/she is assigned 1 of these orders automatically ..
So basicallly all of them see 1 order in their recent orders which belongs to some other guest cusotmer , then they have all the information about other customer . their email etc..
So one option is I find out all the orders(with issues that is no customer assisned to them ) and I assign them to admin ..but this also have some issuses.....
SO is there any other option that these new registered users don't get old orders assigned..
Please help
May be you could try this other similar function based on SQL queries, that should do the trick. Before starting, make a database backup. I have add controls (or limitations) like :
A max date (in YYYY-MM-DD 00:00:00 date time format)
Order number range
So here is that code:
function easter_bulk_editing_orders(){
if( ! is_admin() ) return; // Will work only from Admin Backed.
else {
$processed_orders = array();
// Define the replacement user ID
$replacement_user_id = '2500';
// Define the DATE LIMIT for guest orders max date limit
$max_date = '2017-12-09 00:00:00';
// Define an order range
$order_id_min = '0';
$order_id_max = '100000';
global $wpdb;
// Get all guest orders below a defined max date
$old_guest_orders = $wpdb->get_results( "
SELECT pm.*, p.post_date
FROM {$wpdb->prefix}postmeta AS pm
LEFT JOIN {$wpdb->prefix}posts AS p ON pm.post_id = p.ID
WHERE pm.post_id BETWEEN $order_id_min AND $order_id_max
AND pm.meta_key LIKE '_customer_user'
AND ( pm.meta_value LIKE '0' OR pm.meta_value LIKE '' )
AND p.post_date <= '$max_date'
" );
foreach( $old_guest_orders as $guest_order ){
$meta_id = $guest_order->meta_id;
$wpdb->query( "
UPDATE {$wpdb->prefix}postmeta as pm
SET pm.meta_value = '$replacement_user_id'
WHERE pm.meta_id = '$meta_id'
" );
// Set each order ID in an array
$processed_orders[] = $guest_order->post_id;
}
// Testing ( raw output of processed orders IDS )
var_dump($processed_orders);
}
}
// Run the function
easter_bulk_editing_orders();
Code goes in function.php file of your active child theme (active theme or in any plugin file).
You have to use this function only once and to remove it afterwards (see below).
USAGE:
Once this code is pasted and saved on function.php file, display or reload any Admin page from backend within your browser.
Now you can comment the function this way and save:
// easter_bulk_editing_orders();
Check that the orders have been changed as you want, and remove all this code.
Code is tested and works.
There is error in your importing so try to re-import. Have you tried WP All Import Premium. It will import anything with drag and drop titles, categories, and meta fields. you can also assign new meta name to post meta while importing. If you need more help then share more details.

Woocommerce Get the order_id from one of the item_id?

I try to get the related order (the order id) from one of the line items id.
One case for example:
When editing an order, and deleting a line item (done by Ajax) the Woocommerce only provide the hook "woocommerce_before_delete_order_item" and passes $item_id only. (The woo function performs sql and not WP enviroment). I need the "belonging" order id to make some action!
My solution so far, is to loop through all orders and compare the Items ID, and break and return order ID when the match occurs.
This is way to slow, or "bulky clumsy", when keeping 10000 orders and more alive in the current shop.
This does NOT work:
wp_get_post_parent_id ( $item_id )
But Im hoping there is a similar call, or do I need to make a DB SQL search? Any answer involving mySQL, please make it "copy and paste ready". Im great at PHP but dont handle or never write my own SQL.
Thanks for any help! Below is my solution so far:
$order_id = my_wc_get_order_from_item_id($item_id);
function my_wc_get_order_from_item_id($id) {
$orders = get_posts('post_type=shop_order&numberposts=-1&post_status=publish');
foreach($orders as $obj) {
$order = new WC_Order($obj->ID);
if ( count( $order->get_items('line_item') ) > 0 ) {
foreach($order->get_items('line_item') as $item_id => $item ) {
if($item_id == $id) $return_value = $obj->ID;
if(isset($return_value)) break;
}
}
unset($order);
if(isset($return_value)) break;
}
if(isset($return_value)) return $return_value;
else return 0;
}
Your approach didn't work because order items are not posts in the wp_posts table, so they can't have a post_parent.
However, all is not lost and this was easier than I expected considering that my SQL isn't that strong. Looking at the woocommerce_order_item table in the database you can see that order_id and order_item_id are both there:
so I borrowed a little SQL statement from WooCommerce and modified it to find the order_id given a particular order_item_id row. Let me know how that works.
function so_38286531_get_order_item_order_id( $item_id ) {
global $wpdb;
$order_id = $wpdb->get_var( $wpdb->prepare(
"SELECT order_id FROM {$wpdb->prefix}woocommerce_order_items
WHERE order_item_id = %d",
$item_id
) );
return $order_id;
}
Use the built-in woocommerce function wc_get_order_id_by_order_item_id($item_get_id) to get order ID
// define the woocommerce_before_delete_order_item callback
function my_func_before_delete_order_item( $item_get_id ) {
$order_id = wc_get_order_id_by_order_item_id($item_get_id);
// do what you need...
};
// add the action
add_action( 'woocommerce_before_delete_order_item', 'my_func_before_delete_order_item', 10, 1 );

Wordpress - showing last uploaded images, but only from a specific custom post type

I have a Wordpress-powered website with several custom post types, and I'm struggling to find a way to load the latest uploaded images from one of the custom post types only.
In other words, I know how to ask for the most recent 4 images added to the Wordpress media library, but I can't find a way to filter the attachments depending of the post type of their parent page (in this case, show only the images uploaded to 'Image Gallery' posts - not regular posts, not 'Video Gallery' posts, etc).
It feels like a very basic question, but for some reason I can't get this work and I'm really frustrated. Is there any way to get the post type of the post parent in the query? Or I should approach this issue from another angle?
I don't believe it's possible to use WP_Query to filter based on the post type of the parent. The most straightforward way around this is to use SQL and $wpdb->get_results() directly. This will also bypass all built in caching, etc so think about how your implementation will be used.
global $wpdb;
// the SQL statement to only fetch the N last attachments
$sql = <<<SQL
SELECT attachments.*
FROM {$wpdb->posts} attachments
-- join the parent post based on the parent_post value to filter by cpt
JOIN {$wpdb->posts} post
ON post.ID = attachments.post_parent
AND post.post_status = 'publish'
-- pass in the custom post type
AND post.post_type = %s
WHERE
-- only fetch images, double percents for prepare()
attachments.post_mime_type LIKE 'image%%'
ORDER BY attachments.post_date DESC
-- pass in the number to fetch
LIMIT %d
SQL;
$cpt = "your_custom_post_type";
$limit = 4;
$attachments = $wpdb->get_results( $wpdb->prepare( $sql, $cpt, $limit ) );
foreach ( $attachments as $attachment ){
// $attachment will have all the rows from the posts table.
$id = $attachment->ID;
}

Wordpress get attachment of post doesn't work

I'm trying to retrieve all the attachment of a specific post, but it doesn't work for the moment. Here is the code :
$args = array(
'post_type' => 'attachment',
'posts_per_page' => -1,
'post_parent' => $id
);
$attachments = get_posts($args);
Where Id is the id of my post. I've tried also with new WP_Query() but it didn't worked neither. Both ways return an empty result.
Does anyone have an idea what I'm doing wrong here ?
Thanks
EDIT
Ok, I think I know what's going wront.
When using this arguments for the get_posts function, it will return the images uploaded through the "Add a media" button in the specific post.
So basically, let's say that on my first post, I've uploaded all the images I would need for all my future post. If I apply the request on the first post, I will retrieve all the images, even the one that I don't use in this post.
If I apply this function to another post, because I didn't uploaded any file in this post, the function will retrieve an empty array.
Does anyone have an idea how I can retrieve all the images used in a specific post ? So not only uploaded, but really integrated into the post or added to a custom field ?
Thanks
When using get_posts to fetch attachments, you need to set post_status to inherit. Also, check out the get_children function:
$args = array(
'post_type' => 'attachment',
'post_mime_type' => 'image',
'numberposts' => -1,
'post_status' => 'inherit',
'post_parent' => $id
);
$attachments = get_children( $args );
Case: using ACF, I created an repeater field 'resort_pics' for my gallery, which has 2 fields 'picture_title' as text and 'picture' as an picture type.
Then happily uploaded 100 photos, some of them were same for several posts so I only clicked those from uploaded gallery (I will use term of "referenced images" for those).
Problem : then one happy day I noticed all "referenced images" are missing in our api.
Cause :
As noted at documentation comment from 'Uriahs Victor' (https://developer.wordpress.org/reference/functions/get_attached_media/)
Important to note that this function only returns the attachments that
were first uploaded/added to the post. Uploading an image to Post A(ID
1) and then adding that image later to Post B(ID 2) would give an
empty array if the following code was used:
$media = get_attached_media( 'image', 2 );
var_dump( $media );
Real cause :
Real source of all this problem is The thing that information about "reference images" are not stored in 'wp_posts' table but are actually stored in 'wp_postmeta', so by just querying 'wp_posts' table or using get_attached_media() which only looks there also you will not get all attachements for post.
Solution :
Lets take an example of post with ID - $postId which has defined
repeater - 'resort_pics', field in repeater with image 'picture'. (created with ACF plugin)
First get all attachements for our post (resort) (including images/pdfs and so on) with classic way (can be also used 'get_attached_media'):
$images = $wpdb->get_results("select ID,guid from wp_posts where post_parent = " . $postId . " and `post_type`= 'attachment'");
where guid is 'url' to an attachement, lets index those in array where key will be post id of an attachement
$mapImages = [];
foreach ($images as $image) {
$mapImages[$image->ID] = $image->guid;
}
Now we have all atachements but are missing all referenced images/files.
Lets continue by selecting all meta for our post (resort).
$metas = $wpdb->get_results("select meta_key,meta_value from wp_postmeta where post_id=" . $postId);
And index them by an meta key
$mapMetas = [];
foreach ($metas as $meta) {
$mapMetas[$meta->meta_key] = $meta->meta_value;
}
Lets say our post ($postId) has an 9 entries in 'resort_pics' with an image uploaded to its 'picture' field, then $mapMetas['resort_pics'] will have an value of 8.
Now the way how repeater fields keys are stored in $mapMetas array, is actually an :
'resort_pics_0_picture' -> itsPostId (5640 for example)
'resort_pics_1_picture' -> itsPostId (5641 for example)
'resort_pics_2_picture' -> itsPostId (5642 for example)
...
'resort_pics_8_picture' -> itsPostId (5648 for example)
Knowing this we can get simply all image urls for "resort_pics"
for ($i = 0; $i < $mapMetas['resort_pics']; $i++) {
$picture = [];
$picture['name'] = $mapMetas['resort_pics_' . $i . '_picture_title'];
$picture['url'] = $mapImages[$mapMetas['resort_pics_' . $i . '_picture']];
$pictures[] = $picture;
}
You may already get to this point, simply from $mapMetas get image ID and using it get an image url from $mapImages.
Lets say 'resort_pics_1_picture' is 'referenced' one (not directly uploaded image), we have its id '5641' but since its not connected to our $postID but to some other post id when it was actually uploaded. Its missing in our $mapImages array, so lets edit this code a bit.
for ($i = 0; $i < $mapMetas['resort_pics']; $i++) {
$picture = [];
$picture['name'] = $mapMetas['resort_pics_' . $i . '_picture_title'];
$picture['url'] = getAttachmentOrItsReferenceUrl('resort_pics_' . $i . '_picture', $mapImages, $mapMetas);
$pictures[] = $picture;
}
We have added an getAttachementOrItsReferenceUrl() method, which will simply first check if we already have this image (all uploaded to this post) and if not will fetch image data by its post id from 'wp_posts' table..
function getAttachmentOrItsReferenceUrl($code, $allAttachmentById, $allPostMetaByKey) {
global $wpdb;
$attachmentUrl = $allAttachmentById[$allPostMetaByKey[$code]];
if($attachmentUrl === null && isset($allPostMetaByKey[$code]) && $allPostMetaByKey[$code] !== '') {
$attachments = $wpdb->get_results("select ID,guid from wp_posts where ID = " . $allPostMetaByKey[$code]);
foreach ($attachments as $image) {
return $image->guid;
break;
}
} else {
return $attachmentUrl;
}
return "";
}
Finnal thoughts :
If you know your fields structure you can build up its key pretty straightforward
For an example
'rooms' repeater which has inside 'room_gallery' repeater which has inside 'image' image field.
'rooms_0_room_gallery_0_image'
--
$mapMetas['rooms'] - number of rooms
$mapMetas['rooms_' . $i . '_room_gallery'] - number of gallery images for room
--
'rooms_' . $i . '_room_gallery_' . $j . '_image'
How heavy is it ? Not really, if you are like me, having only small amount of referenced images you wont even feel it. All we added to load is an one meta query for an post, if no images are referenced that's all, then for every referenced image there is one more query, but its query on ID should be fast. There is a lot of way how to make load less, for example not do query for every single missing image, but do single query at the end with 'where in (id1,id2..)' to have single query to get all, or after fetching new image data, store it in some global array by its id, and check this array before fetching image for next posts (so if one image is stored in 200 posts, and you are fetching all it would make only 1 query), you got the point I leave it just at the ideas since this is bloody long post even without that :)
Hope this will help anybody in future.

Wordpress creating plugin for most viewed posts problem?

I just want to create plugin that will when visitor(user,visitor,...) visit some post,remember what post,and to increment counter of that post,I wrote this code,but sometimes,counter is incremented,even post isn't viewed,or post with other Id is added to a table.Can someone help me with this,please.I know that there are plugins for this that I'm trying to do,but still want to write this plugin.
function IncrementPostCount($the_content) {
global $post;
global $wpdb;
if(($post->post_status == 'publish') && (int)$post->ID) {
if(is_single()) { // just for single post - not for page
$postID = (int)$post->ID;
$postTitle = urlencode($post->post_title);
$postLink = urlencode(get_permalink($post->ID));
$oneRow = $wpdb->get_row("SELECT * FROM wp_postovi WHERE postAjDi='$postID'");
if(empty ($oneRow)) {
$postCounter = 1;
$data_array = array(
'readnTimes' => $postCounter,
'linkPost'=>$postLink,
'TitlePost'=>$postTitle,
'postAjDi'=>$postID);
$wpdb->insert('wp_najcitaniji_postovi', $data_array);
}
else {
$postCounter = intval($oneRow->readnTimes) + 1;
$data_array = array('readnTimes' => $postCounter);
$where_array = array('postAjDi'=>intval($oneRow->postAjDi));
$wpdb->update('wp_postovi',$data_array,$where_array);
}
return $the_content;
}
return $the_content;
}
}
add_filter('the_content','IncrementPostCount');
Sorry on my bad english,tnx in advance.
Here's how to do that with the postmeta table.
function IncrementPostCount(){
if(is_single()){
global $wp_query;
$count = get_post_meta( $wp_query->post->ID, 'readnTimes', true );
$count = empty($count) ? 1 : $count + 1;
add_post_meta($wp_query->post->ID, 'readnTimes', $count, true) or update_post_meta($wp_query->post->ID, 'readnTimes', $count);
}
}
add_action( 'template_redirect', 'IncrementPostCount' );
Also, it's better to hook it in earlier. That way, the count is only incremented once per page load (the_content can be fired multiple times per page, even on a single page. template_redirect only fires once per request). Also, if you store the data at template_redirect, you can use the updated view count in the template, giving your visitors an even more accurate view count.
And you don't need to worry about database tables, custom SQL, or any of that.

Resources