I am attempting to validate fields on a custom post type in the admin panel Edit Post page.
When the user clicks "Publish" I want to validate fields in the POST data, and change the post_status to "pending" if the data does not pass the tests. When this occurs, I'd also like to add errors to the page in the admin notices area.
I've been trying this with an added hook to the "wp_insert_post" action which also saves our own data. I'm not certain of the order of operations, but I'm assuming that the wp_insert_post events happen first, and then my function gets called via the hook.
The problem is that it's the Wordpress function which is doing the post publish actions, so by the time I get to validate data, Wordpress has already saved the post with a status of "publish". What I need to do is either prevent that update, or change the status back to "pending", but I'm having little success in finding a way to do this within the API.
So, here's an order of operations I'd like to effect:
1. admin user edits post data and clicks "Publish"
2. via wp_insert_post, my data validation and post meta save routine is called
3. If data passes validation, post status is "published"
4. Otherwise, post status set to "pending" & message shown in admin notice area
Surely someone has done this, but extensive Googling just leads me to the same seemingly irrelevant pages. Can someone point me in the right direction here? Thanks in advance-
UPDATE:
So, RichardML was indeed correct, hooking to the wp_insert_post_data filter gave me the right place to validate admin post edit page fields. I'm updating this however to note what the rest of the solution is, specifically getting the reason reported in the admin notice area.
First off, you can't just output data or set a field because the admin page is the result of a redirect, and by the time you get to rendering the admin post page again, the admin_notices action is already gone. The trick was something I picked up from another forum, and it's hackish, but it works.
What you'll need to do is in your validation filter function, if you determine that you will need to display errors, is use set_option() to add a blog option with a unique name (I used 'publish_errors'). This should be HTML code in a div with a class of "error".
You will also need to add an action hook for 'admin_notices', pointing at a function which checks for the existence of the 'publish_errors' option, and if it finds it, prints it to the page and deletes it with delete_option().
You can use the wp_insert_post_data filter to inspect and modify post data before it's inserted into the database.
In response to your update I don't think it's necessary to temporarily add an option to the database. It should be possible to simply add a query string variable to the Wordpress redirect, something like this:
add_filter('wp_insert_post_data', 'my_post_data_validator', '99');
function my_post_data_validator($data) {
if ($data['post_type'] == 'post') {
// If post data is invalid then
$data['post_status'] = 'pending';
add_filter('redirect_post_location', 'my_post_redirect_filter', '99');
}
return $data;
}
function my_post_redirect_filter($location) {
remove_filter('redirect_post_location', __FILTER__, '99');
return add_query_arg('my_message', 1, $location);
}
add_action('admin_notices', 'my_post_admin_notices');
function my_post_admin_notices() {
if (!isset($_GET['my_message'])) return;
switch (absint($_GET['my_message'])) {
case 1:
$message = 'Invalid post data';
break;
default:
$message = 'Unexpected error';
}
echo '<div id="notice" class="error"><p>' . $message . '</p></div>';
}
Related
I have custom post type Events..
URL is www.mysite.com/events/eventname, these events will have people joining them which I plan to solve by building custom DB table and placing event id and user id inside.. Now what I want and don't know even what to type in google is how to make and rule so when somebody what to see whoi s going to event that he can type URL www.mysite.com/events/eventname/users and the specific template will be pulled which will query that custom DB and show what users are attending..
I will figure out query code just need help how to make that custom url to load that query ? Is this possible within Wordpress ?
Maybe consider using php's $_GET or $_POST to do this instead. It will work fine in WordPress too.
Warning untested code ahead.
$_GET request
Sending a $_GET request using a url looks something like this:
www.mysite.com/events/?eventname=myeventname&users=true
You can then use an if statement in your php on the events page to load data depending on whether these variables are included in the url. Something like:
if(isset($_GET['eventname']) && isset($_GET['users']) && $_GET['eventname'] == 'myeventname' && $_GET['users'] == true){
//Spit out all the users here
}else{
//Continue as if nothing happened
}
$_POST Request
Post request is another way of sending data from one page to another (or sending data to itself) and if you go with this method you won't be making any changes to the url. The most common way to send a $_POST request is with a form. In this case it might be a form with some invisible inputs that will hold our data. Something like:
<form action="www.mysite.com/events/" method="post"> <!-- Submitting form to self -->
<input name="eventname" type="hidden" value="myeventname"></input>
<input name="users "type="hidden" value="true"></input>
<input type="submit" value="See who's going"></input>
</form>
And then retrieve the data when the form is submitted:
if(isset($_POST['eventname']) && isset($_POST['users']) && $_POST['eventname'] == 'myeventname' && $_POST['users'] == true){
//Spit out all the users here
}else{
//Continue as if nothing happened
}
Finally, just on the custom DB table, it might be a good idea to use WordPress users rather than building out another table. You will then be able to make use of all of those handy functions that WordPress comes with.
Best of luck!
I have written an action that attaches to woocommerce_order_status_completed, and it works fine, adding a bit of meta data to the order. But the email that goes out after order completed seems to go BEFORE this runs, and therefore does not send the meta data in question (it will send it if I rerun the completed order again, but that is because this data is now already in the DB). So what I am looking for is either:
a hook that runs JUST before the completed email sends, OR
a way to have the completed email send AFTER woocommerce_order_status_completed hook
Any ideas or pointers? I looked through the Woocommerce API reference but can't find anything that seems to suit.
UPDATE: found an earlier hook and tried hooking it into
add_action( 'woocommerce_order_status_completed_notification','mysite_woocommerce_order_status_completed',5,1 );
which should run sooner, but STILL the email goes out first (before the meta data is in the DB and can be read. If I "recomplete" the order (putting it back into processing status and then completed again), it will send the meta data (again, this is because it is now in the db)
After much hair pulling, I have come up with a workaround which seems kind of ugly, but hopefully it will help someone else out.
I verified that my hook WAS correctly running before the main email one. (using add_action( 'woocommerce_order_status_completed_notification','mysite_woocommerce_order_status_completed',5,1 );
)
I verified that my meta data WAS correctly inserted into the db BEFORE the email went out
Unfortunately, it still refused to grab my meta data on first send. So I did the following:
I copied the woocommerce/templates/emails/email-order-items.php template into my theme and made the following change:
// Variation
if ( ! empty( $item_meta->meta ) ) {
echo '<br/><small>' . nl2br( $item_meta->display( true, true, '_', "\n" ) ) . '</small>';
// following 5 lines are MY extra code (checking for my meta field 'signup_code')
if (!array_key_exists('signup_code',$item_meta->meta)) {
$suc = wc_get_order_item_meta( $item_id, 'signup_code' );
if ($suc) {
echo '<br/><small>signup_code: ' . $suc . '</small>';
}}
}
It will check for a dupe in the meta array and not output if it already exists. It needs to do this to prevent it showing twice (which it would otherwise do on second send). I can't believe this is all necessary, but I can't find any other pointers anywhere that can address this.
UPDATE:
This was apparently caused by a woo internal caching problem. I had a lengthy discussion with one of the woo devs here:
https://wordpress.org/support/topic/hook-an-action-before-transactional-woocommerce-emails-are-triggeredsent-out/page/2?replies=40#post-8379725
And the upshot is, it will be fixed in a future version, but you can see the changes here:
https://github.com/woothemes/woocommerce/commit/3db3f3796eb28fb79983f605a5a70d61ca114c6d
I want a woocommerce order to be automatically marked as completed upon visiting a specific page in WordPress
This specific page will have the order_id in GET variable. Now what i need to know is how to find an order by order_id and mark the status as completed on a specific WordPress page.
You can do it with following piece of code:
if(is_page('page_title')){
$order = new WC_Order($_GET['your_order_id']);
//wc-completed, wc-processing
$update_status = array('ID'=>$_GET['your_order_id'],'post_status'=>'wc-completed');
wp_update_post( $update_status );
}
First check whether It is on your specific page or not.
Second thing is to get your order
Third step is to update status of order.
Let me know if you have any doubt.
EDITED
You can refer codex for more details on is_page.
If you have page_id then you need to put condition like if(is_page(42)) where 42 is ID of your page.
So, Rohil_PHPBeginner's answer works like a charme. If you urgently search for a "just copy and paste" solution and have no time to figure things out, go ahead and use this ready to use snippet for your functions.php:
Don't forget to replace the number 42 with the page id of your desired page that will handle as an order-completion-url. Afterwards you can call the order completion by url by: http://your-url.com/yourdeletionpage/?your_order_id=ORDER_ID_TO_BE_COMPLETED
function my_abhaken() {
if(is_page(42)){
$order = new WC_Order($_GET['your_order_id']);
//wc-completed, wc-processing
$update_status = array('ID'=>$_GET['your_order_id'],'post_status'=>'wc-completed');
wp_update_post( $update_status );
}
}
add_action('wp_head', 'my_abhaken');
I'm trying to process the post content before saving it to database and inform user if there was any error. I'm using wp_insert_post_data filter, sessions and admin_notices, since it seems to be the only simple solution to throw the error message to edit screen in admin panel after publishing. However, I ran into strange problem - the code seems to run twice, so when I have:
function notices(){
if(!empty($_SESSION['notices'])) print $_SESSION['notices'];
unset ($_SESSION['notices']);}
add_action( 'admin_notices', 'notices' );
add_filter('wp_insert_post_data', 'processdata', '99', 2);
function processdata($data) {
$processed_content = filterthis($data['post_content']);
if($processed_content!='error') {
$data['post_content'] = $processed_content;
$_SESSION['notices'] .= '<div class="updated"><p>Everything is ok</p></div>';
return $data;
} else {
$_SESSION['notices'] .= '<div class="error"><p>Something went wrong</p></div>';
return $data;}
I receive two messages. I've made some tests and it seems that first is for content that is to be saved in database and second for the "old" content (already saved). For example: if previous content was "wrong" and the one to be saved is "ok", the first message is "ok" and second "error" and so on. However, all messages are generated on the same time (so this is not some kind of caching problem, I suppose).
What is even more strange for me, is that if I use this simple code:
add_filter ('wp_insert_post_data', 'filterthis', '99', 2);
function filterthis($data){
$date = date('H:i:s');
$_SESSION['my_admin_notices'] .= '<div class="updated"><p>This is a message from '.$date.'</p></div>';
$data['post_content'] .= $date;
return $data;
}
I get also two messages, but only one piece of timing data appended to post content - identical to the one delivered by the first message. So it seems like the code runs twice, but only for the first time it saves the content to database... I'm totally confused after seven hours of googling and reading Codex. Maybe the solution is trivial, but I'm not really a PHP programmer, just learning it for a while, so I would be very glad if someone here could help me.
I have also face the same problem when I made custom field validation before publish the post. Also when update the published post this filter gives strange behavior. so I have decide to go for client side validation.
Which is very simple DOM operation.
Please refer following link if you want to use client side validation.
http://greatindiaclub.oliwy.net/?p=1101
In drupal how to display the user's last login date and Time.I tried out the code
user->login
It displays the current login time, But I want users previous login time and date.
Take a look at the User Stats module, it might be something that could work for you. From the module's project page:
Provides commonly requested user statistics for themers, IP address tracking and Views
integration. Statistics are:
Days registered
Join date
Days since last login
Days since last post
Post count
Login count
User online/offline
IP address
These data are saved with module Login History
Drupal doesn't offer this natively. If you need to use it, you would probably want to add it to for example serialized $user->data array when user logs in (Using hook_user() for $op = "login") and save updated user object afterwards, and then you will be able to fetch it on the next login.
The solution I used: keep track of the last two log-in timestamps (the current and the previous one).
/**
* Implementation of hook_user()
*/
function your_module_user($op, &$edit, &$account, $category = NULL) {
switch($op) {
// Successful login
case 'load':
// If it's less than two it means we don't have a previous login timtestamp yet.
$account->custom_last_login = sizeof($account->custom_login_history) < 2
? NULL
: array_pop($account->custom_login_history);
break;
case 'login':
// If it's the first time, we don't have a history
$login_history = is_array($account->custom_login_history)
? $account->custom_login_history
: array();
// Get rid of the old value.
if (sizeof($login_history) == 2) {
array_pop($login_history);
}
// Add to the history the current login timestamp.
array_unshift($login_history, $account->login);
user_save($account, array('custom_login_history' => $login_history));
break;
}
}
Then in your template you just use $user->custom_last_login. If it's empty it means we don't have the previous timestamp yet, will be available after the next login.