Viewed   183 times

Hello I am fairly new to woocommerce, My shop has four categories of products which use same single-product template. I want to add a fifth category of products where the product page layout is very different from already the one used.

Here's the file structure -

  • theme/woocommerce/single-product/
  • theme/woocommerce/single-product-mock/
  • theme/woocommerce/single-product-mock/title.php
  • theme/woocommerce/content-single-product-mock.php
  • theme/woocommerce/single-product.php

I created a file called content-single-product-mock.php. In single-product.php using the following code

        <?php if (has_term( 'mock', 'product_cat' )) {
            woocommerce_get_template_part( 'content', 'single-product-mock' );
        } else{
         wc_get_template_part( 'content', 'single-product' ); 
        } ?>

For the category mock it redirects to content-single-product-mock.php, but it uses the template files in single-product folder. How do change the template path in content-single-product-mock.php so that it uses the customized files in single-product-mock folder?

 Answers

3

There is probably more than one way to do this, but they all kind of turn around the idea of filtering the template before it is included.

You could totally skip WooCommerce's simple-product.php template (without needing to override that template) and go directly to simple-product-mock.php and create everything there. You'd do that by filtering template_include.

add_filter( 'template_include', 'so_25789472_template_include' );

function so_25789472_template_include( $template ) {
  if ( is_singular('product') && (has_term( 'mock', 'product_cat')) ) {
    $template = get_stylesheet_directory() . '/woocommerce/single-product-mock.php';
  } 
  return $template;
}

You could edit single-product-mock.php to call content-single-product-mock.php and hard-code that file. Nothing requires you to keep using Woo's hooks and functions. The point of them is just to make it easy for you to customize.

Or to be really tricky, you could duplicate templates such as single-product/title.php into a single-product-mock folder... ex: single-product-mock/title.php and then any time we're on a single product template in the mock category, we'll intercept calls to the single-product/something.php template and redirect them to single-product-mock/something.php if it exists and keep pointing to the single-product/something.php if it does not. We'll do this via the woocommerce_locate_template filter.

add_filter( 'woocommerce_locate_template', 'so_25789472_locate_template', 10, 3 );

function so_25789472_locate_template( $template, $template_name, $template_path ){

    // on single posts with mock category and only for single-product/something.php templates
    if( is_product() && has_term( 'mock', 'product_cat' ) && strpos( $template_name, 'single-product/') !== false ){

        // replace single-product with single-product-mock in template name
        $mock_template_name = str_replace("single-product/", "single-product-mock/", $template_name );

        // look for templates in the single-product-mock/ folder
        $mock_template = locate_template(
            array(
                trailingslashit( $template_path ) . $mock_template_name,
                $mock_template_name
            )
        );

        // if found, replace template with that in the single-product-mock/ folder
        if ( $mock_template ) {
            $template = $mock_template;
        }
    }

    return $template;
}

Update to filter wc_get_template instead.

/**
 * Change wc template part for product with a specific category
 *
 * @param  string $templates
 * @param  string $slug
 * @param  string $name
 * @return string
 */
function so_25789472_get_template_part( $template, $slug, $name ) {
  if ( $slug == 'content' && $name = 'single-product' && has_term( 'test', 'product_cat' ) ) {
    $template = locate_template( array( WC()->template_path() . 'content-single-product-test.php' ) );
  } 
  return $template;
}
add_filter( 'wc_get_template_part', 'so_25789472_get_template_part', 10, 3 );
Tuesday, December 27, 2022
5

Update: Is not possible to detect custom cart item data on add to cart event.

Checking cart items will allow you to prevent having cart items that have $cart_item['default-engraving'] and $cart_item['iconic-engraving'] at the same time:

add_action( 'woocommerce_check_cart_items', 'check_cart_items_custom_data' );
function check_cart_items_custom_data() {
    // Initializing: set the current product type in an array
    $types = [];

    // Loop through cart items
    foreach (WC()->cart->get_cart() as $item ){
        if( isset( $item['default-engraving'] ) )
            $types[] = 'default';

        if( isset( $item['iconic-engraving'] ) )
            $types[] = 'iconic';
    }

    $types = array_unique( $types );

    // Check the number of product types allowing only one
    if( count( $types ) > 1 ){

        // Displaying a custom notice and avoid checkout
        wc_add_notice( __('Only items from one product type are allowed in cart'), 'error' );
    }
}

Code goes in functions.php file of the active child theme (or active theme). Tested and works.


Original answer: (it can't work for your case as it's not possible to detect that on add to cart event)

Here is the way targeting the product type to allow only one in cart:

add_filter( 'woocommerce_add_to_cart_validation', 'only_one_product_type_allowed', 10, 3 );
function only_one_product_type_allowed( $passed, $product_id, $quantity ) {

    // Initializing: set the current product type in an array
    $types = [ wc_get_product( $product_id )->get_type() ];

    // Loop through cart items
    foreach (WC()->cart->get_cart() as $item ){
        // Set each product type in the array
        $types[] = wc_get_product( $item['product_id'] )->get_type();
    }

    $types = array_unique( $types );

    // Check the number of product types allowing only one
    if( count( $types ) > 1 ){

        // Displaying a custom notice
        wc_add_notice( __('Only items from one product type are allowed in cart'), 'error' );
        return false; // Avoid add to cart
    }

    return $passed;
}

Code goes in functions.php file of the active child theme (or active theme). Tested and works.

Related: Allow only one product category in cart at once in Woocommerce

Saturday, August 6, 2022
 
uwe
 
uwe
5

Well I found the solve here
https://developer.wordpress.org/reference/functions/get_the_terms/

and here's the code :

$test = $_product->get_attributes();
if(!empty($test)) {
    $terms = get_the_terms($product_id, "pa_size");
    foreach ( $terms as $term ) {
    echo "<option>" . $term->name . "</option>";
    }
} else {
    echo "Not Specified";
}
Wednesday, August 31, 2022
 
3

The action(s) you want are these:

add_action('woocommerce_update_product', 'productPublished');

add_action('woocommerce_new_product', 'productPublished');

function productPublished($product_id){
    //...
}

You can find them both (where they are triggered from) in the Woo source code here:

https://docs.woocommerce.com/wc-apidocs/source-class-WC_Product_Data_Store_CPT.html#237

I actually looked them up backwards by first finding where the source code for saving products was, and then looked for hooks in those methods (create/update).

 //found on Line 134 method create
 do_action( 'woocommerce_new_product', $id );


 //found on Line 237 method update
 do_action( 'woocommerce_update_product', $product->get_id() );

You'll also have to change this line:

function productPublished ($ID , $post , $update){
    $product = wc_get_product( $post->ID);
}

To

function productPublished($product_id){
    $product = wc_get_product( $product_id);
   //....
}

I don't think the other arguments (that are missing) matter to your code. Such as if it's an update or a new product, I also don't see $post used except to get the product ID, which we already have.

UPDATE (determine args for callback)

If you are not sure about the callback's arguments, you can look in the source code (as I did above) Or if you can find it in documentation Or as a last resort you can simply output them. The best way to output them is one of these 3

  • func_get_args() - "Returns an array comprising a function's argument list" http://php.net/manual/en/function.func-get-args.php
  • debug_print_backtrace() - "Prints a backtrace (similar to stacktrace)" https://secure.php.net/manual/en/function.debug-print-backtrace.php
  • Exception::getTraceAsString() try/catch and throw an exception to output the stacktrace http://php.net/manual/en/exception.gettraceasstring.php

Here is an example I built with a minimally/simplified working hook system modeled after WordPress's. For testing reasons and because it's really not that hard to build when you know how it works:

//global storage (functions, not classes)
global $actions;
$actions = [];

