wc_create_order making duplicates - woocommerce

I am writing a batch process to import some orders from a mysql table called tbl_bankimport:
$bankrecs = $wpdb->get_results("Select * from tbl_bankimport");
foreach ($bankrecs as $bankrec) {
$order = wc_create_order(array(['customer_id' => $bankrec->user_id]));
echo "Order " . $order->id . "created </br>";
}
In my test file I have 47 record and when I run this I get 47 order # echoed on the screen. But I end up with 94 records in woocommerce!
If I clear down the orders replace this:
$order = wc_create_order(array(['customer_id' => $bankrec->user_id]));
with
$order = wc_create_order(array(['customer_id' => 7072]));
and then re-run I get 47 records entered.
Bemused and mystified!

Excluding the customer_id from the data array and adding it like this:
update_post_meta($order->id, '_customer_user', bankrecs->user_id );
seems to be a solution.

Related

Wordpress meta query

I have created a theme with a custom post type of reports. I installed a rating plugin that interacts with this post type, allowing users to rate the reports. It stores the post rating in two fields, sum and count where sum is the total for all ratings, and count is the number of individual ratings.
Example: If a 5 people rated a post as 1, 2, 3, 4, and 5, the sum would be 15 and the count would be 5.
When a user visits the reports archive page, they see a list of all posts of the report post type. However, I want to add a query parameter to filter down to posts with an average rating of 4 or higher. I'm currently trying to use the pre_get_posts hook as follows:
add_filter( 'pre_get_posts', 'filterReports' );
function filterReports( $query ) {
if( is_post_type_archive( 'reports' ) && $_GET['top'] ) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT *
FROM
wp_postmeta AS sum
wp_postmeta AS count
WHERE
sum.meta_key = 'sum' AND
count.meta_key = 'count' AND
sum.meta_value / count.meta_value >= 4"
);
}
}
I'm not entirely sure how to construct my custom query in the above. Any advice would be greatly appreciated.
Use below code will work as per your scenario.
add_filter( 'pre_get_posts', 'filterReports' );
function filterReports( $query ) {
if( is_post_type_archive( 'reports' ) && $_GET['top'] ) {
$reports_meta_query = $query->get('meta_query');
//Add our meta query to the original meta queries
$reports_meta_query[] = array(
'key'=>'count',
'value'=> 4,
'compare'=>'>=',
);
$query->set('meta_query',$reports_meta_query);
// somehow construct a query that checks if sum / count >= 4
}
}
Pretty sure the query you are looking for is something like this:
SELECT
sum.post_id,
sum.meta_value,
count.meta_value,
(sum.meta_value / count.meta_value) as result
FROM
wp_postmeta sum
LEFT JOIN wp_postmeta count USING(post_id)
WHERE
sum.meta_key = 'sum' AND
count.meta_key = 'count'
HAVING
result >= 4
You are basically joining twice the same table based on the post_id, so you can then query by the meta_key of both sum and count, then you look for the result of your math in a Having clause to check if the result would be bigger than 4 as requested.
Hope with this you can get what you were looking for.
Cheers

$wpdb query not working in plugin

