i have something i cant get working and i dont understand why.
I want to add some custom fields below a grouped product in woocommerce on the frontend.
So i have the following function (which works fine):
function add_data_opbouw_front(){
global $woocommerce; global $post;
$id = $post->ID;
if ($id === 1411){
$optiestijd = array();
$optiesdag = array();
$id = get_the_ID();
$html = '<table><tbody>';
$i=0;
if( !empty(get_post_meta($id,'tijdslot',true) ) ){
$html .= '<tr><td>Kies een tijd:</td><td><select name="tijdslot" required><option value="">Kies:</option>';
$optiestijden = get_post_meta(get_the_ID(),'tijdslot',true);
foreach ($optiestijden as $key=>$optie){
$optiestijd[$optie] = 'Kies een tijdslot:';
}
foreach ($optiestijd as $optie => $label){
if(!empty($optie)){
$i++;
$html .= '<option value="'.$optie.'"><span class="choiceoption">'. $optie.'</span></option>';
}
}
$html .= '</select></td></tr>';
}
if( !empty(get_post_meta($id,'dag',true) ) ){
$optiesdagen = get_post_meta(get_the_ID(),'dag',true);
$optiesdagen = explode('|',$optiesdagen);
$html.= '<tr><td>Kies een dag:</td><td><select name="dag" required><option value="">Kies:</option>';
foreach ($optiesdagen as $optie){
$i++;
$html .= '<option value="'.$optie.'"><span class="choiceoption">'. $optie.'</span></option>';
}
}
$html .= '</select></td></tr></tbody></table>';
$html .= '<input type="hidden" name="voorraad_afname" value="1">';
echo $html;
}
}
add_action('woocommerce_before_add_to_cart_button','add_data_opbouw_front', 35);
because i want to obligate user to pick a day and time, i want to validate before the add to cart and i do it like this:
function so_validate_add_cart_item( $passed, $product_id, $quantity, $variation_id = '', $variations= '' ) {
global $woocommerce; global $post;
$id = $post->ID;
if ($id === 1411){
// do your validation, if not met switch $passed to false
if ( empty($_POST['tijdslot']) || empty($_POST['dag']) ){
$passed = false;
wc_add_notice( __( 'De velden "Kies een dag" en "Kies een tijdslot" zijn beiden verplicht', 'textdomain' ), 'error' );
}
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'so_validate_add_cart_item', 10, 5 );
But when I leave one of the select boxes empty, the product is being added to the cart anyway. So the validate doesnt throw in the error. I dont really understand why this is not working.
Any ideas?
After a long puzzle, i found out that approaching global $post is not working. Better is to approach the product_id. Because it is a grouped product, i checked wether one of the grouped products ID are in the add-to-cart like so:
$check = array('1398','1399','1400');
if (in_array($product_id,$check)){
And that does work.
So updated code is:
function so_validate_add_cart_item( $passed, $product_id, $quantity, $variation_id = '', $variations= '' ) {
$check = array('1398','1399','1400');
if (in_array($product_id,$check)){
// check if both selectboxes are used
if ( empty($_POST['tijdslot']) || empty($_POST['dag']) ){
$passed = false;
wc_add_notice( __( 'De velden "Kies een dag" en "Kies een tijdslot" zijn beiden verplicht', 'textdomain' ), 'error' );
}
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'so_validate_add_cart_item', 10, 5 );
Related
I found the following code in an older thread (Shortcode that display all product attributes set for a WooCommerce product) which basicly SHOULD be the solution to what im looking for
function get_product_attributes_shortcode($atts ) {
// Extract shortcode attributes
extract( shortcode_atts( array(
'id' => get_the_ID(),
), $atts, 'display-attributes' ) );
global $product;
if ( ! is_a($product, 'WC_Product') ) {
$product = wc_get_product( $id );
}
if ( is_a($product, 'WC_Product') ) {
$html = []; // Initializing
foreach ( $product->get_attributes() as $attribute => $values ) {
$attribute_name = wc_attribute_label($values->get_name());
$attribute_data = $values->get_data();
$is_taxonomy = $attribute_data['is_taxonomy'];
$option_values = array(); // Initializing
// For taxonomy product attribute values
if( $is_taxonomy ) {
$terms = $values->get_terms(); // Get attribute WP_Terms
// Loop through attribute WP_Term(s)
foreach ( $terms as $term ) {
$term_link = get_term_link( $term, $attribute );
$option_values[] = ''.$term->name.'';
}
}
// For "custom" product attributes values
else {
// Loop through attribute option values
foreach ( $values->get_options() as $term_name ) {
$option_values[] = $term_name;
}
}
$html[] = '<strong>' . $attribute_name . '</strong>: ' . implode(', ', $option_values);
}
return '<div class="product-attributes">' . implode(' | ', $html) . '<div>';
}
}
add_shortcode( 'display-attributes', 'get_product_attributes_shortcode' );
I modified the output part to display the attributes in a table
// Loop through attribute WP_Term(s)
foreach ( $terms as $term ) {
$option_values[] = $term->name;
}
}
// For "custom" product attributes values
else {
// Loop through attribute option values
foreach ( $values->get_options() as $term_name ) {
$option_values[] = $term_name;
}
}
$html[] = '<tr><td class="column-1">' . $attribute_name . '</td><td class="column-2">' . implode(', ', $option_values) . '</td></tr>';
}
return '<h4>Technische Daten</h4><table class="tablepress table produktdatenblatt tablepress-responsive"><tbody class="row-hover">' . implode($html) . '</tbody></table>';
}
}
add_shortcode( 'display_attributes', 'get_product_attributes_shortcode' );
Usage should be like this: [display-attributes] or with a defined product Id [display-attributes id="254"]
But its not showing the attributes from the respective ID i put into the shortcode. Its just showing attributes from one product. I want to use the shortcode on Wordpress Pages and Posts to enrich product descriptions i do.
I tried to figure out if i can fix it myself, but im realy bad when it comes to coding :)
Can somebody help me with this?
Based on "How to add variation stock status to Woocommerce product variation dropdown" answer, I have made some code that shows the custom attribute of a variable product and its presence in the store:
// Function that will check the stock status and display the corresponding additional text
function get_stock_status_text($product, $name, $term_slug) {
foreach($product - > get_available_variations() as $variation) {
if ($variation['attributes'][$name] == $term_slug)
$stock = $variation['is_in_stock'];
}
return $stock == 1 ? '' : ' - out of stock';
}
// The hooked function that will add the stock status to the dropdown options elements.
add_filter('woocommerce_dropdown_variation_attribute_options_html', 'show_stock_status_in_dropdown', 10, 2);
function show_stock_status_in_dropdown($html, $args) {
// Only if there is a unique variation attribute (one dropdown)
if (sizeof($args['product'] - > get_variation_attributes()) == 1):
$options = $args['options'];
$product = $args['product'];
$attribute = $args['attribute']; // The product attribute taxonomy
$name = $args['name'] ? $args['name'] : 'attribute_'.sanitize_title($attribute);
$id = $args['id'] ? $args['id'] : sanitize_title($attribute);
$class = $args['class'];
$show_option_none = $args['show_option_none'] ? true : false;
$show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __('Select size', 'woocommerce');
if (empty($options) && !empty($product) && !empty($attribute)) {
$attributes = $product - > get_variation_attributes();
$options = $attributes[$attribute];
}
$html = '<select id="'.esc_attr($id).
'" class="'.esc_attr($class).
'" name="'.esc_attr($name).
'" data-attribute_name="attribute_'.esc_attr(sanitize_title($attribute)).
'" data-show_option_none="'.($show_option_none ? 'yes' : 'no').
'">';
$html. = '<option value="">'.esc_html($show_option_none_text).
'</option>';
if (!empty($options)) {
if ($product && taxonomy_exists($attribute)) {
$terms = wc_get_product_terms($product - > get_id(), $attribute, array('fields' => 'all'));
foreach($terms as $term) {
if (in_array($term - > slug, $options)) {
// HERE Added the function to get the text status
$stock_status = get_stock_status_text($product, $name, $term - > slug);
$html. = '<option value="'.esc_attr($term - > slug).
'" '.selected(sanitize_title($args['selected']), $term - > slug, false).
'>'.esc_html(apply_filters('woocommerce_variation_option_name', $term - > name).$stock_status).
'</option>';
}
}
} else {
foreach($options as $option) {
$selected = sanitize_title($args['selected']) === $args['selected'] ? selected($args['selected'], sanitize_title($option), false) : selected($args['selected'], $option, false);
// HERE Added the function to get the text status
$stock_status = get_the_stock_status($product, $name, $option);
$html. = '<option value="'.esc_attr($option).
'" '.$selected.
'>'.esc_html(apply_filters('woocommerce_variation_option_name', $option).$stock_status).
'</option>';
}
}
}
$html. = '</select>';
endif;
return $html;
}
On a single product page, I show related products. How to show this attribute (its presence in the store) in the form of a label in related products?
For example:
The product's name
Price - $25
Sizes available: L, XL
Try this code, I hooked into the woocommerce_after_shop_loop_item and displayed the product attributes. Right now it only works for the size attribute. It is showing the Large, Medium, Small. If you want to show L, XL, M, etc. just edit the size attribute.
add_action( 'woocommerce_after_shop_loop_item', 'mamham_woocommerce_after_shop_loop_item', 10);
function mamham_woocommerce_after_shop_loop_item() {
global $product;
// Bail if not in the single product page.
if ( ! is_product() ) return;
// Prduct attributes which you want to show.
// Attributes to show in the slug.
// 'attribute slug' => 'Text shown';
$show_attributes = array(
'size' => 'Sizes available:',
);
// Get the slugs.
$show_slugs = array_keys( $show_attributes );
// Get product type.
$product_type = $product->get_type();
if( 'simple' === $product_type ) {
$product_attributes = $product->get_attributes();
foreach( $product_attributes as $key => $product_attribute ) {
$slug = substr( $key, 3 );
if ( in_array( $slug, $show_slugs, TRUE ) ) {
$terms = $product_attribute->get_terms();
$terms_output = wp_list_pluck( $terms, 'name');
$terms_output = implode(', ', $terms_output );
echo esc_html( "${show_attributes[$slug]} ${terms_output}" );
}
}
} elseif ( 'variable' === $product_type ) {
$product_variations = $product->get_available_variations();
foreach ( $product_variations as $product_variation ) {
if ( $product_variation['is_in_stock'] && isset( $product_variation['attributes']['attribute_pa_size'] )) {
$terms_output[] = ucwords( $product_variation['attributes']['attribute_pa_size'] );
}
}
if ( ! empty( $terms_output) ) {
$terms_output = implode(', ', $terms_output );
echo esc_html( "Sizes available: ${terms_output}" );
}
}
}
Here is the screenshot, I have storefront theme and WooCommerce installed.
I have created a recently viewed script which generated a shortcode which I then inserted into my home page.
The script is designed so that people who may have visited my website and left, once they come back can see instantly what products they had been viewing on their last visit.
I have placed the shortcode [woocommerce_recently_viewed_products]
and have generated the shortcode using the following script:
function rc_woocommerce_recently_viewed_products( $atts, $content = null ) {
// Get shortcode parameters
extract(shortcode_atts(array(
"per_page" => '5'
), $atts));
// Get WooCommerce Global
global $woocommerce;
// Get recently viewed product cookies data
$viewed_products = ! empty( $_COOKIE['woocommerce_recently_viewed'] ) ? (array) explode( '|', $_COOKIE['woocommerce_recently_viewed'] ) : array();
$viewed_products = array_filter( array_map( 'absint', $viewed_products ) );
// If no data, quit
if ( empty( $viewed_products ) )
return __( 'You have not viewed any product yet!', 'rc_wc_rvp' );
// Create the object
ob_start();
wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) );
}
// Get products per page
if( !isset( $per_page ) ? $number = 4 : $number = $per_page )
// Create query arguments array
$query_args = array(
'posts_per_page' => $number,
'no_found_rows' => 1,
'post_status' => 'publish',
'post_type' => 'product',
'post__in' => $viewed_products,
'orderby' => 'rand'
);
// Add meta_query to query args
$query_args['meta_query'] = array();
// Check products stock status
$query_args['meta_query'][] = $woocommerce->query->stock_status_meta_query();
// Create a new query
$r = new WP_Query($query_args);
// If query return results
if ( $r->have_posts() ) {
$content = '<ul class="rc_wc_rvp_product_list_widget">';
// Start the loop
while ( $r->have_posts()) {
$r->the_post();
global $product;
$content .= '<li>
<a href="' . get_permalink() . '">
' . ( has_post_thumbnail() ? get_the_post_thumbnail( $r->post->ID, 'shop_thumbnail' ) : woocommerce_placeholder_img( 'shop_thumbnail' ) ) . ' ' . get_the_title() . '
</a> ' . $product->get_price_html() . '
</li>';
}
$content .= '</ul>';
}
// Get clean object
$content .= ob_get_clean();
// Return whole content
return $content;
}
// Register the shortcode
add_shortcode("woocommerce_recently_viewed_products",
"rc_woocommerce_recently_viewed_products");
Everything seems to have registered. However,when I test this myself. I view a few products, go back to the homepage where the shortcode is registered and I see the text
You have not viewed any product yet!
I can not figure out what might be missing in order to register and show the products which I or a potential customer may have viewed.
Woocommerce only save the recently viewed cookie IF woocommerce_recently_viewed_products WIDGET is ACTIVE! See code in wc-product-functions.php wc_track_product_view() function.
Code to save the cookie always in functions.php:
/**
* Track product views. Always.
*/
function wc_track_product_view_always() {
if ( ! is_singular( 'product' ) /* xnagyg: remove this condition to run: || ! is_active_widget( false, false, 'woocommerce_recently_viewed_products', true )*/ ) {
return;
}
global $post;
if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) ) { // #codingStandardsIgnoreLine.
$viewed_products = array();
} else {
$viewed_products = wp_parse_id_list( (array) explode( '|', wp_unslash( $_COOKIE['woocommerce_recently_viewed'] ) ) ); // #codingStandardsIgnoreLine.
}
// Unset if already in viewed products list.
$keys = array_flip( $viewed_products );
if ( isset( $keys[ $post->ID ] ) ) {
unset( $viewed_products[ $keys[ $post->ID ] ] );
}
$viewed_products[] = $post->ID;
if ( count( $viewed_products ) > 15 ) {
array_shift( $viewed_products );
}
// Store for session only.
wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) );
}
remove_action('template_redirect', 'wc_track_product_view', 20);
add_action( 'template_redirect', 'wc_track_product_view_always', 20 );
You need to set the cookie when you are viewing a single product page so use something like this where I set the cookie to equal the product ID I just viewed. In your case you'll need to get the cookie value if it exists then append the new product to the list of products.
function set_user_visited_product_cookie() {
global $post;
if ( is_product() ){
// manipulate your cookie string here, explode, implode functions
wc_setcookie( 'woocommerce_recently_viewed', $post->ID );
}
}
add_action( 'wp', 'set_user_visited_product_cookie' );
Below code to set cookie 'woocommerce_recently_viewed' worked for me. Hope it helps other
$Existing_product_id = $_COOKIE['woocommerce_recently_viewed'];
if ( is_product() )
{
$updated_product_id = $Existing_product_id.'|'.$post->ID;
wc_setcookie( 'woocommerce_recently_viewed', $updated_product_id );
}
So i make custom form-tag in contact form 7! It is a drop down with list of my courses and now I want to make it required because that is the main thing in whole form.
So can someone give me a tip how to do that?
When I do the [myCustomField* course-name class:custom-field]
It does not working with *
So if someone can help it will be great!
I have been working on this myself this afternoon and I do not think Mahmoud has added everything that is needed to get the validation working well and the messages showing up.
using what I have learnt from the posts on contact form 7 here:
https://contactform7.com/2015/01/10/adding-a-custom-form-tag
https://contactform7.com/2015/02/27/using-values-from-a-form-tag/
and looking at this file in the plugin: contact-form-7/modules/select.php which helped a lot.
I think this will work better and needs to be added to your functions.php file in your child-theme.
add_action( 'wpcf7_init', 'custom_add_form_tag_myCustomField' );
function custom_add_form_tag_myCustomField() {
wpcf7_add_form_tag( array( 'myCustomField', 'myCustomField*' ),
'custom_myCustomField_form_tag_handler', true );
}
function custom_myCustomField_form_tag_handler( $tag ) {
$tag = new WPCF7_FormTag( $tag );
if ( empty( $tag->name ) ) {
return '';
}
$validation_error = wpcf7_get_validation_error( $tag->name );
$class = wpcf7_form_controls_class( $tag->type );
if ( $validation_error ) {
$class .= ' wpcf7-not-valid';
}
$atts = array();
$atts['class'] = $tag->get_class_option( $class );
$atts['id'] = $tag->get_id_option();
if ( $tag->is_required() ) {
$atts['aria-required'] = 'true';
}
$atts['aria-invalid'] = $validation_error ? 'true' : 'false';
$atts['name'] = $tag->name;
$atts = wpcf7_format_atts( $atts );
$myCustomField = '';
$query = new WP_Query(array(
'post_type' => 'CUSTOM POST TYPE HERE',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
));
while ($query->have_posts()) {
$query->the_post();
$post_title = get_the_title();
$myCustomField .= sprintf( '<option value="%1$s">%1$s</option>',
esc_html( $post_title ) );
}
wp_reset_query();
$myCustomField = sprintf(
'<span class="wpcf7-form-control-wrap %1$s"><select %2$s>%3$s</select>%4$s</span>',
sanitize_html_class( $tag->name ),
$atts,
$myCustomField,
$validation_error
);
return $myCustomField;
}
That is how we create the custom tag. The important differences here are the addition of the $validation_error variables as wells the aria-required and aria-invalid data. It is also important to include the $validation_error in the final output so that we can see the validation messages being created.
Then to finish it off we need to add some validation via filters.
There is no documentation on this yet, but I used the functions from the select.php and altered them to what I needed.
/* Validation filter */
add_filter( 'wpcf7_validate_myCustomField', 'wpcf7_myCustomField_validation_filter', 10, 2 );
add_filter( 'wpcf7_validate_myCustomField*', 'wpcf7_myCustomField_validation_filter', 10, 2 );
function wpcf7_myCustomField_validation_filter( $result, $tag ) {
$tag = new WPCF7_FormTag( $tag );
$name = $tag->name;
if ( isset( $_POST[$name] ) && is_array( $_POST[$name] ) ) {
foreach ( $_POST[$name] as $key => $value ) {
if ( '' === $value ) {
unset( $_POST[$name][$key] );
}
}
}
$empty = ! isset( $_POST[$name] ) || empty( $_POST[$name] ) && '0' !== $_POST[$name];
if ( $tag->is_required() && $empty ) {
$result->invalidate( $tag, wpcf7_get_message( 'invalid_required' ) );
}
return $result;
}
This code should also go in your functions.php file just under the code for the custom CF7 tag.
Here the filter's first string $tag should match with the class that is being generated in the custom CF7 tag so if your custom tag->type = 'myCustomField' then the $tag of the filter must include the name, like so wpcf7_validate_myCustomField as well as the required version of it, wpcf7_validate_myCustomField*.
I hope that helps anyone else looking for this.
If you want even more of the options available from the backend of Contact Form 7 check the select.php file as it lays it out quite nicely on how to get each option and include it.
You can use [select*] to output a required drop-down menu.
[select* course-name include_blank "English" "Math"]
Check https://contactform7.com/checkboxes-radio-buttons-and-menus/
EDIT:
So you have your own shortcode [myCustomField]. To make two versions of your shortcode as [myCustomField] and [myCustomField*] you have to pass both shortcodes to your function as the following:
add_action( 'wpcf7_init', 'wpcf7_add_form_tag_mycustomfield' );
function wpcf7_add_form_tag_mycustomfield() {
wpcf7_add_form_tag( array( 'myCustomField', 'myCustomField*'),
'wpcf7_mycustomfield_form_tag_handler', array( 'name-attr' => true ) );
}
function wpcf7_mycustomfield_form_tag_handler( $tag ) {
$tag = new WPCF7_FormTag( $tag );
if ( empty( $tag->name ) ) {
return '';
}
$atts = array();
$class = wpcf7_form_controls_class( $tag->type );
$atts['class'] = $tag->get_class_option( $class );
$atts['id'] = $tag->get_id_option();
$atts['name'] = $tag->name;
$atts = wpcf7_format_atts( $atts );
$html = sprintf( '<your-tag %s></your-tag>', $atts );
return $html;
}
Then, you can use it:
[myCustomField course-name class:custom-field]
or
[myCustomField* course-name class:custom-field]
References:
https://contactform7.com/2015/01/10/adding-a-custom-form-tag
https://contactform7.com/2015/02/27/using-values-from-a-form-tag/
I use specific thank you pages depend of product
I want to display the last purchase (product's name) of the current user on this page and fetch cross sell of the specific product too.
Any help would be apreciate
OF course i made some research and found tutorial like this one
https://www.skyverge.com/blog/get-all-woocommerce-orders-for-a-customer/#comment-703884
But i am not enaugh good at php to modify it for my needs
Thank you
If you want to display 50 last purchased product for example, go to function.php in Appearance>Editor>function.php then past the code below:
// Recently Sold Products shortcode for WooCommerce
add_shortcode('sold_products', 'sold_products');
function sold_products($attributes) {
global $wpdb, $woocommerce;
$defaults = array('max_products' => 50, 'title' => "Last purchased");
$parameters = shortcode_atts( $defaults, $attributes);
$max = absint($parameters['max_products']); // number of products to show
$title = sanitize_text_field($parameters['title']);
$html = '<div class="sold_products">'.PHP_EOL;
if ($title) {
$html .= '<h3>Last purchased:</h3>'.PHP_EOL;
}
$table = $wpdb->prefix.'woocommerce_order_items';
$my_query = $wpdb->prepare("SELECT * FROM $table WHERE `order_item_type`='line_item' ORDER BY `order_id` DESC LIMIT %d", $max);
$nr_rows = $wpdb->query($my_query);
if (!$nr_rows) {
$html .= '<p>Aucun produit n\'est acheté encore!</p>';
} else {
$html .= '<ul>'.PHP_EOL;
for ($offset = 0; $offset < $nr_rows; $offset++) {
if ( is_user_logged_in() ) {
$user_info = wp_get_current_user();
}
$row = $wpdb->get_row($my_query, OBJECT, $offset);
$product_name = $row->order_item_name;
$product = get_page_by_title($product_name, OBJECT, 'product');
$url = get_permalink($product->ID);
$order_id = $row->order_id;
$order = new WC_Order($order_id);
$user = $order->get_user();
if ($user) {
$user_id = $user->ID;
} else {
$user_id = 'Guest';
}
if($user_info->ID == $user_id){
$unix_date = strtotime($order->order_date);
$date = date('d/m/y', $unix_date);
$html .= '<li>'.$product_name.' acheté le '.$date.'</li>'.PHP_EOL;
}
}
$html .= '</ul>'.PHP_EOL;
}
$html .= '</div>'.PHP_EOL;
return $html;
} // end function
you can dislay this result anywhere in your pages just past this shotcode where you want it to be displayed:
[sold_products max_products="50" title="Last purchased"]