Wordpress Validate New Nicename/Slug - wordpress

I have written a custom "Edit Account" script that allows a Wordpress user to update their Wordpress account. Everything is working great, except that I can't seem to find a way to update the user's nicename, which also doubles as the user's URL slug (via the get_author_posts_url function). This is causing issues because when a user changes their name, their slug still contains their original name - not the new one.
I know that the sanitize_title function will generate the new nicename, but I don't know how to verify that it is unique and modify it if it is not before entering it into the DB. I am wondering what built-in functions Wordpress has to handle this. I know I can write my own script to do this, but I would much rather use Wordpress functions. I couldn't find this anywhere in the WP documentation. Thanks!

Here is a function I have written to in lue of a built in function:
function new_user_slug($string){
//GENERATE NEW SLUG
$slug=sanitize_title($string);
//MAKE SURE SLUG IS UNIQUE
$result=mysql_query("SELECT * FROM wp_users WHERE user_nicename='$slug'");
if(mysql_num_rows($result)==0){
return $slug;
}else{
$counter=2;
$kill=0;
while($kill==0){
$mod_slug=$slug."-".$counter;
$result=mysql_query("SELECT * FROM wp_users WHERE user_nicename='$mod_slug'");
if(mysql_num_rows($result)==0){
$kill=1;
}else{
$counter++;
}
}
return $mod_slug;
}
}
This takes a string (the user's updated name) and converts it into the default slug. It then checks the slug against the database to see if it is unique. If it is, the slug is returned. If not, it enters an iteration loop that incrementally changes the slug until it is unique.

Try this:
wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent )
Source: https://codex.wordpress.org/Function_Reference/wp_unique_post_slug

Actually if you use wordpress user functions like wp_insert_user and wp_update_user Wordpress itselt handles duplicate user_nicename entries and adds -n suffix to them.
So your code would be something like this:
function new_nicename( $user_id, $nicename ) {
$nicename = sanitize_title( $nicename );
$user_id = wp_update_user( array( 'ID' => $user_id, 'user_nicename' => $nicename ) );
}

Related

Headless Wordpress, Is there a way to access data in wp_options table as REST endpoint?

Wordpress has an awesome REST API interface.
https://developer.wordpress.org/rest-api/reference/
But the content in wp_options table seems to be missing REST support. Is there a way to access the content in wp_otions table as REST endpoint via plugins?. Thanks.
There is the settings endpoint, but it only contains a surprisingly limited amount of them it seems.
This is something you could very easily do yourself though. I'm not sure if any plugins do it, but I also wouldn't recommend a plugin for something that can be done with less than 20 lines of code.
You just need to register a route using register_rest_route() on the rest_api_init hook, and pass it a callback function. You can drop code like this in your functions.php file or create a Must Use Plugin and drop the code in there, either way.
add_action( 'rest_api_init', function () {
register_rest_route( 'my-custom-route/v1', '/opt/', array(
'methods' => 'GET',
'callback' => 'get_rest_option',
//'permission_callback' => function () {
// return current_user_can( 'administrator' );
//}
) );
} );
function get_rest_option( $data ) {
return get_option( $data['option_name'] );
}
The above will give you access to whatever option you want by accessing:
/wp-json/my-custom-route/v1/opt/?option_name=siteurl
I went ahead and dropped an example on a site of mine:
https://xhynk.com/content-mask/wp-json/my-custom-route/v1/opt/?option_name=blogname
https://xhynk.com/content-mask/wp-json/my-custom-route/v1/opt/?option_name=siteurl
However, this will potentially expose anything in your options table. I went ahead and commented out the permission_callback so that any person, signed in or not, can access it. However, I also added a check like this:
function get_rest_option( $data ) {
if( $data['option_name'] === 'siteurl' || $data['option_name'] === 'blogname' ){
return get_option( $data['option_name'] );
} else {
return 'Unauthorized. Use `siteurl` or `blogname`';
}
}
You can see that home will fail: https://xhynk.com/content-mask/wp-json/my-custom-route/v1/opt/?option_name=home
I would recommend adding in a valid array of options, or using the permission_callback in order to lock it down a bit. You could even have an access key instead, and keep that key secret. Either way, be aware of the security implications of exposing your entire wp_options table, and take some sort of preventative measure!

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);
}
}
}

WP Settings API save to multiple options

