This question is in regards to a plug-in I'm developing.
I'm trying to fire a function each time a custom post type called "Product" is added or edited. In particular, I need a hook that fires before the meta boxes load on the add/edit page, but that only fires on that "Product" custom post type's edit page.
The function that will fire makes an API request, and caches the response in a transient.
The reason for the action hook is because in my current code, when the transient has expired, the add/edit page is broken during the first page load. However if you refresh the page after that, it shows up as intended. I'm fairly certain this is happening because the current conditional statement that checks the transient is located inside of the function that generates the meta box. So my theory is if I can set up an action hook to check the transient before the meta box is generated, it might solve the problem.
However I've got a second theory that the problem is being caused because of the time it takes to make the API request and return the response is longer than the time it takes for the page to load. So if there is an action hook that will delay page loading until the function finishes executing it would be an ideal solution, but I don't believe such an action hook exists. I'm not even certain if such a delay is possible.
I'd really appreciate any help or alternative suggestions you guys might have. Thanks for your time guys.
Code Example:
add_action( 'edit_product', 'llc_hook_campaign_find_active' );
function llc_hook_campaign_find_active() {
if (!$t_campaign_find_active){
limelight_cart_campaign_find_active();
return false;
}
}
Since you are using an action hook, it is not waiting for your API response.
Try using a filter hook instead.
Try using wp_insert_post_data
function filter_handler( $data , $postarr ) {
//make your API call, get the response and store it in post meta or data wherever you want
$response = 'your API response';
//e.g. update_post_meta($postarr['ID'], 'meta_key', $response); OR
//$data['post_content'] = $response;
return $data;
}
add_filter( 'wp_insert_post_data', 'filter_handler', '99', 2 );
In your case, following should work -
add_filter( 'wp_insert_post_data', 'llc_hook_campaign_find_active', '99', 2 );
function llc_hook_campaign_find_active( $data , $postarr ) {
if (!$t_campaign_find_active){
limelight_cart_campaign_find_active();
return $data;
}
}
I was able to make the API request before the meta boxes loaded on the Admin Add/Edit screen by using the action filter edit_form_top. That particular action hook is fired as soon as the Add/Edit page for any post/page/custom post type is loaded. In order to narrow it down so that the function only fires on the Add/Edit screen for my "product" custom post type, I used get_current_screen() along with an if statement.
add_action('edit_form_top', 'llc_hook_campaign_find_active');
function llc_hook_campaign_find_active() {
//Fetch current screen information
$screen = get_current_screen();
//Check if post type is "product"
if($screen->post_type == "product") {
//API Request that checks for an existing transient
$t_campaign_find_active = get_transient('campaign_find_active');
if (!$t_campaign_find_active){
limelight_cart_campaign_find_active();
return false;
}
}
}
Works like a charm.
Related
I have a custom post type that must have its feature image. Its feature image also appears in the Media Library where user can delete the image/attachment file permanently. But I want to prevent user from deleting the feature image of my custom post type. So I use the following hook to intercept the ajax request, validate if user is deleting the image of my custom post type and stop the process by using wp_die().
add_filter('pre_delete_attachment', 'check_my_custom_post_type', 0, 2);
function check_my_custom_post_type($delete, $post) {
if (Yes it is image of my custom type) {
wp_die('My message', 'My title', ['response' => 400]);
}
}
It works fine on the server side. The image of my custom post type cannot be deleted. But Media Library, on the client side, still removes the image from its views even the image on the server side has not been deleted and an exception 400 has been thrown.
How to prevent Media Library from removing images from its views on the client side if image has not been deleted on the server side?
In documentation second parameter is bool|null. Try to return false instead of wp_die
You can remove the delete link totally using the wp_prepare_attachment_for_js hook.
function remove_media_delete_link_in_grid_view( $response ) {
$response['nonces']['delete'] = false;
return $response;
}
add_filter( 'wp_prepare_attachment_for_js', 'remove_media_delete_link_in_grid_view' );
This works also with the "Bulk Delete" action. If the attachment has this nonce value false, it will not be deleted with the other bulk selected attachments 👍🏻.
But this works only with the Grid View that uses the wp_prepare_attachment_for_js hook. The List view doesn't use any Javascript. So, to prevent deleting media files in the List view, you have to:
Remove the individual delete link of each media item in the list using the media_row_actions hook.
And remove the delete option from the bulk actions menu using the bulk_actions-{$screen} hook.
function remove_media_delete_link_in_list_view( $actions ) {
unset( $actions['delete'] );
return $actions;
}
add_filter( 'media_row_actions', 'remove_media_delete_link_in_list_view' );
add_filter( 'bulk_actions-upload', 'remove_media_delete_link_in_list_view' );
I need to store current URL in transient, so I can access it for later use (when I need to link back to that page from search page).
Code:
if(!is_page_template('search.php')) {
set_transient( 'last_url', $current_url, 60*60 );
}
So this code should save current url of current page, until we are on search page.
However, once I click on the search page, 'last_url' will become domain.tld/search. I have no idea why is that happening when I explicitly have the rule if(!is_page_template('search.php'))
However, my temporary solution is to check if there is also word search in URL, and if there isn't then create transient, like:
if(!is_page_template('search.php')) {
if(stripos($current_url, 'search') === false) {
set_transient( 'shop_last_url', $current_url, 60*60 );
}
}
While this solution works, its the bad one since search page have different slug - for example if there are several languages...
I have also tried to use cookies and sessions without any luck.
If your theme doesn't uses default WordPress queries ($wp_query) then the functions such as is_page_template,get_page_template_slug would not work properly.
You can see it in corresponding Core code here.
So, for your current case you can use global template variable instead.
if (basename($GLOBALS["template"])=='search.php'){
set_transient( 'last_url', $current_url, 60*60 );
}
or
if (basename(get_page_template())=='search.php'){
set_transient( 'last_url', $current_url, 60*60 );
}
I read the action hook is use for call the functions and filter hook is use for filtering the content before save or display on website. Can you tell me the some working phases of project example where we use action and filter hook. So it will better to understand more.
Action: Doing new things. Ex. adding CSS and JS.
Filter: Doing something which is already in WordPress. Adding or removing some things. Assume adding some text after post content. It means doing something with WordPress.
Example code:
add_action('admin_menu','my_func',8,2);
function my_func($one, $two){
//Something here …
}
In short:
Filter Hook: used to modify (filter) a value
Action Hook: do something, right at this moment
Filter Hook Example:
$name = "Smith";
$gender = "female";
echo apply_filter('form_of_address', $name, $gender );
// somewhere else
add_filter( 'form_of_address', function( $name, $gender ) {
if ( "female" == $gender ) {
$name = "Mrs. " . $name;
} else if ( "male" == $gender ) {
$name = "Mr. " . $name;
}
return $name
}, 10, 2 );
You want to know what the difference is between an action and filter event (hook).
Let's start with understanding what a hook or event is in WordPress.
What is a Hook (Event)?
Through the Plugin API, WordPress provides us with the means to run our code when events happen.
Programs run in a specific order from top to bottom. Line 1 runs and then line 2 and so on. As WordPress loads in files, each of those run and so on.
As the web page request is processed, WordPress Core is running the lines of code and doing stuff. But what if you need to run your code at a certain point in the request cycle? For example, let's say you want to change the post's byline from the default of the theme. Using a filter event (hook), you gain:
access to the actual value or HTML before it's sent out to the browser
the ability to change that value or HTML
the ability to run other code at a specific point in the web page request cycle.
Why are there hooks (events)?
WordPress runs out of the box without our custom themes or plugins. But the Plugin API gives you and I as well as all developers the means to customize, change the default behavior, and extend WordPress.
It lets us run our code when we need to, i.e. at specific points in the web page request cycle.
Pre-registration
For our code to run, we need to pre-register our callback, which is the function or method that we want to run at that specific point. You use add_action() to pre-register an action event callback. You use add_filter() to pre-register a filter event callback.
When you pre-register your callback, it's being added into a registration lookup table, which is an array.
Then when the event fires, your callback and all the others are called in order. You set the order using the priority parameter. The argument(s) are passed to your callback. Then your method or function runs.
Here's a video that explains the big picture of this process and the why of it.
Action vs. Filter
A filter event (hook) allows you to filter the return value. It gives you (and all the other registered callbacks) the ability to filter, which means change the value.
Think about that. Let's say you want to change the "read more" link. Maybe you want it to say "Continue reading" and maybe you want it to be a button instead of a just a hyperlink. The filter allows you:
to receive the current value
change it to what you want
send it back by returning it
That new value is then passed along to all the other registered callbacks and then eventually returned to the line of code that fired the event.
Action Event
The action event (hook) is the same as a filter except that the value is not returned.
The event is fired, such as init or plugins_loaded. The pre-registered callbacks are called in order and any arguments are passed to each one. Your function or method is called. You can run your code.
It gives you the means to do something at a specific point in the web page request cycle.
Wrap it Up
Think about the power of the event management system. If you need to run something say right after all of the plugins have loaded, you pre-register to the plugins_loaded event name. When Core fires the event by doing do_action( 'plugins_loaded' );, your code will run too.
Filter and Action events (hooks) let you run your code at a specific point in the web page request cycle.
The Difference
Filter events (hooks) let you filter or change the value before it's processed. Action events do not let filter.
How do you fire an event?
For action events, you can use do_action( 'event_name' ) or do_action_ref_array( 'event_name', $args ).
For filter events, you can use apply_filters( 'event_name', $value_to_filter ) or apply_filters( 'event_name', $args );
Examples
Let's say you want to add a styling class attribute to the post. You can pre-register to the post_class event name. You'll receive an array of classes.
add_filter( 'post_class', 'add_my_class_attribute_to_post_class' );
/**
* Add styling class to the post class.
*
* #since 1.0.0
*
* #param array $classes
*
* #return array
*/
function add_my_class_attribute_to_post_class( array $classes ) {
$classes[] = 'some-class-attribute';
return $classes;
}
What if you wanted to add a styling class to the front page's body.
add_filter( 'body_class', 'add_front_page_to_body_class' );
/**
* Add the class attribute "front-page" to the body classes.
*
* #since 1.0.0
*
* #param array $classes
*
* #return array
*/
function add_front_page_to_body_class( array $classes ) {
$classes[] = 'front-page';
return $classes;
}
Notice how you're changing the value and then returning it back.
I've registered a custom Admin page in my plugin through add_submenu_page. In the callback function (the one that generates the contents of the admin page), I have the following code:
wp_redirect('http://google.com');
exit;
However, when I visit the admin page I get an error:
Warning: Cannot modify header information - headers already sent by (output started at ..\wp-admin\includes\template.php:1637) in ..\wp-includes\pluggable.php on line 878
The callback from add_submenu_page happens too late (after the admin sidebar and header are rendered), this is why the location header can not be sent anymore.
To accomplish this, we need to hook a function a bit earlier in the WordPress admin area, before the headers are sent (e.g. admin_init).
A good way:
function myplugin_preprocess_pages($value){
global $pagenow;
$page = (isset($_REQUEST['page']) ? $_REQUEST['page'] : false);
if($pagenow=='admin.php' && $page=='myplugin-custom-page-slug'){
wp_redirect('http://google.com');
exit;
}
}
add_action('admin_init', 'myplugin_preprocess_pages');
The above code will redirect you to Google whenever you try to view wp-admin/admin.php?page=myplugin-custom-page-slug.
In my case, I've attached the custom page via add_submenu_page to the default (admin.php) parent in the Admin area and I've set the custom page's slug to myplugin-custom-page-slug. Feel free to replace the values in the code above or even add a PHP switch if you have a lot of custom admin pages.
This way we have hooked early enough to do a redirection whenever our custom admin page is viewed.
Update: (A different approach)
Thanks to this post, I've learned that WordPress creates a unique action that you can hook to for each custom admin page (load-{parent_page_slug}_page_{plugin_subpage_slug}). For example, if you've added a custom admin page with parent admin.php and slug myplugin-custom-page, you can hook to its "load" action in the following manner:
add_action( 'load-admin_page_myplugin-custom-page', 'myplugin_custom_page_redirect' );
function myplugin_custom_page_redirect() {
if ( 'myplugin-custom-page' == filter_input( INPUT_GET, 'page' ) ) {
wp_redirect( 'http://google.com' );
exit;
}
}
Note that the action name has some things to consider. It's a mixture of underscores and dashes and make sure you only include the parent page's name without the extension (so "admin" instead of "admin.php")
I am using the save_post action to inspect a metadata field in a custom post and take some action on that value. This is the essential guts of how I am doing it:
add_action('save_post', 'my_save_post');
function my_save_post($post_id)
{
// Check if not autosaving, processing correct post type etc.
// ...
// Get the custom field value.
$my_field_value = get_post_meta($post_id, 'my_field', true);
// Do some action
// ...
}
This works fine when updating the post through the admin page. However, when first creating the post, the my_field_value is always empty. The field does get saved correctly, but this action trigger does not seem to be able to see it, nor any other custom field values.
I would like the action to be performed on all posts of this type created, and I will be importing many through the CSV Imported plugin. Even then, the custom fields do get imported correctly, and the action trigger does get fired for each row imported, but the save_post action still cannot see the custom field value.
So far as I can see from documentation, the post has already been created by the time this action fires, so I should always be able to see that custom metafield.
The answer, it seems, is in the order in which things happen. When creating a post from a form, the custom fields are all collected by the appropriate actions and added to the post before my save_post action fires. This means my trigger is able to see those custom field values.
When importing from CSV, the basic post is created first, and then the custom metafields are added. The save_post trigger fires on the first creation, before the metafields are added, and so the custom field data is not visible to the save_post action.
My solution was to catch the updates of the metadata using the updated_post_meta and added_post_meta actions as well as the save_post action:
add_action('updated_post_meta', 'my_updated_post_meta', 10, 4);
add_action('added_post_meta', 'my_updated_post_meta', 10, 4);
function my_updated_post_meta($meta_id, $post_id, $meta_key, $meta_value)
{
// Make sure we are handling just the meta field we are interested in.
if ($meta_key != 'my_custom_field') return;
if (wp_is_post_revision($post_id)) return;
if (get_post_type($post_id) != 'my_post_type') return;
if (trim($meta_value) == '') return;
// Do my custom task (linking this post to a parent post in a different
// post type). This is the same task performed by the save_post action.
my_link_product_track($post_id, trim($meta_value));
}
That is essentially what I do, and it seems to work well. I do encapsulate all the above into a custom class in the theme, and don't recommend using global scope variables as shown here, but this is just to show the method.
You should look at using $post->ID instead of $post_id -
$my_field_value = get_post_meta($post->ID, 'my_field', true);
get_post_meta in the Codex
EDIT:
Could you do something like this?
if($post->ID == ''){
$pid = $post_id;
} else {
$pid = $post->ID;
}
//$pid = $post->ID or $post_id, whichever contains a value
$my_field_value = get_post_meta($pid, 'my_field', true);
something that looks for a value in $post->ID and $post_id, and uses whichever one isn't blank?