Override Plugin function - wordpress

I am using the WP Jobs Plugin with the add-on WP Jobs Application Deadline and there is a function that I need to change:
public function display_the_deadline() {
global $post;
$deadline = get_post_meta( $post->ID, '_application_deadline', true );
$expiring = false;
$expired = false;
$date_str = null;
if ( $deadline ) {
$expiring_days = apply_filters( 'job_manager_application_deadline_expiring_days', 2 );
$expiring = ( floor( ( current_time( 'timestamp' ) - strtotime( $deadline ) ) / ( 60 * 60 * 24 ) ) >= -$expiring_days );
$expired = ( floor( ( current_time( 'timestamp' ) - strtotime( $deadline ) ) / ( 60 * 60 * 24 ) ) >= 0 );
$date_str = date_i18n( $this->get_date_format(), strtotime( $deadline ) );
}
// Do not display anything if listing is already expired.
if ( is_singular( 'job_listing' ) && $expired ) {
return;
}
$timestamp = strtotime( $deadline );
/**
* Filters the display string for the application closing date.
*
* #since 1.2.1
*
* #param string $date_str The default date string to be displayed.
* #param string $timestamp The timestamp of the closing date.
*/
$date_str = apply_filters( 'job_manager_application_deadline_closing_date_display', $date_str, $timestamp );
if ( $date_str ) {
echo '<li class="application-deadline ' . ( $expiring ? 'expiring' : '' ) . ' ' . ( $expired ? 'expired' : '' ) . '"><label>' . ( $expired ? __( 'Closed', 'wp-job-manager-application-deadline' ) : __( 'Closes', 'wp-job-manager-application-deadline' ) ) . ':</label> ' . $date_str . '</li>';
}
}
In the section that echo's the "li class="application-deadline..." I need to change Closes: to Deadline: and am wondering if it is possible to override this function in my functions.php file. I tried to replace it using Jquery but that didn't work.

Unfortunately there's not a great way to filter existing functions in plugins unless they're designed that way - and it doesn't appear this one is set up to allow that.
Using JavaScript should absolutely work. Since your HTML output is something like:
<li class="application-deadline expiring"><label>Closes:</label> Sept 4th, 2018</li>
You should be able to select the element fairly easily. Take a look at this vanilla JS snippet:
let deadlines = document.querySelectorAll('.application-deadline');
for( i = 0, n = deadlines.length; i < n; ++i ){
deadlines[i].innerText = deadlines[i].innerText.replace('Closes:', 'Deadline:');
}
<li class="application-deadline expiring"><label>Closes:</label> Sept 4th, 2018</li>
Or if you so-desire, a jQuery version:
jQuery(document).ready(function($){
$('.application-deadline').each(function(){
$(this).text( $(this).text().replace('Closes:', 'Deadline:') );
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li class="application-deadline expiring"><label>Closes:</label> Sept 4th, 2018</li>
Edit: It appears your content is loaded in dynamically with Ajax. You could attempt to use $.ajaxComplete to handle the code:
jQuery(document).ajaxComplete( function( event, request, settings){
$('.application-deadline').each(function(){
$(this).text( $(this).text().replace('Closes:', 'Deadline:') );
});
});
Alternatively you can abuse CSS pseudo-elements to manipulate the text:
.job_listing-location.job-end label {
font-size: 0;
}
.job_listing-location.job-end label:before {
content: "Deadline:";
font-size: 16px;
}

Related

Show discount percentage in WooCommerce sale button

I'm looking for a way to show the percentage of discount in the sale bubble in WooCommerce. Here is an image how the button looks now:
So, basically, the button will show: -20%
You should be able to hook into the woocommerce_sale_flash filter, grab the product object, work out the percentage and add it to the HTML.
Something like this:
add_filter( 'woocommerce_sale_flash', 'add_percentage_to_sale_bubble' );
function add_percentage_to_sale_bubble( $html ) {
global $product;
$percentage = round( ( ( $product->regular_price - $product->sale_price ) / $product->regular_price ) * 100 );
$output =' <span class="onsale">VERKOOP -'.$percentage.'%</span>';
return $output;
}
Edit - Variable Products:
With variable products getting thrown into the mix, you will need to include a check using is_type('simple|variable') and adjust your calculations from there, like this:
add_filter( 'woocommerce_sale_flash', 'add_percentage_to_sale_bubble', 20 );
function add_percentage_to_sale_bubble( $html ) {
global $product;
if ($product->is_type('simple')) { //if simple product
$percentage = round( ( ( $product->regular_price - $product->sale_price ) / $product->regular_price ) * 100 ).'%';
} else { //if variable product
$percentage = get_variable_sale_percentage( $product );
}
$output =' <span class="onsale">-'.$percentage.'</span>';
return $output;
}
function get_variable_sale_percentage( $product ) {
//get variables
$variation_min_regular_price = $product->get_variation_regular_price('min', true);
$variation_max_regular_price = $product->get_variation_regular_price('max', true);
$variation_min_sale_price = $product->get_variation_sale_price('min', true);
$variation_max_sale_price = $product->get_variation_sale_price('max', true);
//get highest and lowest percentages
$lower_percentage = round( ( ( $variation_min_regular_price - $variation_min_sale_price ) / $variation_min_regular_price ) * 100 );
$higher_percentage = round( ( ( $variation_max_regular_price - $variation_max_sale_price ) / $variation_max_regular_price ) * 100 );
//sort array
$percentages = array($lower_percentage, $higher_percentage);
sort($percentages);
if ($percentages[0] != $percentages[1] && $percentages[0]) {
return $percentages[0].'% - '.$percentages[1].'%';
} else {
return $percentages[1].'%';
}
}

Creating breadcrumbs without plugin WordPress

How can i create breadcrumb home->page->post name when we click on main menu any page, open the list of post that time breadcrumb create home ->page name its ok but now when we click on any post that time breadcrumb create home->post category name->post name is is that when we click on post category name on breadcrumb layout shown different we want to its goes on page link, not category link. so we need to when we open any post we need to create the breadcrumb like this home->page name->post name so when we click on page name open the post list page, not category page.
WordPress doesn't provide builtin breadcrumbs functionality. Thus you'll have to either use a plugin or else code it yourself (or copy from the reference below).
As a matter of fact, the plugin or custom code, if providing similar functionality, make not much of a difference. Thus use the one which is more convenient for you.
If you would like to add a custom code, here are few resources which I could look up on search:
https://www.techpulsetoday.com/wordpress-breadcrumbs-without-plugin/
https://www.thewebtaylor.com/articles/wordpress-creating-breadcrumbs-without-a-plugin
https://www.codexworld.com/wordpress-how-to-display-breadcrumb-without-plugin/
https://gist.github.com/tinotriste/5387124
You can look into them and modify them as you wish!
I hope it helps!
I can't understand how an answer with only pasted links can get to that many upvote. The regular WordPress breadcrumb approach is painfully unoptimized, most of the one out there do not suit custom themes. I decided to built a URL based breadcrumb which is, from my point of view, far more efficient and adaptable. I wanted something generic, SEO friendly, without any default styling. It needed also to properly handle posts and pages title.
Version
Requires at least WordPress:
5.0.0
Requires at least PHP:
7.0.0
Tested up to WordPress:
6.0.2
The latest version is available on my GitHub as an unofficial WordPress plugin.
<?php
/**
* Checks if a string ends with a given substring.
* Backward compatibility for PHP < 8.0.0.
*
* #since 1.2.0
* #param String $haystack The string to search in.
* #param String $needle The substring to search for in the haystack.
* #return Boolean
*/
if ( ! function_exists( 'backward_compatibility_str_ends_with' ) ) {
function backward_compatibility_str_ends_with( $haystack, $needle ) {
$length = strlen( $needle );
if ( ! $length ) {
return true;
};
return substr( $haystack, -$length ) === $needle;
};
};
/**
* Determine if a string contains a given substring.
* Backward compatibility for PHP < 8.0.0.
*
* #since 1.2.0
* #param String $haystack The string to search in.
* #param String $needle The substring to search for in the haystack.
* #return Boolean
*/
if ( ! function_exists( 'backward_compatibility_str_contains' ) ) {
function backward_compatibility_str_contains( $haystack, $needle ) {
if ( strpos( $haystack, $needle ) !== false ) {
return true;
};
};
};
/**
* Retrieve the crumbs.
*
* #since 1.0.0
* #return Array Crumbs array.
*/
if ( ! function_exists( 'get_the_crumbs' ) ) {
function get_the_crumbs() {
/**
* $_SERVER["REQUEST_SCHEME"] seems to be UNRELIABLE.
*
* Article "Is $_SERVER['REQUEST_SCHEME'] reliable?".
* #see https://stackoverflow.com/a/18008178/3645650
*
* $_SERVER['REQUEST_SCHEME'] is a native variable of Apache web server since its version 2.4.
* Naturally, if a variable is not set by the server, PHP will not include it in its global array $_SERVER.
*
* An alternative to $_SERVER['REQUEST_SCHEME'] is $_SERVER['HTTPS'] which set to a non-empty value if the script was queried through the HTTPS protocol.
*
* Article "How to find out if you're using HTTPS without $_SERVER['HTTPS']".
* #see https://stackoverflow.com/a/16076965/3645650
*/
if ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) {
$server_scheme = 'https';
} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || ! empty( $_SERVER['HTTP_X_FORWARDED_SSL'] ) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on' ) {
$server_scheme = 'https';
} else {
$server_scheme = 'http';
};
/**
* $_SERVER["REQUEST_URI"] seems to be RELIABLE.
* $_SERVER['REQUEST_URI'] will not be empty in WordPress, because it is filled in wp_fix_server_vars() (file wp-includes/load.php).
*
* Article "Is it safe to use $_SERVER['REQUEST_URI']?".
* #see https://wordpress.stackexchange.com/a/110541/190376
*/
$server_uri = $_SERVER['REQUEST_URI'];
/**
* $_SERVER["HTTP_HOST"] seems to be RELIABLE.
*
* Article "How reliable is HTTP_HOST?".
* #see https://stackoverflow.com/a/4096246/3645650
*/
$server_host = $_SERVER["HTTP_HOST"];
if ( backward_compatibility_str_contains( $server_uri, '?' ) ) {
$server_uri = substr( $server_uri, 0, strpos( $server_uri, '?' ) );
};
if ( backward_compatibility_str_ends_with( $server_uri, '/' ) ) {
$server_uri = explode( '/', substr( $server_uri, 1, -1 ) );
} else {
$server_uri = explode( '/', substr( $server_uri, 1 ) );
};
$crumbs = array();
foreach ( $server_uri as $crumb ) {
$slug = esc_html( urldecode( $crumb ) );
$url = esc_url( $server_scheme . '://' . $server_host . '/' . substr( implode( '/', $server_uri ), 0, strpos( implode( '/', $server_uri ), $crumb ) ) . $crumb. '/' );
array_push( $crumbs,
array(
'slug' => $slug,
'url' => $url,
)
);
};
/**
* WordPress, by default, doesn't generate a taxonomy index, meaning https://.../taxonomy will redirect to a 404.
* Any request needs to be made against a term. eg: https://.../taxonomy/term will redirect to taxonomy.php.
* Therefore we need to remove the taxonomy slug from the crumbs array to avoid displaying a link to a 404.
*
* We round up all taxonomies through get_taxonomies().
* #see https://developer.wordpress.org/reference/functions/get_taxonomies/
*
* Through array_filter we filter-out any matching crumbs.
* #see https://www.php.net/manual/en/function.array-filter.php
*/
$banned_slugs = array();
$taxonomies = get_taxonomies(
array(
'public' => true,
),
'objects'
);
foreach ( $taxonomies as $taxonomy ) {
array_push( $banned_slugs, $taxonomy->name );
if ( isset( $taxonomy->rewrite['slug'] ) ) {
array_push( $banned_slugs, $taxonomy->rewrite['slug'] );
};
};
$banned_crumbs = array();
foreach ( $banned_slugs as $banned_slug ) {
$slug = esc_html( $banned_slug );
$url = esc_url( $server_scheme . '://' . $server_host . '/' . substr( implode( '/', $server_uri ), 0, strpos( implode( '/', $server_uri ), $banned_slug ) ) . $banned_slug. '/' );
array_push( $banned_crumbs,
array(
'slug' => $slug,
'url' => $url,
)
);
};
$crumbs = array_filter( $crumbs, function( $crumb ) use ( $banned_slugs ) {
if ( ! in_array( $crumb['slug'], $banned_slugs ) && ! in_array( $crumb['url'], $banned_slugs ) ) {
return ! in_array( $crumb['slug'], $banned_slugs );
};
} );
return $crumbs;
};
};
/**
* Display the bread, a formatted crumbs list.
*
* #since 1.0.0
* #param Array $ingredients The bread arguments.
* #param Array $ingredients['crumbs'] The crumbs array. Default to get_the_crumbs().
* #param Array $ingredients['root'] Root crumb. Default to null.
* #param String $ingredients['root']['slug'] Root crumb slug.
* #param String $ingredients['root']['url'] Root crumb url.
* #param String $ingredients['separator'] The crumb's separator.
* #param Integer $ingredients['offset'] Crumbs offset. Accept positive/negative Integer. Default to "0". Refer to array_slice, https://www.php.net/manual/en/function.array-slice.php.
* #param Integer $ingredients['length'] Crumbs length. Accept positive/negative Integer. Default to "null". Refer to array_slice, https://www.php.net/manual/en/function.array-slice.php.
* #return Array The formatted crumbs list.
*/
if ( ! function_exists( 'the_bread' ) ) {
function the_bread( $ingredients = array() ) {
if ( empty( $ingredients['crumbs'] ) ) {
$crumbs = get_the_crumbs();
} else {
$crumbs = $ingredients['crumbs'];
};
if ( empty( $ingredients['root'] ) ) {
$root = null;
} else {
$root = $ingredients['root'];
};
if ( empty( $ingredients['offset'] ) ) {
$offset = 0;
} else {
$offset = $ingredients['offset'];
};
if ( empty( $ingredients['length'] ) ) {
$length = null;
} else {
$length = $ingredients['length'];
};
/**
* Handling the root crumb case.
* Prepend one or more elements to the beginning of an array.
* #see https://www.php.net/manual/en/function.array-unshift.php
*/
if ( ! empty( $root ) ) {
array_unshift( $crumbs, $ingredients['root'] );
};
/**
* Handling the length case.
* Extract a slice of the array.
* #see https://www.php.net/manual/en/function.array-slice.php
*/
$crumbs = array_slice( $crumbs, $offset, $length );
if ( ! empty( $crumbs ) ) {
echo '<ol class="🍞 bread" itemscope itemtype="https://schema.org/BreadcrumbList">';
$i = 0;
foreach ( $crumbs as $crumb ) {
$i++;
/**
* Unparsing the slug.
*/
if ( url_to_postid( $crumb['url'] ) ) {
$title = get_the_title( url_to_postid( $crumb['url'] ) );
} elseif ( get_page_by_path( $crumb['slug'] ) ) {
$title = get_the_title( get_page_by_path( $crumb['slug'] ) );
} else {
$title = ucfirst( str_replace( '-', ' ', $crumb['slug'] ) );
};
echo '<li class="crumb" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a itemprop="item" href="' . $crumb['url'] . '">
<span itemprop="name">' . $title . '</span>
</a>
<meta itemprop="position" content="' . $i . '">
</li>';
if ( $i !== sizeof( $crumbs ) && ! empty( $ingredients['separator'] ) ) {
echo $ingredients['separator'];
};
};
echo '</ol>';
};
};
};
Displaying the bread, a formatted crumbs list.
<?php
the_bread( $ingredients = array() );
Parameters
Parameter
Description
$ingredients
(Optional) Array The bread arguments.
$ingredients['crumbs']
Array The crumbs array. Default to get_the_crumbs().
$ingredients['root']
Array Root crumb. Default to null.
$ingredients['root']['slug']
(Required if $ingredients['root']). Root crumb slug.
$ingredients['root']['url']
(Required if $ingredients['root']). Root crumb url.
$ingredients['separator']
The crumb's separator.
$ingredients['offset']
Crumbs offset. Accept positive/negative Integer. Default to 0. Refer to array_slice.
$ingredients['length']
Crumbs length. Accept positive/negative Integer. Default to null. Refer to array_slice.
Example: The bread with a custom separator
<?php
$ingredients = array(
'separator' => 'β†’',
);
the_bread( $ingredients );
Example: Displaying the last 3 crumbs
<?php
$ingredients = array(
'offset' => -3,
'length' => 3,
);
the_bread( $ingredients );
Example: The bread with a root crumb
<?php
$ingredients = array(
'root' => array(
'slug' => 'home',
'url' => get_home_url(),
),
);
the_bread( $ingredients );
Example: Intercepting the crumbs array
<?php
//Intercept the crumbs array...
$crumbs = get_the_crumbs();
//... Do something with it:
//In our case we're appending a new crumb to the crumbs array.
array_push( $crumbs, array(
'slug' => 'search',
'url' => 'https://.../search/',
) );
$ingredients = array(
'crumbs' => $crumbs,
);
the_bread( $ingredients );
HTML5 structure output
<ol class="🍞 bread" itemscope="" itemtype="https://schema.org/BreadcrumbList">
<li class="crumb" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemprop="item" href="http://example.com/where/">
<span itemprop="name">Where</span>
</a>
<meta itemprop="position" content="1">
</li>
>
<li class="crumb" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemprop="item" href="http://example.com/where/is/">
<span itemprop="name">Is</span>
</a>
<meta itemprop="position" content="2">
</li>
>
<li class="crumb" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemprop="item" href="http://example.com/where/is/my/">
<span itemprop="name">My</span>
</a>
<meta itemprop="position" content="3">
</li>
>
<li class="crumb" itemprop="itemListElement" itemscope="" itemtype="https://schema.org/ListItem">
<a itemprop="item" href="http://example.com/where/is/my/bread/">
<span itemprop="name">Bread</span>
</a>
<meta itemprop="position" content="4">
</li>
</ol>
Minimal css boilerplate (Optional)
.🍞,
.bread {
list-style-type: none;
margin:0;
padding:0;
}
.🍞 li,
.bread li {
display:inline-block;
}
.🍞 li.crumb:last-child a,
.bread li.crumb:last-child a {
text-decoration: none;
pointer-events: none;
color: inherit;
}
Retrieving the crumbs
Even tho we recommend you to use the_bread() function to display and build your own breadcrumb, you can use get_the_crumbs() to retrieve the crumbs object.
Example: Outputting the crumbs object
<?php
var_dump( get_the_crumbs() );
A minimalistic breadcrumbs generator in a few lines as a filter:
add_filter( 'my_get_breadcrumbs', [ $this, 'the_breadcrumbs' ], 10, 1 );
/**
* Returns a list of all the breadcrumbs for the current page.
* usage: apply_filters( 'my_get_breadcrumbs', false )
*
* #param $max_depth int
*
* #return string
*/
function the_breadcrumbs() {
$crumbs = '';
$current_page_id = get_the_ID();
$parent = wp_get_post_parent_id( $current_page_id );
$index = 0;
while ( $parent ) {
$index ++;
$crumbs = '<li>' . get_the_title( $parent ) . '</li>' . $crumbs;
$parent = wp_get_post_parent_id( $parent );
if ( $index > 10 ) {
break;
}
}
return $crumbs . '<li><a>' . get_the_title( $current_page_id ) . '</a></li>';
}

Wordpress Excerpt Character Limit On Theme wpexplorer Today Theme

I need help for fixing http://wpexplorer-demos.com/today/page/2/ today theme.
frontpage Boxes takes random sizes. How i fix it from words to characters. Also images. any help plz really appreciated. I am not a coder.
/**
* Custom excerpts based on wp_trim_words
* Created for child-theming purposes
*
* #link http://codex.wordpress.org/Function_Reference/wp_trim_words
* #since 1.0.0
*/
function wpex_excerpt( $length = 45, $readmore = false ) {
// Get global post data
global $post;
// Check for custom excerpt
if ( has_excerpt( $post->ID ) ) {
$output = $post->post_excerpt;
}
// No custom excerpt...so lets generate one
else {
// Redmore text
$readmore_text = get_theme_mod( 'entry_readmore_text', esc_html__( 'read more', 'today' ) );
// Readmore link
$readmore_link = ''. $readmore_text .'<span class="wpex-readmore-rarr">β†’</span>';
// Check for more tag and return content if it exists
if ( strpos( $post->post_content, '<!--more-->' ) ) {
$output = get_the_content( '' );
}
// No more tag defined so generate excerpt using wp_trim_words
else {
// Generate excerpt
$output = wp_trim_words( strip_shortcodes( get_the_content( $post->ID ) ), $length );
// Add readmore to excerpt if enabled
if ( $readmore == true ) {
$output .= apply_filters( 'wpex_readmore_link', $readmore_link );
}
}
}
// Apply filters and echo
echo apply_filters( 'wpex_excerpt', $output );
}
I've edited it for you:
**
* Custom excerpts based on wp_trim_words
* Created for child-theming purposes
*
* #link http://codex.wordpress.org/Function_Reference/wp_trim_words
* #since 1.0.0
*/
function wpex_excerpt( $length = 45, $readmore = false ) {
// Get global post data
global $post;
$max_characters = 40;
// Check for custom excerpt
if ( has_excerpt( $post->ID ) ) {
$output = mb_strimwidth($post->post_excerpt, 0, $max_characters, '...');
}
// No custom excerpt...so lets generate one
else {
// Redmore text
$readmore_text = get_theme_mod( 'entry_readmore_text', esc_html__( 'read more', 'today' ) );
// Readmore link
$readmore_link = ''. $readmore_text .'<span class="wpex-readmore-rarr">β†’</span>';
// Check for more tag and return content if it exists
if ( strpos( $post->post_content, '<!--more-->' ) ) {
$output = mb_strimwidth(get_the_content( '' ), 0, $max_characters, '...');
}
// No more tag defined so generate excerpt using wp_trim_words
else {
// Generate excerpt
$output_to_trim = wp_trim_words( strip_shortcodes( get_the_content( $post->ID ) ), $length );
$output = mb_strimwidth($output_to_trim, 0, $max_characters, '...');
// Add readmore to excerpt if enabled
if ( $readmore == true ) {
$output .= apply_filters( 'wpex_readmore_link', $readmore_link );
}
}
}
// Apply filters and echo
echo apply_filters( 'wpex_excerpt', $output );
}
You can change the maximum of characters in this code by changing number in $max_characters variable.
I used mb_strimwidth() function and return it to $output variable: https://secure.php.net/manual/pl/function.mb-strimwidth.php
By example you should understand what's going on:
<?php
echo mb_strimwidth("Hello World", 0, 10, "...");
// outputs Hello W...
?>
The function needs string like "Hello World", start of string (like 0), maximum number of characters (like 10) and at the end you can optionally choose nice ending of excerpt (like ...).

WooCommerce: how to add multiple products to cart at once?

I need a "get products A, B and C for $xxx" special offer, products A, B and C must be available on their own, and the bundle is a special offer accessible through a coupon code.
On a marketing page hosting outside my site, I would like a button leading to my site that carries a query string like ?add-to-cart=244,249,200 so that once on my site, all bundle products are already added to the cart (instead of adding them one by one which sounds unacceptably tedious).
If not possible, then at least I'd like a landing page on my site with a single button adding all bundle products to cart at once.
I couldn't find working solutions googling around (here's one example). Any suggestion?
After some research I found that DsgnWrks wrote a hook that does exactly this. For your convenience, and in case the blog goes offline, I bluntly copied his code to this answer:
function woocommerce_maybe_add_multiple_products_to_cart( $url = false ) {
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
// Remove WooCommerce's hook, as it's useless (doesn't handle multiple products).
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $id_and_quantity ) {
// Check for quantities defined in curie notation (<product_id>:<product_quantity>)
// https://dsgnwrks.pro/snippets/woocommerce-allow-adding-multiple-products-to-the-cart-via-the-add-to-cart-query-string/#comment-12236
$id_and_quantity = explode( ':', $id_and_quantity );
$product_id = $id_and_quantity[0];
$_REQUEST['quantity'] = ! empty( $id_and_quantity[1] ) ? absint( $id_and_quantity[1] ) : 1;
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action( $url );
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->get_type(), $adding_to_cart );
// Variable product handling
if ( 'variable' === $add_to_cart_handler ) {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_variable', $product_id );
// Grouped Products
} elseif ( 'grouped' === $add_to_cart_handler ) {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_grouped', $product_id );
// Custom Handler
} elseif ( has_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler ) ){
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url );
// Simple Products
} else {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_simple', $product_id );
}
}
}
// Fire before the WC_Form_Handler::add_to_cart_action callback.
add_action( 'wp_loaded', 'woocommerce_maybe_add_multiple_products_to_cart', 15 );
/**
* Invoke class private method
*
* #since 0.1.0
*
* #param string $class_name
* #param string $methodName
*
* #return mixed
*/
function woo_hack_invoke_private_method( $class_name, $methodName ) {
if ( version_compare( phpversion(), '5.3', '<' ) ) {
throw new Exception( 'PHP version does not support ReflectionClass::setAccessible()', __LINE__ );
}
$args = func_get_args();
unset( $args[0], $args[1] );
$reflection = new ReflectionClass( $class_name );
$method = $reflection->getMethod( $methodName );
$method->setAccessible( true );
$args = array_merge( array( $class_name ), $args );
return call_user_func_array( array( $method, 'invoke' ), $args );
}
It works just like you'd expect, by providing a comma separated list of products. It even works with quantities using ?add-to-cart=63833:2,221916:4
I was, and am still looking for a 'pure' solution that allows to add multiple products to the cart without having to install a plugin or add custom actions. But for many, the above might be an appropriate solution

How do you create a basic Wordpress admin pointer?

I have been looking around for quite awhile now and all I have found are tutorials from 3-4 years ago that explain how to do a pointer tour. All I want to do is add a pointer that pops up when someone activates my plugin so that I can notify them of a new menu option where they will go to view my plugin settings. Any help would be greatly appreciated!
Pointers in WP need 3 components:
1: wp-pointer css file
2: wp-pointer JS file
3: A JavaScript snippet
To 1 and 2
include them simply with:
wp_enqueue_style( 'wp-pointer' );
and
wp_enqueue_script( 'wp-pointer' );
The JS code:
<script type="text/javascript">
(function($){
var options = {"content":"<h3>Personal Data and Privacy<\/h3><h4>Personal Data Export and Erasure<\/h4><p>New <strong>Tools<\/strong> have been added to help you with personal data export and erasure requests.<\/p><h4>Privacy Policy<\/h4><p>Create or select your site’s privacy policy page under <strong>Settings > Privacy<\/strong> to keep your users informed and aware.<\/p>","position":{"edge":"left","align":"bottom"},"pointerClass":"wp-pointer arrow-bottom","pointerWidth":420}, setup;
if ( ! options )
return;
options = $.extend( options, {
close: function() {
$.post( ajaxurl, {
pointer: 'wp500_isrc_pointer',
action: 'dismiss-wp-pointer'
});
}
});
setup = function() {
$('#menu-settings').first().pointer( options ).pointer('open');
};
if ( options.position && options.position.defer_loading )
$(window).bind( 'load.wp-pointers', setup );
else
$(document).ready( setup );
})( jQuery );
</script>
Of Course you need to wrap all them in a php file to check the user capabilities and check the dismiss from the users meta.
I have copied the WP pointer class in wp-admin/includes/class-wp-internal-pointers and made a custom one from it.
Here the complete code which i can call it with an action hook like:
add_action( 'admin_enqueue_scripts', array( 'isrc_Internal_Pointers', 'enqueue_scripts') );
add_action( 'user_register',array( 'isrc_Internal_Pointers', 'dismiss_pointers_for_new_users' ) );
The Full PHP file (include it in your code and call the 2 actions):
<?php
/**
* Administration API: WP_Internal_Pointers class
*
* #package WordPress
* #subpackage Administration
* #since 4.4.0
*/
/**
* Core class used to implement an internal admin pointers API.
*
* #since 3.3.0
*/
final class isrc_Internal_Pointers {
/**
* Initializes the new feature pointers.
*
* #since 3.3.0
*
* All pointers can be disabled using the following:
* remove_action( 'admin_enqueue_scripts', array( 'WP_Internal_Pointers', 'enqueue_scripts' ) );
*
* Individual pointers (e.g. wp390_widgets) can be disabled using the following:
* remove_action( 'admin_print_footer_scripts', array( 'WP_Internal_Pointers', 'pointer_wp390_widgets' ) );
*
* #static
*
* #param string $hook_suffix The current admin page.
*/
public static function enqueue_scripts( $hook_suffix ) {
/*
* Register feature pointers
*
* Format:
* array(
* hook_suffix => pointer callback
* )
*
* Example:
* array(
* 'themes.php' => 'wp390_widgets'
* )
*/
$registered_pointers = array(
'index.php' => 'wp500_isrc_pointer',
);
// Check if screen related pointer is registered
if ( empty( $registered_pointers[ $hook_suffix ] ) )
return;
$pointers = (array) $registered_pointers[ $hook_suffix ];
/*
* Specify required capabilities for feature pointers
*
* Format:
* array(
* pointer callback => Array of required capabilities
* )
*
* Example:
* array(
* 'wp390_widgets' => array( 'edit_theme_options' )
* )
*/
$caps_required = array(
'wp500_isrc_pointer' => array(
'install_plugins'
),
);
// Get dismissed pointers
$dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) );
$got_pointers = false;
foreach ( array_diff( $pointers, $dismissed ) as $pointer ) {
if ( isset( $caps_required[ $pointer ] ) ) {
foreach ( $caps_required[ $pointer ] as $cap ) {
if ( ! current_user_can( $cap ) )
continue 2;
}
}
// Bind pointer print function
add_action( 'admin_print_footer_scripts', array( 'isrc_Internal_Pointers', 'pointer_'.$pointer ) );
$got_pointers = true;
}
if ( ! $got_pointers )
return;
// Add pointers script and style to queue
wp_enqueue_style( 'wp-pointer' );
wp_enqueue_script( 'wp-pointer' );
}
/**
* Print the pointer JavaScript data.
*
* #since 3.3.0
*
* #static
*
* #param string $pointer_id The pointer ID.
* #param string $selector The HTML elements, on which the pointer should be attached.
* #param array $args Arguments to be passed to the pointer JS (see wp-pointer.js).
*/
private static function print_js( $pointer_id, $selector, $args ) {
if ( empty( $pointer_id ) || empty( $selector ) || empty( $args ) || empty( $args['content'] ) )
return;
?>
<script type="text/javascript">
(function($){
var options = <?php echo wp_json_encode( $args ); ?>, setup;
if ( ! options )
return;
options = $.extend( options, {
close: function() {
$.post( ajaxurl, {
pointer: '<?php echo $pointer_id; ?>',
action: 'dismiss-wp-pointer'
});
}
});
setup = function() {
$('<?php echo $selector; ?>').first().pointer( options ).pointer('open');
};
if ( options.position && options.position.defer_loading )
$(window).bind( 'load.wp-pointers', setup );
else
$(document).ready( setup );
})( jQuery );
</script>
<?php
}
/**
* Display a pointer for wp500_isrc_pointer
*
* #since 4.9.6
*/
public static function pointer_wp500_isrc_pointer() {
$content = '<h3>' . __( 'Personal Data and Privacy' ) . '</h3>';
$content .= '<h4>' . __( 'Personal Data Export and Erasure' ) . '</h4>';
$content .= '<p>' . __( 'New <strong>Tools</strong> have been added to help you with personal data export and erasure requests.' ) . '</p>';
$content .= '<h4>' . __( 'Privacy Policy' ) . '</h4>';
$content .= '<p>' . __( 'Create or select your site’s privacy policy page under <strong>Settings > Privacy</strong> to keep your users informed and aware.' ) . '</p>';
if ( is_rtl() ) {
$position = array(
'edge' => 'right',
'align' => 'bottom',
);
} else {
$position = array(
'edge' => 'left',
'align' => 'bottom',
);
}
$js_args = array(
'content' => $content,
'position' => $position,
'pointerClass' => 'wp-pointer arrow-bottom',
'pointerWidth' => 420,
);
self::print_js( 'wp500_isrc_pointer', '#menu-settings', $js_args );
}
/**
* Prevents new users from seeing existing 'new feature' pointers.
*
* #since 3.3.0
*
* #static
*
* #param int $user_id User ID.
*/
public static function dismiss_pointers_for_new_users( $user_id ) {
add_user_meta( $user_id, 'dismissed_wp_pointers', 'wp500_isrc_pointer' );
}
}
What you are looking for is WordPress Activation / Deactivation Hooks. For example:
register_activation_hook( __FILE__, 'pluginprefix_function_to_run' );
And on pluginprefix_function_to_run, display a nice message to let users know that you've added a menu using admin_notices:
function my_admin_notice() {
?>
<div class="updated">
<p><?php _e( 'Your message goes here!', 'my-text-domain' ); ?></p>
</div>
<?php
}
function pluginprefix_function_to_run() {
add_action( 'admin_notices', 'my_admin_notice' );
}

Resources