In the Wordpress Settings API, creating a new options page usually starts out with
register_setting('sample_options', 'my_option');
add_settings_section('section', 'Sample Options', 'callback1', 'page');
add_settings_field('name', 'Label', 'callback2', 'page', 'section');
In this simplified example, the data gets saved in the option my_option making the value of name accessible through
$option = get_option('my_option');
$name = $option['name']; // Got it
But what if the value of the name field is there not to place a new value but to update an already existing option that's not my_option like for example this_other_option? I guess what I'm really looking for is is it possible for one field to save to multiple options (my_option and this_other_option) while using the Settings API?
I suppose you could use a callback in register_setting. It would look something like the following:
<?php
register_setting('sample_options', 'my_option', 'my_sanitize_callback');
function my_sanitize_callback($value, $option) {
$value = mysql_real_escape_string($value);
update_option('my_other_option', $value);
return $value;
}
?>
You may have to tweak that a bit. I haven't tested it.

Easiest way to hide (some) WordPress plugins from users?

I'm using WordPress to make my users make their own website/blog. I have a set up that I'm cloning out to all the users with some special user-roles and standard plugins.
However, some of the plugins are not supposed to be changed or inactivated by the users.
Is their any way to select which plugins different user roles are allowed to use? Or a easy way to hide some plugins in the plugins-page but still have them working as normal?
Maybe there's some plugin that helps me to do this?
You could write a plugin that uses the "all_plugins" filter hook to remove from the array plugins that you don't want displaying for a certain user. Something like this:
$plugin_credentials = array(
'bob' => array(
'Hello Dolly' => 1
),
'jim' => array(
'Akismet' => 1,
'Hello Dolly' => 1,
),
'admin' => "**ALL**"
);
function plugin_permissions($plugins)
{
global $current_user, $plugin_credentials;
$username = $current_user->user_login;
if ($plugin_credentials[$username] == "**ALL**")
return $plugins;
$viewable_plugins = array();
foreach ($plugins as $plugin) {
if (isset($plugin_credentials[$username]) &&
isset($plugin_credentials[$username][$plugin['Name']]) &&
$plugin_credentials[$username][$plugin['Name']] == 1) {
array_push($viewable_plugins, $plugin);
}
}
return $viewable_plugins;
}
add_filter('all_plugins', 'plugin_permissions');
Managing the user permissions in the plugin itself is not ideal, but it is probably easiest. You can expand on that idea to create admin pages for managing the users and their viewable plugins in a database table somewhere.
Each plugin will usually specify their own role/permission, which you can see if you look at their add_submenu_page() or such function calls. You can create new roles for those plugins and replace the one specified by the author, but it will also break the changes if you upgrade the plugins.
You should stratify the users. Make sure that the Admin user(s) are trusted and know not to fiddle with what they don't understand. The others should be limited to their roles. Authors, editors, etc. For example, if they're just a part of the site to write articles, then they don't need to see the rest of it. Make them an author and be done with it.
This is part of client education. If its a smaller client with less stratified roles, then make them two accounts. Tell them "this is the account you administer the site with, you'll be using this rarely. And this is the account that you'll use most of the time to write and edit. You can do all of your daily tasks here and will most likely never need the administrator account". You won't always have luck with this approach, but its less time and effort invested in crap you shouldn't be wasting time on.
I've done a new version based on #spuriousdata Answer. This one uses the plugin slugs (file name minus the extension) to build the list of restrictions. This way is easier as we can unset the array using the first level $keys.
Configuration instructions in the code itself.
<?php
/**
* Plugin Name: Limit Plugins by User
* Plugin URI: http://stackoverflow.com/q/14340131/1287812
* Description: Show selected plugins for specific users.
* Based on the code by spuriousdata, http://stackoverflow.com/a/3713985.
* Author: brasofilo
* Author URI: http://wordpress.stackexchange.com/users/12615/brasofilo
* Version: 1.0
* License: GPLv2 or later
*/
add_filter( 'all_plugins', 'plugin_permissions_so_3707134' );
/**
* Filter the list of plugins according to user_login
*
* Usage: configure the variable $plugin_credentials, which holds a list of users and their plugins.
* To give full access, put a simple string "ALL"
* To grant only for some plugins, create an array with the Plugin Slug,
* which is the file name without extension (akismet.php, hello.php)
*
* #return array List of plugins
*/
function plugin_permissions_so_3707134( $plugins )
{
// Config
$plugin_credentials = array(
'admin' => "ALL",
'other-admin' => array(
'akismet',
),
'another-admin' => array(
'akismet',
'hello',
),
);
// Current user
global $current_user;
$username = $current_user->user_login;
// Super admin, return everything
if ( "ALL" == $plugin_credentials[ $username ] )
return $plugins;
// Filter the plugins of the user
foreach ( $plugins as $key => $value )
{
// Get the file name minus extension
$plugin_slug = basename( $key, '.php' );
// If not in the list of allowed plugins, remove from array
if( !in_array( $plugin_slug, $plugin_credentials[ $username ] ) )
unset( $plugins[ $key ] );
}
return $plugins;
}

Resources