Asked  1 Year ago    Answers:  5   Viewed   7 times

I'm using WooCommerce to build a webshop.

The determined format for forms is that there's no labels, only placeholders. I've been removing the labels like so:

<?php

// WooCommerce Checkout Fields Hook
add_filter( 'woocommerce_checkout_fields' , 'custom_wc_checkout_fields' );

// Change the format of fields with type, label, placeholder, class, required, clear, label_class, options
function custom_wc_checkout_fields( $fields ) {

//BILLING
$fields['billing']['billing_first_name']['label'] = false;

return $fields;
}
?>

But since I want no labels anywhere, I was wondering if there's a way to remove all of them at once. Instead of going through them all individually. Does anybody have an idea?

Thanks!


EDIT:

I realize this is possible by just adding some css(display none), but since this is not a very clean solution, I was wondering if there is some other way to accomplish this.

 Answers

2

You can remove any $field->property with unset.
Good reading and references can be found here: Customizing checkout fields using actions and filters

Now, for your question in how to do it globally, you can use a loop, something like:

// WooCommerce Checkout Fields Hook
add_filter('woocommerce_checkout_fields','custom_wc_checkout_fields_no_label');

// Our hooked in function - $fields is passed via the filter!
// Action: remove label from $fields
function custom_wc_checkout_fields_no_label($fields) {
    // loop by category
    foreach ($fields as $category => $value) {
        // loop by fields
        foreach ($fields[$category] as $field => $property) {
            // remove label property
            unset($fields[$category][$field]['label']);
        }
    }
     return $fields;
}
  • Online example: http://codepad.org/drvBYYS8
  • Related with good advise in acepted answer: WooCommerce Change Form Labels and Remove Fields
Thursday, April 1, 2021
 
Baba
 
2

Hooked functions will never override other hooked functions that are using the same action or filter hook.

They are added to a kind of "hook queue" with an execution order based on priority rules:

  • If a priority is specified, they will be ordered in the queue first by hook priority and by declaration priority.
  • If there is no priority specified, they take the default priority of 10 and they will be ordered in the queue by declaration.

So you can have many hooked functions on the same hook, like for example in the Woocommerce template file content-single-product.php

Illustrated example:

In the below commented code example, you can see the execution order in the hook queue for each hooked function for the woocommerce_thankyou action hook:

// No defined priority (default priority is 10)
add_action( 'woocommerce_thankyou', 'first_custom_function_no_priority' );
function first_custom_function_no_priority( $order_id ) {
    // ==> Triggered in third position ==> [3]
}

## Default Hook "woocommerce_order_details_table" (default priority is 10)
    // ==> Triggered in second position ==> [2]

// Defined priority is 10
add_action( 'woocommerce_thankyou', 'order_created_get_skus', 10 );
function order_created_get_skus( $order_id ) {
    // ==> Triggered in Fourth position ==> [4] 
}

// Defined priority is 5
add_action( 'woocommerce_thankyou', 'third_custom_function', 5 );
function third_custom_function( $order_id ) {
    // ==> Triggered in first position ==> [1]
}

// Defined priority is 20
add_action( 'woocommerce_thankyou', 'fourth_custom_function', 20 );
function fourth_custom_function( $order_id ) {
    // ==> Triggered at last (sixth) ==> [6]
}

// No defined priority (default priority is 10)
add_action( 'woocommerce_thankyou', 'last_custom_function_no_priority' );
function last_custom_function_no_priority( $order_id ) {
    // ==> Triggered in fifth position ==> [5]
}

Lower priority is executed (or triggered) before, higher priority is executed (or triggered) after. If no priority is specified, the default priority is 10.

The hooked functions can only be removed with remove_action() or remove_filter() with a mandatory defined priority.

To see how many hooked functions are hooked on a specific hook with all necessary details, you can use the following that will give you a raw output:

global $wp_filter;

// HERE below you define the targeted hook name
$hook_name = 'woocommerce_widget_shopping_cart_buttons';

if( isset($wp_filter[$hook_name]) ) {
    echo '<pre>';
    print_r($wp_filter[$hook_name]);
    echo '</pre>';
} else {
    echo '<p>Hook "'.$hook_name.'" is not used yet!</p>';
}

There is 2 kind of hooks, as you have noticed may be, which are filter hooks and action hooks.

  1. Action hook:

    • Action hook execution point (trigger): with do_action()
    • Attaching a function to an action hook (triggered): with add_action(): the function is executed and can have optional arguments.
  2. Filter hook:

    • Filter hook execution point (trigger): with apply_filters()
    • Attaching a function to a filter hook (filtering / triggered): with add_filter(): a mandatory argument (a variable) is filtered and returned from the "hooked" function

