Wordpress: How to flush cached option value - wordpress

I am currently working on a plugin, and I am stuck with an issue.
I executed an SQL request on my database through PhpMyAdmin (which I implemented later through a plugin update mechanism), the request looked like this:
UPDATE `wp_options`
SET `option_value` = replace( `option_value` , 'model', 'ldp_model' )
WHERE `option_name` LIKE 'ldp_container%'
As you can see, I am updating all options value having name beginning by 'ldp_container'. Later in the code execution, when I am retrieving the option value, I am getting a value being false using:
$termId = $term->term_id;
$termMeta = get_option("ldp_container_$termId"); // $termMeta = false
I looked in this issue, and I got to the point where it seems that when I update/create this option, as I used:
update_option("ldp_container_$termID", $termMeta);
instead of
update_option("ldp_container_$termID", $termMeta, false);
The value of this option become part of the alloptions cache. So, retrieving it always return false now, and I do not know how to flush this cache.
Using a plugin update mechanism based on version number, I tried to flush the Wordpress object cache using:
$flush_cache = wp_cache_flush(); // returns true
And the Database query cache too:
$wpdb->flush();
I also tried explicitely deleting those options keys from the cache using a query then looping on results and calling wp_cache_delete:
$result = $wpdb->get_results(
"SELECT 'option_name'
FROM $wpdb->options
WHERE 'option_name' LIKE '%ldp_container_%';"
);
foreach ( $result as $current ) {
wp_cache_delete($current, 'options');
}
Still, no luck.
Does anybody have a clue for me here ?
Thanks,
EDIT:
Seems like my error is somewhere else.
After some debugging using Atom and Xdebug, it points out that the issue is with the unserialize method applied to the string retrieved from the database in the file /wp-includes/option.php as follows:
return apply_filters( 'option_' . $option, maybe_unserialize( $value ) );
maybe_unserialize do that:
if ( is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
return unserialize( $original );
return $original;
The call to the unserialize fails, so it returns false and then the false value is cached...
Using an online unserializer it confirms that the string is not correct, but I do not get why yet.

So, as I assumed my issue was due to the modification in serialized objects I was executing directly on the database. Because of the nature of serialized objects, looking like this:
a:1:{s:9:"ldp_model";s:1651:"{...
The 1651 being the number of characters in the ldp_model string, if you change anything inside this sting, then unserializing using unserialize() will return false, because the actual number of characters is different from the one indicated.
In my case, the solution was to instead of executing a database update of this kind:
$wpdb->query(
"UPDATE $wpdb->options
SET `option_value` = replace( `option_value` , 'ldp_', '' );"
);
To use the Options API as follows:
$result = $wpdb->get_results(
"SELECT `option_name`
FROM $wpdb->options
WHERE `option_name` LIKE '%ldp_container_%';"
);
foreach ( $result as $current ) {
$option = get_option($current->option_name);
if (!empty($option) && !empty($option['ldp_model'])) {
$option['ldp_model'] = str_replace('ldp_', '', $option['ldp_model']);
update_option($current->option_name, $option, false);
}
}
So that the serialized objects stays correct, and the Options API takes care of serialization and unserialization.
Lots of learning here for me ;-)

Related

WooCommerce Eway plugin not creating customer token for new customer

I have setup a fresh docker container with Wordpress 5.0.3 and the latest WC and WC Eway plugin (WooCommerce eWAY Gateway).
Created a store with some products, hooked up my Eway sandbox environment, enabled Save Cards (which would enable the token) and created an order.
After checking the post_meta in my DB for the order, I didn't see a _eway_token_customer_id field. While being logged in as a customer, I tried again and with the new order I still do not get a token.
The reason for this tests is that I got this strange behaviour in my real, new website, where the first order with a NEW customer, doesn't result in a token.
However, when I create a second order whilst being logged in, I do get a _eway_token_customer_id value within the order_meta.
It is imperative for me to get that token with the first order, because after that I will auto renew the product using the tokenp ayment option.
Debugging this issue is hell, and I find it very disconcerting that on my fresh WP installation I get no token at all.
Is there anyone that has a bright idea?
**update
After some digging around in the Eway Plugin, I found out that the first time I do an order, the function request_access_code() from the class WC_Gateway_EWAY is checking if there is a token in the database for this user.
The function body:
protected function request_access_code( $order ) {
$token_payment = $this->get_token_customer_id( $order );
if ( $token_payment && 'new' === $token_payment ) {
$result = json_decode( $this->get_api()->request_access_code( $order, 'TokenPayment', 'Recurring' ) );
} elseif ( 0 === $order->get_total() && 'shop_subscription' === ( version_compare( WC_VERSION, '3.0', '<' ) ? $order->order_type : $order->get_type() ) ) {
$result = json_decode( $this->get_api()->request_access_code( $order, 'CreateTokenCustomer', 'Recurring' ) );
} else {
$result = json_decode( $this->get_api()->request_access_code( $order ) );
}
if ( isset( $result->Errors ) && ! is_null( $result->Errors ) ) {
throw new Exception( $this->response_message_lookup( $result->Errors ) );
}
return $result;
}
The function handles three possible outcomes:
1) new customer: results in calling `$this->get_api()->request_access_code( $order, 'TokenPayment', 'Recurring' )` <-- this is the one we are after!
2) shop_subscription: calls `$this->get_api()->request_access_code( $order, 'CreateTokenCustomer', 'Recurring' )`
3) else..: calls `$this->get_api()->request_access_code( $order )`
What is happening during debugging, is that the $token_payment variable has the value of an empty string for a new customer, instead of new.
So I will attempt to fix this, either via a filter/action hook, or figure out why this is happening.
When I forced the function the always use the first if block, I got my token. :)
**Update 2:
I tested with an existing user account, created a new order.
When I look in the post_meta table:
Voila, the new value is present.
However, when I am not logged in and I create an account, the new value is not added and that is where it goes wrong.
A temp fix would be to use a hook and add the new value to the order so that when get_token_customer_id is called it retrieves a new value and not an empty string.
I think this is a bug, since this value should be added. It explains why the second transactions get the token but not the first.
If only Woocommerce Eway plugin had a git repo.... I could flag an issue or fork it.
***Solution without hack
Added this to my plugin (or functions.php if you like):
add_action( 'woocommerce_checkout_order_processed', function( $order_id, $posted_data, $order ) {
update_post_meta( $order_id, '_eway_token_customer_id', 'new' );
}, 10, 3);
This will add the new value when you checkout with a non-existent user.
The token was added nicely after adding my creditcard details.
The matter of the fact stays that the plugin still has a bug, which you can work around.

Regenerating WooCommerce Download Permissions on older orders

I am trying to add some download permissions to all previous orders via a script to do them in batch. The script seems to work fine expect for one thing. Here is the script…
function update_download_permissions(){
$orders = get_posts( array(
'post_type' => 'shop_order',
'post_status' => 'wc-completed',
'posts_per_page' => -1
) );
foreach ( $orders as $order ) {
wc_downloadable_product_permissions( $order->ID, true );
}
}
The problem is the wc_downloadable_product_permissions function is producing duplicate entries in the wp_woocommerce_downloadable_product_permissions table.
I tried to set the second argument to false (the default) but that resulted in no permissions being created.
Does anybody have any ideas as to why duplicate download permissions are being set?
Cheers!
I came across your question after digging through some of the WooCommerce source code, while attempting to add an item to an existing order and then regenerate the permissions.
The reason wc_downloadable_product_permissions() will create duplicate permission entries is because it does not check for any existing permissions. It simply inserts another entry into the permissions table for every item in the order, which is no good because this will then show up as another download in both the admin and user account frontend.
The second force parameter (poorly documented), is related to a boolean flag that indicates whether wc_downloadable_product_permissions() has run before. The boolean is set to true at the end of the function via the set_download_permissions_granted method. If force is true, it will ignore the boolean. If force is false, and the boolean is true, the function will return near the start.
I created this function which uses the same functions as used by the admin Order action "Regenerate download permissions":
/**
* Regenerate the WooCommerce download permissions for an order
* #param Integer $order_id
*/
function regen_woo_downloadable_product_permissions( $order_id ){
// Remove all existing download permissions for this order.
// This uses the same code as the "regenerate download permissions" action in the WP admin (https://github.com/woocommerce/woocommerce/blob/3.5.2/includes/admin/meta-boxes/class-wc-meta-box-order-actions.php#L129-L131)
// An instance of the download's Data Store (WC_Customer_Download_Data_Store) is created and
// uses its method to delete a download permission from the database by order ID.
$data_store = WC_Data_Store::load( 'customer-download' );
$data_store->delete_by_order_id( $order_id );
// Run WooCommerce's built in function to create the permissions for an order (https://docs.woocommerce.com/wc-apidocs/function-wc_downloadable_product_permissions.html)
// Setting the second "force" argument to true makes sure that this ignores the fact that permissions
// have already been generated on the order.
wc_downloadable_product_permissions( $order_id, true );
}
I found the best way to update order download is to hook into the save_post action hook and check if it's a product that's being updated
there you can get order ids by product id and update just orders that relate to that specific product.
it's more efficient
function get_orders_ids_by_product_id($product_id) {
global $wpdb;
$orders_statuses = "'wc-completed', 'wc-processing', 'wc-on-hold'";
return $wpdb->get_col(
"
SELECT DISTINCT woi.order_id
FROM {$wpdb->prefix}woocommerce_order_itemmeta as woim,
{$wpdb->prefix}woocommerce_order_items as woi,
{$wpdb->prefix}posts as p
WHERE woi.order_item_id = woim.order_item_id
AND woi.order_id = p.ID
AND p.post_status IN ( $orders_statuses )
AND woim.meta_key IN ( '_product_id', '_variation_id' )
AND woim.meta_value LIKE '$product_id'
ORDER BY woi.order_item_id DESC"
);
}
// if you don't add 3 as as 4th argument, this will not work as expected
add_action('save_post', 'prefix_on_post_update', 10, 3);
function prefix_on_post_update($post_id, $post, $update) {
if ($post->post_type == 'product') {
$orders_ids = get_orders_ids_by_product_id($post_id);
foreach ($orders_ids as $order_id) {
$data_store = WC_Data_Store::load('customer-download');
$data_store->delete_by_order_id($order_id);
wc_downloadable_product_permissions($order_id, true);
}
}
}

wordpress wpdb->update strange error by converting single quote to html entity

Update Julu 2018:
I find out problem is something else. The print error method will always print out a encoded html message like the one below. If the message is not showing any extra piece of information means the SQL query is fine.
Original Question:
I tried to update the invite_code by using the $wpdb->update method, but it return strange error, it seems like WordPress convert the single quote to html entity - &#39
Please help me if anyone knows why it will convert the single quote to HTML entity automatically.
I am not able to do use any WordPress built-in method to update the query because the problem seems to happen at the prepared statement which will use in every built-in method.
WordPress database error: UPDATE exhibitor_invite SET invite_code =
&#039 ;8j8mxfkkubd0kppi082p&#039 ; WHERE id = 10
function createCode() {
$length = 20;
$inviteCode = "";
$characters = "0123456789abcdefghijklmnopqrstuvwxyz";
for ($p = 0; $p < $length; $p++) {
$inviteCode .= $characters[mt_rand(0, strlen($characters))];
}
return $inviteCode;
}
function updateCode($id) {
global $wpdb;
$wpdb->show_errors();
$prefix = $wpdb->prefix;
$invite_code = createCode() ;
// I tried to esc the string, but it doesn't work
// $invite_code = $wpdb->esc_like($invite_code);
// I also tried to use normal query, but it return the same error
// $affected_rows = $wpdb->query( $wpdb->prepare(
// " UPDATE {$wpdb->prefix}exhibitor_invite SET invite_code = %s WHERE id = %d", $invite_code, $id ));
$affected_rows = $wpdb->update( $prefix.'exhibitor_invite',
array('invite_code' => $invite_code),
array('id' => $id),
'%s',
'%d' );
$wpdb->print_error();
if(!is_bool($affected_rows)) {
return $affected_rows > 0;
}
return $affected_rows;
}
Perhaps way too late, but in case not I had the exact same problem and spent hours looking for a solution.
It seems that the WordPress property 'update' of wpdb object is where the problem occurs.
One solution that I found to work is to store the entire SQL string in a variable and then before using it, pass the variable through a PHP function of mysqli_real_escape_string().
PHP manual states:
This function is used to create a legal SQL string that you can use in an SQL statement. The given string is encoded to an escaped SQL string, taking into account the current character set of the connection.
Your solution may look something like this (untested).
$sql_string =
"
UPDATE ${prefix}exhibitor_invite
SET invite_code = %s
WHERE id = %d
";
//procedural style
mysqli_real_escape_string( $your_conn_to_server, $sql_string );
//update
$wpdb->update( $wpdb->prepare(
$sql_string,
array(
$invite_code,
$id
)
), OBJECT );