I'm trying to do a reviews plugin in Wordpress, and one of the thing that my client asked me to do was that a user should be able to rate only one product every 24 horas, so I have to establish a limit between rates.
And I made this function to check if the limit is reached:
function isAllowedToRate(){
global $wpdb;
$currentDate = date('Y-m-d');
$userId = get_current_user_id();
$table_name = $wpdb->prefix .'esvn_reviews';
$isAllowed = $wpdb->get_results(
"
SELECT user_id, fecha_calificacion
FROM {$table_name}
WHERE user_id = {$userId}
AND review_date = {$currentDate}
"
);
if( count( $isAllowed > 0 ) ){
return false;
}else{
return true;
}
}
But every time I try to run it, it returns false, the error that I'm getting from Wordpress is this:
<div id='error'>
<p class='wpdberror'><strong>WordPress database error:</strong> []<br />
<code>
SELECT user_id, fecha_calificacion
FROM wp_esvn_reviews
WHERE user_id = 6
AND fecha_calificacion = 2015-10-30
</code></p>
</div>
If I take the same SQL and run it directly into the database, it works like a charm, but I keep getting this error from Wordpress and the function always returns false.
You need to wrap your date in single quotes.
$isAllowed = $wpdb->get_results(
"
SELECT user_id, fecha_calificacion
FROM {$table_name}
WHERE user_id = {$userId}
AND review_date = '{$currentDate}'
"
);
Alternatively you could use $wpdb->prepare(), which I would consider better form.
$sql = <<<SQL
SELECT user_id, fecha_calificacion
FROM {$table_name}
WHERE
user_id = %d -- this is formatted as a (d)igit
AND review_date = %s -- this is formatted as a (s)tring
SQL;
$isAllowed = $wpdb->get_results($wpdb->prepare($sql, $userId, $currentDate));
I found the error (in case someone needs the information in the future), I was treating $isAllowed as an array, and it was an object, I had to add ARRAY_A to the get_results:
$isAllowed = $wpdb->get_results(
"
SELECT user_id, fecha_calificacion
FROM {$table_name}
WHERE user_id = {$userId}
AND review_date = {$currentDate}
",ARRAY_A /* output as an associative array */
);

Drupal 7 | update multiple rows using db_update

I have an array like
Array (
[1] => 85590762,22412382,97998072
[3] => 22412382
)
Where key is the item_id and value is the value of a column which I need to update against an item. I can use db_update in a loop but i want to avoid this strategy due to performance. I want to update all the rows in a single db call. Also using db_query I think will not be a good idea. So is there any way using db_update to update these rows?
According to above data, standard mysql queries will be like
update items set sold= 1, users = '85590762,22412382,97998072' Where item_id = 1;
update items set sold = 1, users = '22412382' Where item_id = 3;
Unfortunately you can't do that. Currently and probably in the feature, there won't be support for updating (multiple) values on different rows with different values with one db_update.
If your data is this:
$for_update = array(
1 => "85590762,22412382,97998072",
3 => "22412382",
);
You can do these:
CASE 1: Create a loop and call every time the update function:
foreach ($for_update as $key => $value) {
$fields = array('sold' => 1, 'users' => $value);
db_update('items')->fields($fields)->condition('item_id', $key, '=')->execute();
}
Pros: other modules can hook inside your query, can support multiple DB drivers
Cons: Object initialization is slow, lots of DB connection/traffic
CASE 2: It's faster if you use db_query()...
... because in that case there's no object instantiation/operation, which is a bit costly and other conversion. (i.e.: https://drupal.stackexchange.com/questions/129669/whats-faster-db-query-db-select-or-entityfieldquery)
foreach ($for_update as $key => $value) {
db_query("UPDATE items SET sold = :sold, users = :users WHERE item_id = :item_id",
array(':sold' => 1, ':users' => $value, ':item_id' => $key)
);
}
Pros: no object initialization and operation < so it's faster
Cons: other modules can't hook inside your query, your code can break under different DB drivers, lots of DB connection/traffic
CASE 3: Also you can use transaction
This can boost a bit your performance at some case.
$transaction = db_transaction();
try {
foreach ($for_update as $key => $value) {
$fields = array('sold' => 1, 'users' => $value);
db_update('items')->fields($fields)->condition('item_id', $key, '=')->execute();
}
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('my_type', $e);
}
Note: transaction mostly used, when you want everything or nothing. But with some DB drivers, you can optimize out the COMMIT commands.
In case 1 you get something like this:
UPDATE items SET sold = 1, users = '85590762,22412382,97998072' WHERE item_id = 1;
COMMIT;
UPDATE items SET sold = 1, users = '22412382' WHERE item_id = 3;
COMMIT;
With transaction you do something like this:
UPDATE items SET sold = 1, users = '85590762,22412382,97998072' WHERE item_id = 1;
UPDATE items SET sold = 1, users = '22412382' WHERE item_id = 3;
COMMIT;
With COMMIT you write out the data to the final place (into long-term storage, until this, it's only somewhere in the memory or a temporary place). When you use rollback, you drop these changes. At least this is how I understand this. (i.e. I get performance improvement with this when I used sqlite.)
Same as case 1 or 2 +
Pros: you can rollback your data if you need to be consistent, you write out data only once to the drive < so sql only "once" do IO, lots of DB connection/traffic
CASE 4: Or you can build one sql statement for this:
$ids = array();
$when_then = "";
foreach ($for_update as $key => $value) {
$ids[] = $key;
$when_then .= "WHEN $key THEN '$value' ";
}
db_query("UPDATE items
SET sold = 1, users = CASE item_id
$when_then
ELSE users
END
WHERE item_id IN(:item_ids)", array(':item_ids' => $ids));
Probably this is the fastest way from all from here.
NOTE: $when_then variable doesn't contain the best solution, it's better if you can use the params or you escape unsafe data.
Pros: between your server and sql there will be only one request and less redundant data, one bigger sql queries faster then a lot of small ones
Cons: other modules can't hook inside your query, your code can break under different DB drivers
Also please note, after (I think) PHP 5.3 you can't run more than one statement in db_query or in PDO by default, because of it can be an SQL Injection, so it blocks. However you can set this, but not recommended. (PDO support for multiple queries (PDO_MYSQL, PDO_MYSQLND))
13-05-2019 edit:
Just a side note, I did a few month ago some performance measurement on medium dataset to query down some data via db_select and db_query. The magnitude of the performance of these are: under you run one db_select, you can run two db_query. This also applies to db_update and db_query. The test queried down some columns with a simple SELECT, no JOINS, no fancy dandy stuff, just two condition. Of course, the measurement also contained/included the time which was required to build up these queries (at both case). However please note, the Drupal Coding Standard requires to use db_update, db_delete, db_merge on modification normally, only 'allows' db_query instead of db_select operation.
Using BULK UPDATE you can update multiple values on diferent rows or in the same row with the dinamic query db_update .
Note: By bulk updating only one query can be sent to the server instead of one query for each row to update.
Syntax:
UPDATE yourTableName
SET yourUpdateColumnName =
(CASE yourConditionColumnName WHEN Value1 THEN 'UpdatedValue'
WHEN Value2 THEN 'UpdatedValue'
.
.
N
END)
WHERE yourConditionColumnName IN(Value1,Value2,.....N);
More info about expressions on UpdateQuery on:
https://api.drupal.org/api/drupal/includes%21database%21query.inc/function/UpdateQuery%3A%3Aexpression/7.x
With your data will be:
//Create array
$for_update = [
1 => "85590762,22412382,97998072",
3 => "22412382",
];
$item_ids_to_filter = [];
//Setup when-then conditions
$when_then = "CASE item_id ";
foreach ($for_update as $item_id => $users_id) {
$when_then .= "WHEN {$item_id} THEN '{$users_id}' ";
$item_ids_to_filter[] = $item_id;
}
$when_then .= "END";
//Execute query and update the data with db_update
db_update('items')
->expression("users",$when_then)
->condition('item_id', $item_ids_to_filter)
->execute();
As result the next query is generated:
UPDATE items
SET users =
(CASE item_id = WHEN 1 THEN '85590762,22412382,97998072'
WHEN 3 THEN '22412382'
END)
WHERE (item_id IN ('1', '3'))
I think you could do something like
$query = db_udpate('table')->fields(array('field_1','field_2','field_3'));
foreach ($fields as $record) {
$query->values($record);
}
return $query->execute();
Where $fields = array ('field_1' => VALUE, 'field_2' => VALUE)

How to prevent `out of stock` items to show as a filter option in the sidebar (layered nav widget ) in WooCommerce?

I have 'hide out of stock' checked in the settings, however when using the layered navigation widget to filter results by shoe size, it is returning products which have that size listed as an attribute, but the size is out of stock. Is there a fix to this?
WordPress version 3.9.1, WooCommerce version 2.1.7
http://www.foten.se
He means not showing the products in the filter results. If a user filters by size M, he wants to see available size M products in the results, not All products that used to have a size M but dont anymore...
This is something woocommerce has not solved in years! and something that anyone opening a woocommerce store should know. I am sure most of them wont use woocommerce because of this issue alone.
I solve it by running the following script every night
$dir = dirname(__FILE__);
include_once($dir . '/../../../../wp-load.php');
$currentCount = 0;
$pageSize = 10;
global $wpdb;
$run = true;
while ($run) {
// select all variation of products with stock == 0
$sql = "SELECT * FROM `wpmf_postmeta` as pm
LEFT JOIN wpmf_posts as p on p.ID = pm.post_id
WHERE
meta_key = '_stock' and meta_value = 0
and p.post_parent <> 0 LIMIT $currentCount," . ($currentCount + $pageSize);
// var_dump($sql);die;
$res = $wpdb->get_results($sql, ARRAY_A);
if (!$res) { //|| $currentCount > 20
$run = false;
break;
}
foreach ($res as $r) {
$post_parent = $r['post_parent'];
$post_excerpt = $r['post_excerpt'];
$size = preg_replace('/[^0-9.]/', '', $post_excerpt);
// echo($post_parent . ", $size" . '<br>');
if ($size && $size != '') {
// find the term ID and delete it from parent product
$res_term = $wpdb->get_results("SELECT * FROM `wpmf_term_relationships` as tr
LEFT JOIN wpmf_terms as t on t.term_id = tr.term_taxonomy_id
where `object_id` = $post_parent and name = $size", ARRAY_A);
// var_dump($terms_rel);
if ($res_term) {
$query = "
DELETE FROM wpmf_term_relationships
WHERE term_taxonomy_id = " . $res_term[0]['term_id'] . "
AND object_id = " . $post_parent . "
";
$res_query = $wpdb->query(
$query);
echo($post_parent . ", $size, $res_query" . '<br>');
}
}
$currentCount++;
}
}
wp_cache_flush();
echo 'done! ' . $currentCount;
The problem this script fixes is: the sidebar filters the product by attribute but the stock is manage by product variation (child post).
The DB doesn't have a way to link the 2 fields, therefore it is not possible to create a query that filters attributes that has a matching variation with stock == 0.
Therefore this script solve the problem by deleting products attributes that have stock == 0
here is a opposite script to set an attribute for a product with stock > 0 and a missing attr:
$dir = dirname(__FILE__);
include_once($dir . '/../../../../wp-load.php');
$currentCount = 0;
$pageSize = 10;
global $wpdb;
$run = true;
while ($run) {
// select all varaition of a prod with stock > 0
$sql = "SELECT * FROM `wpmf_postmeta` as pm
LEFT JOIN wpmf_posts as p on p.ID = pm.post_id
WHERE
meta_key = '_stock' and meta_value <> 0
and p.post_parent <> 0 LIMIT $currentCount," . ($currentCount + $pageSize);
// var_dump($sql);die;
$res = $wpdb->get_results($sql, ARRAY_A);
if (!$res) { //|| $currentCount > 20
$run = false;
break;
}
foreach ($res as $r) {
$post_parent = $r['post_parent'];
$post_excerpt = $r['post_excerpt'];
$size = preg_replace('/[^0-9.]/', '', $post_excerpt);
if ($size && $size != '') {
// find the relevant term
$res_parent = $wpdb->get_results("SELECT * FROM `wpmf_term_relationships` as tr
LEFT JOIN wpmf_terms as t on t.term_id = tr.term_taxonomy_id
where `object_id` = $post_parent and name = $size", ARRAY_A);
// var_dump($terms_rel);
// if term is missing, create it
if (!$res_parent) {
wp_set_object_terms($post_parent, $size, 'pa_size', true);
echo($post_parent . ", $size" . '<br>');
}
$currentCount++;
}
}
}
wp_cache_flush();
echo 'done! ' . $currentCount;
NOTE:
this is not a proper solution to the problem - a solution should be at the design level, i.e. finding a way to link the 2 fields with an SQL query, this solution is a temp by-pass until a real solution is available
I didn't fully tested this code, I will update the answer if necessary in the future
I'm not fully familiar with the DB structure of wordpress or woocommerce, and the result of this code might change depending on different plugins, using this code is at your own risk
In WooCommerce, the default option is that, it shows all products that are in stock and out of stock on search results and the product category pages. This is a WooCommerce default functionality
Link regarding this:
https://github.com/woothemes/woocommerce/issues/5840
Also, not showing these products on the contrary is a bad idea, as it is not as per the best SEO practices.

Delete posts with custom field date older than current date via cronjob

I want to make a cron job witch deletes all posts older than the date in a custom field of the post. I got following function within my functions.php My custom field name is bewerbungs_frist.
function foobar_truncate_posts(){
global $wpdb;
$currenttime = new DateTime();
$currenttime_string = $currenttime->format('Ymd');
# Set your threshold of max posts and post_type name
$post_type = 'job';
# Query post type
$query = "
SELECT ID FROM $wpdb->posts
WHERE post_type = '$post_type'
AND post_status = 'publish'
ORDER BY post_modified DESC
";
$results = $wpdb->get_results($query);
# Check if there are any results
if(count($results)){
foreach($results as $post){
$customfield = get_field('bewerbungs_frist', $post->ID);
$customfield_object = new DateTime($customfield);
$customfield_string = $customfield_object->format('Ymd');
if ( $customfield_string < $currenttime_string ) {
echo "The value of the custom date field is in the past";
echo $customfield_string;
$purge = wp_delete_post($post->ID);
}
}
}
}
foobar_truncate_posts();
I use a plugin to handle my cronjobs. The Hock name is: foobar_truncate_posts and Arguments is []
The cronjob works but it does not delete those post with the date of the customfield older than todays date. The two variables are the same.
$currenttime_string 20130820
$customfield_string 20130820
there's a typo in your code, you're missing an 's' at the end of $result.
This:
foreach($result as $post){
Should be this:
foreach($results as $post){
I just tried it out. Once you make that fix the wp_delete_post() works great.
EDIT
I'm really unclear on what you're trying to do. You want to check if the custom field is set to some time in the past? What purpose does the continue serve? Also, I'm guessing you're using Advanced Custom Fields (ACF). Using the jquery Date Picker, you can format your date to Ymd by default so you don't have to convert it to a DateTime object.
At any rate, this function should explain how to properly set and compare time values, you should be able to take it from there:
function foobar_truncate_posts(){
global $wpdb;
$currenttime = new DateTime();
$currenttime_string = $currenttime->format('Ymd');
# Set your threshold of max posts and post_type name
$post_type = 'post_type_job';
# Query post type
$query = "
SELECT ID FROM $wpdb->posts
WHERE post_type = '$post_type'
AND post_status = 'publish'
ORDER BY post_modified DESC
";
$results = $wpdb->get_results($query);
# Check if there are any results
if(count($results)){
foreach($results as $post){
$customfield = get_field('bewerbungs_frist', $post->ID);
$customfield_object = new DateTime($customfield);
$customfield_string = $customfield_object->format('Ymd');
if ( $customfield_string < $currenttime_string ) {
echo "The value of the custom date field is in the past";
$purge = wp_delete_post($post->ID);
}
}
}
}
foobar_truncate_posts();

Resources