//substitute wordpress add_action function (for testing only) 
function add_action($action, $callback, $priorty=10, $num_args=1){
    global $actions;
    $actions[$action][] = [
         'exec' => $callback,
         'priorty'=>$priorty,
         'num_args' => $num_args
    ];

}
//substitute wordpress do_action function (for testing only) 
function do_action($action, ...$args){
    // PHP5.6+ variadic (...$args) wraps all following arguments in an array inside $args (sort of the opposite of func_get_args)
    global $actions;

    if(empty($actions[$action])) return;
    //sort by priory
    usort($actions[$action], function($a,$b){
       //PHP7+ "spaceship" comparison operator (<=>)
       return $b['priorty']<=>$a['priorty'];
    });

    foreach($actions[$action] as $settings){
        //reduce the argument list
        call_user_func_array($settings['exec'], array_slice($args, 0, $settings['num_args']));
    }
}

//test callback
function callback1(){
     echo "nn".__FUNCTION__."n";
    print_r(func_get_args());
}

//test callback
function callback2(){
    echo "nn".__FUNCTION__."n";
    try{
        //throw an empty exception
        throw new Exception;
    }catch(Exception $e){
         //pre tags preserve whitespace (white-space : pre)
        echo "<pre>";
        //output the stacktrace of the callback
        echo $e->getTraceAsString();
        echo "nn</pre>";
   }
}

//Register Hook callbacks, added in opposite order, sorted by priority
add_action('someaction', 'callback2', 5, 4);
add_action('someaction', 'callback1', 1, 5);

//execute hook
do_action('someaction', 1234, 'foo', ['one'=>1], new stdClass, false);

Output:

callback2
<pre>#0 [...][...](25): callback2(1234, 'foo', Array, Object(stdClass))
#1 [...][...](52): do_action('someaction', 1234, 'foo', Array, Object(stdClass), false)
#2 {main}

</pre>

callback1
Array
(
    [0] => 1234
    [1] => foo
    [2] => Array
        (
            [one] => 1
        )

    [3] => stdClass Object
        (
        )
    [4] =>
)

Sandbox

Stacktrace As you can see in the first output, we have a complete stacktrace of the application (minus the redacted info), including the function calls and the arguments used for those calls. Also note in this example, I registered it 2nd but the priority (set in add_action) made it execute first. Lastly, only 4 of the 5 arguments were used (also do to how add_action was called).

So do_action was called like this (with 'action' and 5 other args):

 do_action('someaction', 1234, 'foo', Array, Object(stdClass), false)

And the actual callback was called like this (without 'action' and only 4 other args):

 callback2(1234, 'foo', Array, Object(stdClass))

Function Arguments This is a bit more strait forward, but it doesn't give you the original call so you won't know the maximum number of args (which you can get from the call to do_action in the stacktrace). But if you just want a quick peek at the incoming data, it's perfect. Also I should mention this one uses all 5 args, which you can clearly see in the array for the second output. The [4] => is false, this is typically how print_r displays false (as just empty).

Debug Backtrace Unfortunately, debug_print_backtrace is disabled (for security reasons) in the sandbox, and Exception stacktrace is heavily redacted (typically it has file names and lines where functions are called from and located at) also for security reasons. Both of these can return arguments from things like connecting to the Database, which would contain the DB password in plain text, just for example. Anyway, debug_print_backtrace is pretty close to what an exception stack trace looks like anyway.

Summery

But in any case, this should give you an idea of what the data looks like. We can use functions like this (and Reflection) to interrogate the application at run time. I am sure there are other/more ways to do similar things too.

PS. It should go without saying, but I will say it anyway, these methods above work with any PHP function, and can be quite useful. Also as noted above, you should never show stacktraces on a live production machine.

Anyway, good luck.

Tuesday, August 30, 2022
 
1

If you want to completely disable the SKU feature then you have to use wc_product_sku_enabled filter. It will remove the SKU field from both backend and frontend.

add_filter( 'wc_product_sku_enabled', '__return_false' );

If you want to keep the SKU feature but need to disable unique SKU check then you have to use wc_product_has_unique_sku filter. It will keep the SKU field in both backend and frontend, but will allow you to add multiple duplicate SKU.

add_filter( 'wc_product_has_unique_sku', '__return_false' ); 

Code goes in function.php file of your active child theme (or theme). Or also in any plugin php files.
Hope this helps!

Tuesday, September 13, 2022
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share