How to get data from Wordpress database?

I am trying to get a row out of a table (gc_test) I created in my Wordpress database. I have read the Wordpress documentation and followed it exactly and still no joy.
The $results array never seems to get populated.
$results = $wpdb->get_row("SELECT * FROM $wpdb->gc_test WHERE coupon_code = $code, ARRAY_A");
if($results['redeemable']=="true"){
$message = "Code is good!";
}
else{
$message = "Code has already been redeemed!";
}
The way you are referring to your custom table is not correct. The correct way is to global $wpdb and the prefix property. The way that you have $code embedded is also not correct for a few reasons - it is likely a string and should be surrounded by single quotes, but no matter what is likely a potential SQL injection vulnerability. Make sure to use $wpdb->prepare() to pass arguments with placeholders of %s for strings and %d for digits. You also have the double quotes in the wrong place - it is including ARRAY_A in with your SQL rather than as an argument to get_rows().
// declare $wpdb.
global $wpdb;
// sql string using $wpdb->prefix and %s placeholder
$sql = "SELECT * FROM {$wpdb->prefix}gc_test WHERE coupon_code = %s";
// pass the sql into prepare()
$query = $wpdb->prepare( $sql, $coupon_code );
// call get_row() and tell it that you want an associative array back
$row = $wpdb->get_row( $query, ARRAY_A );
if ( empty( $row ) ){
// nothing came back from the db
$message = "Code not found.";
} elseif ( isset( $row['redeemable'] ) && $row['redeemable'] == "true" ){
// we got a row and it was redeemable
$message = "Code is good!";
} else {
// something else
$message = "Code has already been redeemed!";
}
Thanks to doublesharp's post I went back and studies the $wpdb->prefix function. Apparently this is pretty important. So I changed my prefix on the table from gc_ to wp_ and then assigned the table to a variable with: {$wpdb->prefix}test and it now works like a charm. Thanks a ton!

wordpress plugin creation get_post_meta

I am building my first plugin, and I am using as a reference the following link.
http://www.sitepoint.com/create-a-voting-plugin-for-wordpress/
and I am trying to underestand the following part of the code:
function voteme_addvote()
{
$results = '';
global $wpdb;
$post_ID = $_POST['postid'];
$votemecount = get_post_meta($post_ID, '_votemecount', true) != '' ? get_post_meta($post_ID, '_votemecount', true) : '0';
$votemecountNew = $votemecount + 1;
update_post_meta($post_ID, '_votemecount', $votemecountNew);
$results.='<div class="votescore" >'.$votemecountNew.'</div>';
// Return the String
die($results);
}
I run the code and it works, but I just dont understand the following:
What is "get_post_meta" doing?
Does it create a custom meta field, the same as add_post_meta?, if it doesnt why there is not an add_post_meta?
I checked the DB, and it looks like it is creating a custom meta field... so in that order what is the difference between get_post_meta and add_post_meta?
Thanks very much for helping me understand this.
The first time your code runs, get_post_meta returns '' so $votemecount is set to 0. The following update_post_meta creates the new meta field as documented below. Values that start with _ are not displayed (are hidden meta fields).
The function, update_post_meta(), updates the value of an existing meta key (custom field) for the specified post.
This may be used in place of add_post_meta() function. The first thing this function will do is make sure that $meta_key already exists on $post_id. If it does not, add_post_meta($post_id, $meta_key, $meta_value) is called instead and its result is returned.

Resources