Hooks and their hooked functions can be located anywhere like in the function.php file of your active child theme (or active theme) and also in any plugins php files.


Related:

  • WooCommerce action hooks and overriding templates
  • How to add custom hooks to a custom plugin for Woocommerce
  • WordPress: Difference Between Filter and Action Hooks?
Thursday, April 1, 2021
 
4

Try inserting this line of code into the template file where you want the form to show up:

wc_get_template( 'myaccount/form-lost-password.php', array( 'form' => 'lost_password' ) );
Thursday, April 1, 2021
 
4

The following code will allow adding to cart only items from one product category avoiding add to cart and displaying a custom notice:

add_filter( 'woocommerce_add_to_cart_validation', 'only_one_product_category_allowed', 20, 3 );
function only_one_product_category_allowed( $passed, $product_id, $quantity) {

    // Getting the product categories term slugs in an array for the current product
    $term_slugs   = wp_get_post_terms( $product_id, 'product_cat', array('fields' => 'slugs') );

    // Loop through cart items
    foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item ){

        // Check if the product category of the current product don't match with a cart item
        if( ! has_term( $term_slugs, 'product_cat', $cart_item['product_id'] ) ){

            // Displaying a custom notice
            wc_add_notice( __('Only items from one product category are allowed in cart'), 'error' );

            // Avoid add to cart
            return false; // exit
        }
    }
    return $passed;
}

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


Addition (updated) - The same but only for parent product categories:

add_filter( 'woocommerce_add_to_cart_validation', 'only_one_product_category_allowed', 20, 3 );
function only_one_product_category_allowed( $passed, $product_id, $quantity) {
    $parent_term_ids = $item_parent_term_ids = array(); // Initializing

    // Loop through the current product category terms to get only parent main category term
    foreach( get_the_terms( $product_id, 'product_cat' ) as $term ){
        if( $term->parent > 0 ){
            $parent_term_ids[] = $term->parent; // Set the parent product category
        }
    }

    // Loop through cart items
    foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item ){
        // Loop through the cart item product category terms to get only parent main category term
        foreach( get_the_terms( $cart_item['product_id'], 'product_cat' ) as $term ){
            if( $term->parent > 0 ){
                $item_parent_term_ids[] = $term->parent; // Set the parent product category
            }
        }

        // Check if parent product categories don't match
        if( ! array_intersect( $parent_term_ids, $item_parent_term_ids ) ){

            // Displaying a custom notice
            wc_add_notice( __('Only items from one product category are allowed in cart'), 'error' );

            // Avoid add to cart
            return false; // exit
        }
    }
    return $passed;
}

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

Saturday, May 29, 2021
 
Besnik
 
2

Markus, here is a solution that I use that seems to work well, hopefully it will be suitable for you.

First, in order to render the form with no <dl> tag, we need to set the decorators on form object itself. From inside a class extending Zend_Form, you would call Zend_Form->setDecorators() passing an array of form decorators.

From the reference guide:

The default decorators for Zend_Form are FormElements, HtmlTag (wraps in a definition list), and Form; the equivalent code for creating them is as follows:

  $form->setDecorators(array(
      'FormElements',
      array('HtmlTag', array('tag' => 'dl')),
      'Form'
  ));

To wrap the form in something other than a dl, we use the above decorators but change the dl to whatever tag you use, I typically use a div of class form which we will see later.

Next, the elements need to be dealt with. Zend_Form elements have different decorators for different types of elements. The following groups of element types each have their own distinct set of decorators: [Submit & Button], [Captcha], [File], [Image], and [Radio*]. The decorator for radio is very similar to standard elements except that it does not specify the for attribute inside the label.

All other form elements, text, password, select, checkbox, etc use the same set of default decorators.

To remove the dd/dt tags from an individual form element we would need to apply our own set of decorators to it. Here is an example that does not use dd/dt tags:

array(
    'ViewHelper',
    'Errors',
    array('Description', array('tag' => 'p', 'class' => 'description')),
    array('HtmlTag',     array('class' => 'form-div')),
    array('Label',       array('class' => 'form-label'))
);

This will wrap each form element in a div tag with the class form-div. The problem is, you have to apply this set of decorators to EVERY element that you don't want to be wrapped in the dd/dt tags which can be a bit problematic.

To solve this issue, I create a class that extends from Zend_Form and give it some default behavior and decorators that are different from the default decorators for Zend_Form.

While we can't quite have Zend_Form automatically assign the correct decorators to specific element types (you can assign them to specific element names), we can set a default, and give ourselves easy access to the decorators from one place, so if they need to change, they can be easily changed for all forms.

Here is the base class:

<?php

class Application_Form_Base extends Zend_Form
{
    /** @var array Decorators to use for standard form elements */
    // these will be applied to our text, password, select, checkbox and radio elements by default
    public $elementDecorators = array(
        'ViewHelper',
        'Errors',
        array('Description', array('tag' => 'p', 'class' => 'description')),
        array('HtmlTag',     array('class' => 'form-div')),
        array('Label',       array('class' => 'form-label', 'requiredSuffix' => '*'))
    );

    /** @var array Decorators for File input elements */
    // these will be used for file elements
    public $fileDecorators = array(
        'File',
        'Errors',
        array('Description', array('tag' => 'p', 'class' => 'description')),
        array('HtmlTag',     array('class' => 'form-div')),
        array('Label',       array('class' => 'form-label', 'requiredSuffix' => '*'))
    );

    /** @var array Decorator to use for standard for elements except do not wrap in HtmlTag */
    // this array gets set up in the constructor 
    // this can be used if you do not want an element wrapped in a div tag at all
    public $elementDecoratorsNoTag = array();

    /** @var array Decorators for button and submit elements */
    // decorators that will be used for submit and button elements
    public $buttonDecorators = array(
        'ViewHelper',
        array('HtmlTag', array('tag' => 'div', 'class' => 'form-button'))
    );


    public function __construct()
    {
        // first set up the $elementDecoratorsNoTag decorator, this is a copy of our regular element decorators, but do not get wrapped in a div tag
        foreach($this->elementDecorators as $decorator) {
            if (is_array($decorator) && $decorator[0] == 'HtmlTag') {
                continue; // skip copying this value to the decorator
            }
            $this->elementDecoratorsNoTag[] = $decorator;
        }

        // set the decorator for the form itself, this wraps the <form> elements in a div tag instead of a dl tag 
        $this->setDecorators(array(
                             'FormElements',
                             array('HtmlTag', array('tag' => 'div', 'class' => 'form')),
                             'Form'));

        // set the default decorators to our element decorators, any elements added to the form
        // will use these decorators
        $this->setElementDecorators($this->elementDecorators);

        parent::__construct();
        // parent::__construct must be called last because it calls $form->init()
        // and anything after it is not executed
    }
}

/*
   Zend_Form_Element default decorators:
   $this->addDecorator('ViewHelper')
        ->addDecorator('Errors')
        ->addDecorator('Description', array('tag' => 'p', 'class' => 'description'))
        ->addDecorator('HtmlTag', array('tag' => 'dd',
                                        'id'  => array('callback' => $getId)))
        ->addDecorator('Label', array('tag' => 'dt'));
*/

Now to use the class, extend all of your forms from this base class and go about assigning elements as usual. If you use Zend_Form_Element_XXX as opposed to addElement() then you will need to pass one of the decorators as an option to the element constructor, if you use Zend_Form->addElement, then it will use the default decorator $elementDecorators we assigned in the class.

Here is an example that shows how to extend from that class:

<?php

class Application_Form_Test extends Application_Form_Base
{
    public function init()
    {
        // Add a text element, this will automatically use Application_Form_Base->elementDecorators for its decorators
        $this->addElement('text', 'username', array(
            'label'      => 'User Name:',
            'required'   => false,
            'filters'    => array('StringTrim'),
        ));

        // This will not use the correct decorators unless we specify them directly
        $text2 = new Zend_Form_Element_Text(
            'text2',
            array(
                'decorators' => $this->elementDecorators, // must give the right decorator
                'label' => 'Text 2'
            )
        );

        $this->addElement($text2);

        // add another element, this also uses $elementDecorators
        $this->addElement('text', 'email', array(
            'label'      => 'Email:', 
            'required'   => false,
            'filters'    => array('StringTrim', 'StringToLower'), 
        ));

        // add a submit button, we don't want to use $elementDecorators, so pass the button decorators instead
        $this->addElement('submit', 'submit', array(
            'label' => 'Continue', 
            'decorators' => $this->buttonDecorators // specify the button decorators
        ));
    }
}

This shows a pretty effective way to get rid of the dd/dt and dl elements and replace them with your own. It is a bit inconvenient to have to specify the decorators for every element, as opposed to being able to assign decorators to specific elements, but this seems to work well.

To add one more solution that I think you were looking to do, if you would like to render an element with no label, simply create a new decorator and omit the label decorator from it like this:

$elementDecorators = array(
    'ViewHelper',
    'Errors',
    array('Description', array('tag' => 'p', 'class' => 'description')),
    array('HtmlTag',     array('class' => 'form-div')),
    // array('Label',       array('class' => 'form-label', 'requiredSuffix' => '*'))
    // comment out or remove the Label decorator from the element in question
    // you can do the same for any of the decorators if you don't want them rendered
);

Feel free to ask for clarification on anything, hopefully this will help you out.

Tuesday, July 20, 2021
 
Sauleil
 
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :