I am developing a plugin for wordpress and I want to add the button to the header of gutenberg editor just like the one added by Elementor plugin "Edit with Elementor"
Can anyone guide me what should i do to achieve this... Thanks in Advance
I've checked Guternberg source code on header toolbar and unfortunately right now there's only not a beautiful solution possible. You can have 'custom' buttons but they are only done through calling registerPlugin with PluginSidebar component which creates sidebar and button appears on right side for pinned\starred sidebars (like Yoast SEO does).
Elementor does it this way: it subscribes to wp.data store changes and whenever something happens it injects it's button if it's not yet injected. Just because React rerenders DOM it may eventually wipe button out, so subscribing to changes is important so if it wipes out custom button js code just reinjects it.
Below is simplified ES5 code to inject a custom link\button or really any custom HTML into it (just as Elementor does).
// custom-link-in-toolbar.js
// wrapped into IIFE - to leave global space clean.
( function( window, wp ){
// just to keep it cleaner - we refer to our link by id for speed of lookup on DOM.
var link_id = 'Your_Super_Custom_Link';
// prepare our custom link's html.
var link_html = '<a id="' + link_id + '" class="components-button" href="#" >Custom Link</a>';
// check if gutenberg's editor root element is present.
var editorEl = document.getElementById( 'editor' );
if( !editorEl ){ // do nothing if there's no gutenberg root element on page.
return;
}
var unsubscribe = wp.data.subscribe( function () {
setTimeout( function () {
if ( !document.getElementById( link_id ) ) {
var toolbalEl = editorEl.querySelector( '.edit-post-header__toolbar' );
if( toolbalEl instanceof HTMLElement ){
toolbalEl.insertAdjacentHTML( 'beforeend', link_html );
}
}
}, 1 )
} );
// unsubscribe is a function - it's not used right now
// but in case you'll need to stop this link from being reappeared at any point you can just call unsubscribe();
} )( window, wp )
So create a js file in your theme or plugin and then link it this way:
add_action( 'enqueue_block_editor_assets', 'custom_link_injection_to_gutenberg_toolbar' );
function custom_link_injection_to_gutenberg_toolbar(){
// Here you can also check several conditions,
// for example if you want to add this link only on CPT you can
$screen= get_current_screen();
// and then
if ( 'cpt-name' === $screen->post_type ){
wp_enqueue_script( 'custom-link-in-toolbar', get_template_directory_uri() . '/assets/js/custom-link-in-toolbar.js', array(), '1.0', true );
}
}
Again this solution isn't perfect because we just inject our custom HTML into DOM which is managed by Gutenberg(React) but at this point it seems like the only way to achieve it, may be in future we'll have something like filters or hooks which will allow us to inject our custom components being rendered in toolbar but not yet.
Still Elementor does it this way. you can check it's code at
https://github.com/elementor/elementor/blob/master/assets/dev/js/admin/gutenberg.js
Now there is a slot for the Main dashboard button you can use that one
import { registerPlugin } from '#wordpress/plugins';
import {
__experimentalFullscreenModeClose as FullscreenModeClose,
__experimentalMainDashboardButton as MainDashboardButton,
} from '#wordpress/edit-post';
import { close,image } from '#wordpress/icons';
const MainDashboardButtonIconTest = () => (
<MainDashboardButton>
<FullscreenModeClose icon={ close } href="http://wordpress.org" />
<Button variant="primary" icon={image}>
My Button
</Button>
</MainDashboardButton>
);
registerPlugin( 'main-dashboard-button-icon-test', {
render: MainDashboardButtonIconTest,
} );
ref: https://developer.wordpress.org/block-editor/reference-guides/slotfills/main-dashboard-button/
I have a contact form in my Wordpress website. In that I have three fields Name, Email and Mobile, and a button called Submit. When the user fills all the fields and click on Submit button an email should send which can possible with Contact From 7 plugin.
But challenge here is I need to make the user download a PDF also, when he clicks on Submit button upon filling all the fields.'
How can I achieve this in Wordpress?
You can use the wpcf7_mail_sent hook provided by Contact form 7 like this:
add_action('wpcf7_mail_sent', function ($cf) {
// Run code after the email has been sent
});
This link: https://contactform7.com/2017/06/07/on-sent-ok-is-deprecated/ also describes another way:
add_action( 'wp_footer', 'mycustom_wp_footer' );
function mycustom_wp_footer()
{ ?>
<script type="text/javascript">
document.addEventListener( 'wpcf7mailsent', function( event )
{
//Write a javascript code to download the file.
} , false );
</script>
<?php
}
Since the on_sent_ok has been deprecated, here is an example you could use as inspiration.
I had the similar need to add a download CTA after the content of all case studies of a website, but "in exchange" of user's data for:
display a CF7 form on your page, I had the same one on all case studies post type single which I hooked after the content
find a way to get the wanted PDF url for people to download, as for me all case studies have a different PDF, I simply added an ACF field, filtered on PDF only, which returns the file url
based on CF7 Dom events, choose the action you prefer to make the dowload happens, as I am not sending any confirmation email, I prefer working on the wpcf7submit event. Note that wpcf7submit event is fired only if the form has been validated
So the code looks like this:
<?php
// For simplicity, using an anonymous functions
add_action( 'wp_print_footer_scripts', function () {
// Check the wanted singular post type loading
if ( is_admin() || ! is_singular( 'case-study' ) ) {
return;
}
// Check if the ACF PDF field is not empty for use
$pdf_link = get_field( 'pdf' );
if ( empty( $pdf_link ) ) {
return;
}
// Hook on the "wpcf7submit" CF7 Dom event to force the download
printf( "<script>document.addEventListener( 'wpcf7submit', function( event ) { window.open('%s'); }, false );</script>", $pdf_link );
} );
I'm using Contact Form 7 on a site and need to alter the checkbox submitted data. The checkbox has a label called 'Tick here if you dont want to receive further marketing', when this is checked the value sent in the admins notification email displays the checkbox label.
So it looks like:
Tick here if you dont want to receive further marketing: Tick here if you dont want to receive further marketing
I want to alter it so when its checked the value posted is No.
I believe I can use the following action hook to achieve this but I dont know how to check if the checkbox has been ticked within this function and modify its value.
Any help much appreciated.
// define the wpcf7_posted_data callback
function action_wpcf7_posted_data( $array ) {
// make action magic happen here...
};
// add the action
add_action( 'wpcf7_posted_data', 'action_wpcf7_posted_data', 10, 1 );
I believe you can just use:
// define the wpcf7_posted_data callback
function action_wpcf7_posted_data( $array ) {
//'checkbox-name' is the name that you gave the field in the CF7 admin.
$value = $array['checkbox-name'];
if( !empty( $value ) ){
$array['checkbox-name'] = "New Value";
}
return $array;
};
add_filter( 'wpcf7_posted_data', 'action_wpcf7_posted_data', 10, 1 );
I know we can add meta for woocommerce cart item using woocommerce_add_cart_item_data hook.
Is there any way to update existing cart item meta.?
Yes, but it seems, only via accessing the cart directly:
global $woocommerce;
$woocommerce->cart->cart_contents[$cart_item_key]['whatever_meta'] = 'testing';
$woocommerce->cart->set_session(); // when in ajax calls, saves it.
I would recommend to remove and re-add the product, as other meta data could be lost (as in Phong Tran's answer).
Based on #DanielSalcedos answer, only keeping the absolute minimum required to answer the question.
I know It's been a while, but as it's still not answered, and it took me lots of sweat and a pint of blood, I share my solution here.
First
I'll assume you know how to add metadata to a cart and into an order. If not, you can have a look at pwnewbie's solution but I recommend you the full article at Wisdm labs
Wisdm's method takes many steps. First you create a PHP's session variable trough Ajax. Second, you intercept woocommerce_add_cart_item_data filter to add your Session variable to woocommerce session.
The thing about woocommerce_add_cart_item_data filter is that it gets executed in the middle of the add to cart process, so, if you add your variable to the main $wooocmmerce object, at some point, it get's stored as the add-to-cart event. (Sort of)
The idea
What if I want to edit that metadata and not any of the standard cart properties. The ideal would be to get a filter or an action that runs in the middle of saving something. The problem is that as long as we don't change anything else, there's no hook to run (I tried with woocommerce_update_cart_action_cart_updated hook that runs after coupons, quantities and removals from cart had happened, but it never fired as I never passed the validations)
My approach
In a shell, rebuild as little as possible, as much as needed. I added a synchronous ajax event to the cart form OnSubmit event. (I want my UI to be updated with my changes, so the reload must happen after my update)
AJAX:
var myFlag33322805 = true;
$('form').submit(function(e){
if(myFlag33322805){
myFlag33322805 = false;
e.preventDefault(); // Flag and prevent default to syncronize submits
var kart = []; // Will retrieve every cart item's meta
$('.cartRow').each(function(){
//This object will store your meta data and be pushed into kart array
var kitm = {
'p' : $(this).data('product_id'),
'm' : $(this).find('select[name=myMetaData]').val(),
'k' : $(this).data('key')
};
kart.push(kitm);
});
var data = {
'action': 'Ajax_Update_My_MetaData_33322805',
'k': kart
};
$.post(VKVAjax.ajaxurl, data, function (response) {
// Might do something with the response here
});
$('form').submit(); // This time, the form will submit, but AJAX wont run because of myFlag33322805 = false
}
});
The magic:
The php ajax response is a function that will receive my meta data to update (actually, all of it, updated or not), and will take the global $woocommerceobject to insert the meta data AND save it to the session:
PHP:
function fn_Update_My_MetaData_33322805(){
global $woocommerce;
$cart = $woocommerce->cart->cart_contents;
$updt = Array();
foreach ($_POST['k'] AS $item){
$product = new stdClass();
$updtCL = new stdClass();
$product->{'id'} = $item['p']; //This is product id
$product->{'mymeta'} = $item['m']; //This is metadata
$updtCL->{'krtkey'} = $item['k']; //This is product key in cart
$updtCL->{'meta'} = $product;
$updt[] = $updtCL;
}
// cycle the cart replace the meta of the correspondant key
foreach ($cart as $key => $item) {
foreach($updt as $updtitem){
if($key == $updtitem->krtkey){ // if this kart item corresponds with the received, the meta data is updated
// Update the content of the kart
$woocommerce->cart->cart_contents[$key]['vkv_AlternCart_value'] = $updtitem->meta;
}
}
}
// This is the magic: With this function, the modified object gets saved.
$woocommerce->cart->set_session();
wp_die('{"e":"ok", "Updt": "'.count($arrupdt).'"}');
}
Of course, this should be hooked as any other ajax event.
add_action('wp_ajax_nopriv_Ajax_Update_My_MetaData_33322805', 'fn_Ajax_Update_My_MetaData_33322805');
add_action('wp_ajax_Ajax_Update_My_MetaData_33322805', 'fn_Ajax_Update_My_MetaData_33322805');
Conclussion
You can update the meta data of a cart item with a synchronous Ajax call, overwritting the object directly to the $woocommerce global variable and saving it with the $woocommerce->cart->set_session(); method.
Footnotes
It's not the ideal method, and is quite risky to work directlly with the $woocommerce global. I would love to know of a better approach.
I'm newbie and my English is not good so the answer may be a little confusing.
Thank vlad274 for the advice.
My approach:
Same with Daniel Salcedo, I also try to change the metadata by editing woocommerce_sessions, but there is a problem. When adding an item to the cart, the woocommerce will create a unique cart_item_key for it by md5 (product_id + ... + metadata), then look for this cart_item_key already exists, if available, this item will be updated in quantity, otherwise will create a new item.
This means that if you change the meta value of the hat product from blue to red, then add the hat product with blue, instead of creating a new item blue hat, it will only increase the quantity of red hat, you need to change the cart_item_key if you want the cart to be updated correctly.
Changing cart_item_key is quite risky, instead we can simply remove and re-add the product. Like this
// get cart_item_key of item you want to change
$cart_item_key_old = $_POST['cart_item_key'];
// retrieve its information
$cart_item_old = WC()->cart->cart_contents[ $cart_item_key_old ];
$product_id_old = $cart_item_old['product_id'];
$quantity_old = $cart_item_old['quantity'];
$variation_id_old = $cart_item_old['variation_id'];
$variation_old = $cart_item_old['variation'];
// creating a cart_item_key with the same information except metadata
$cart_item_key_new = WC()->cart->generate_cart_id( $product_id_old, $variation_id_old, $variation_old, ['color'=>'red'] );
// check new cart_item_key already exists
$found = WC()->cart->find_product_in_cart( $cart_item_key_new );
// if true, update its quantity
if ($found != '') {
$new_quantity = $cart_item_old['quantity'] + WC()->cart->cart_contents[ $cart_item_key_new ]['quantity'];
WC()->cart->set_quantity( $cart_item_key_new, $new_quantity );
}
// else, re-add with new metadata
else {
WC()->cart->add_to_cart($product_id_old, $quantity_old, $variation_id_old, $variation_old, ['color'=>'red'] );
}
// finally delete the old item
WC()->cart->remove_cart_item($cart_item_key_old);
wp_die();
Note: If you want to submit cart form instead of page refreshes after running the above ajax, the quantity of item is likely to be overridden by the set_quantity method when woocommerce update_cart. In this case you just need to return new_quantity and change the input value by js before submitting the form.
Full code:
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
...
?>
<!-- place it anywhere within the foreach -->
<div class="box-type-field">
<select class="box-type" name="box-type" cart_item_key="<?php echo $cart_item_key ?>">
<option <?php echo $cart_item['box-type']=='boxes'?"selected":""; ?> value="boxes"><?php _e( 'Boxes', 'woocommerce' ); ?></option>
<option <?php echo $cart_item['box-type']=='bags'?"selected":""; ?> value="bags"><?php _e( 'Bags', 'woocommerce' ); ?></option>
</select>
</div>
<?php
...
}
AJAX:
$('.box-type-field .box-type').live('change', function () {
var cartItemKey = $(this).attr("cart_item_key");
var boxType = $(this).val();
$.ajax({
type : "post",
url : '<?php echo admin_url('admin-ajax.php');?>',
datatype: 'json',
data : {
action : "update_cart_boxtype",
cart_item_key : cartItemKey,
box_type : boxType,
},
success: function(cartItem) {
cartItemKey = cartItem[0];
cartItemQty = cartItem[1];
if (cartItem) $('input[name="cart['+cartItemKey+'][qty]"]').val(cartItemQty); // update quantity
$('.woocommerce-cart-form button[type="submit"]').click(); // submit form
}
})
})
PHP:
add_action( 'wp_ajax_update_cart_boxtype', 'update_cart_boxtype_init' );
add_action( 'wp_ajax_nopriv_update_cart_boxtype', 'update_cart_boxtype_init' );
function update_cart_boxtype_init() {
if ( ! WC()->cart->is_empty() ) {
$cart_item_key = (isset($_POST['cart_item_key']))?$_POST['cart_item_key'] : '';
$cart_item = WC()->cart->cart_contents[ $cart_item_key ];
$box_type = (isset($_POST['box_type']))?$_POST['box_type'] : '';
$cart_updated = false;
$cart_item_key_new = WC()->cart->generate_cart_id( $cart_item['product_id'], $cart_item['variation_id'], $cart_item['variation'], ['box-type'=>$box_type] );
$found = WC()->cart->find_product_in_cart( $cart_item_key_new );
if ($found != '') {
$new_qty = $cart_item['quantity'] + WC()->cart->cart_contents[ $cart_item_key_new ]['quantity'];
WC()->cart->remove_cart_item($cart_item_key);
wp_send_json_success([$cart_item_key_new, $new_qty]);
} else {
WC()->cart->add_to_cart($cart_item['product_id'], $cart_item['quantity'], $cart_item['variation_id'], $cart_item['variation'], ['box-type' => $box_type]);
$cart_updated = true;
WC()->cart->remove_cart_item($cart_item_key);
wp_send_json_success(false);
}
}
wp_die();
}
Step 1: Add Data in a Custom Session, on ‘Add to Cart’ Button Click
For those of you who have worked with WooCommerce might know that on the click of the ‘Add to Cart’ button the product page gets refreshed and the user data is lost. Hence, we should add the custom data from our product page to a custom session created using Ajax. This code is invoked before the WooCommerce session is created.
<?php
add_action('wp_ajax_wdm_add_user_custom_data_options', 'wdm_add_user_custom_data_options_callback');
add_action('wp_ajax_nopriv_wdm_add_user_custom_data_options', 'wdm_add_user_custom_data_options_callback');
function wdm_add_user_custom_data_options_callback()
{
//Custom data - Sent Via AJAX post method
$product_id = $_POST['id']; //This is product ID
$user_custom_data_values = $_POST['user_data']; //This is User custom value sent via AJAX
session_start();
$_SESSION['wdm_user_custom_data'] = $user_custom_data_values;
die();
}
Step 2: Add Custom Data in WooCommerce Session
At this step, the WooCommerce session has been created and is now available for us to add our custom data. We use the following code to add the custom data from the session we have created into the WooCommerce session. At this step, our session is also unset since the data in it has been captured and it is not needed anymore.
add_filter('woocommerce_add_cart_item_data','wdm_add_item_data',1,2);
if(!function_exists('wdm_add_item_data'))
{
function wdm_add_item_data($cart_item_data,$product_id)
{
/*Here, We are adding item in WooCommerce session with, wdm_user_custom_data_value name*/
global $woocommerce;
session_start();
if (isset($_SESSION['wdm_user_custom_data'])) {
$option = $_SESSION['wdm_user_custom_data'];
$new_value = array('wdm_user_custom_data_value' => $option);
}
if(empty($option))
return $cart_item_data;
else
{
if(empty($cart_item_data))
return $new_value;
else
return array_merge($cart_item_data,$new_value);
}
unset($_SESSION['wdm_user_custom_data']);
//Unset our custom session variable, as it is no longer needed.
}
}
Step 3: Extract Custom Data from WooCommerce Session and Insert it into Cart Object
At this stage, we have default product details along with the custom data in the WooCommerce session. The default data gets added to the cart object owing to the functionality provided by the plugin. However, we need to explicitly extract the custom data from the WooCommerce session and insert it into the cart object. This can be implemented with the following code.
add_filter('woocommerce_get_cart_item_from_session', 'wdm_get_cart_items_from_session', 1, 3 );
if(!function_exists('wdm_get_cart_items_from_session'))
{
function wdm_get_cart_items_from_session($item,$values,$key)
{
if (array_key_exists( 'wdm_user_custom_data_value', $values ) )
{
$item['wdm_user_custom_data_value'] = $values['wdm_user_custom_data_value'];
}
return $item;
}
}
Step 4: Display User Custom Data on Cart and Checkout page
Now that we have our custom data in the cart object all we need to do now is to display this data in the Cart and the Checkout page. This is how your cart page should look after the custom data has been added from the WooCommerce session to your Cart.
My-Cart-Page
I have a "Products" custom post type. Normally, this custom post type have an "Add New" button. I want to add another button call "Update from Provider".
Currently, I have modify the Wordpress code (in "wordpress\wp-admin\includes\class-wp-list-table.php") to add that button. In this case, when I update Wordpress, my modified code will be deleted. Therefore, I need to move that button to my plug-in code.
In this case, please help me how to move that button to my plug-in code.
Well, if you opened the core file you saw that there's no action in it where we can hook.
Only a couple of filters. We can use the following:
add_filter( 'views_edit-movies', 'so_13813805_add_button_to_views' );
function so_13813805_add_button_to_views( $views )
{
$views['my-button'] = '<button id="update-from-provider" type="button" title="Update from Provider" style="margin:5px">Update from Provider</button>';
return $views;
}
It produces this:
To put it in an approximate position from where you'd like, use the following:
add_action( 'admin_head-edit.php', 'so_13813805_move_custom_button' );
function so_13813805_move_custom_button( )
{
global $current_screen;
// Not our post type, exit earlier
if( 'movies' != $current_screen->post_type )
return;
?>
<script type="text/javascript">
jQuery(document).ready( function($)
{
$('#update-from-provider').prependTo('span.displaying-num');
});
</script>
<?php
}
Which results in this: