Code Snippets Archive - Mark Wilkinson https://markwilkinson.dev/code-snippets/ WordPress developer Fri, 10 Jan 2025 13:17:02 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://markwilkinson.dev/wp-content/uploads/2023/05/IMG_0013-edited-150x150.jpeg Code Snippets Archive - Mark Wilkinson https://markwilkinson.dev/code-snippets/ 32 32 Setting a max quantity for a WooCommerce product in the basket https://markwilkinson.dev/code-snippets/setting-a-max-quantity-for-a-woocommerce-product-in-the-basket/ Fri, 10 Jan 2025 13:16:27 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7350 I recently came across a client requirement to only allow up to a maximum number of a specific item in the WooCommerece basket. This was primarily to prevent a single customer from order the entire stock of a specific item. The requirements were as follows: I therefore went about building a plugin to handle this. […]

The post Setting a max quantity for a WooCommerce product in the basket appeared first on Mark Wilkinson.

]]>
I recently came across a client requirement to only allow up to a maximum number of a specific item in the WooCommerece basket.

This was primarily to prevent a single customer from order the entire stock of a specific item.

The requirements were as follows:

  • Set a max quantity value for a product which is the maximum quantity amount which can be ordered for the product
  • Set validation rules that prevent adding more than the max quantity to the basket
  • Create validation rules that prevent something updating the item with a higher quantity value than the max quantity value when updating an existing item in the basket
  • Show appropriate messages to the user about the rules regarding max number of items allowed.

I therefore went about building a plugin to handle this.

Add max quantity input field under WooCommerce product inventory

WooCommerce has an inventory section in the product data section on the product edit screen.

I used the following code to add a new _max_quantity_in_cart field to that area.

<?php
/**
 * Adds a max quanity field to the inventory section on the post edit screen.
 */
function wcmqic_add_max_quantity_field() {

	
	// Add a custom text field
	woocommerce_wp_text_input(
		[
			'id'          => '_max_quantity_in_cart',
			'label'       => __( 'Maximum Quantity in Basket', 'wc-max-quantity-in-cart' ),
			'description' => __( 'Set the maximum quantity of this product which can be added to the basket in a single transaction.', 'wc-max-quantity-in-cart' ),
			'type'        => 'number',
			'desc_tip'    => true,
			'custom_attributes' => array(
				'min' => '1', // Minimum value is 1
			),
		]
	);
}

add_action( 'woocommerce_product_options_inventory_product_data', 'wcmqic_add_max_quantity_field' );

Save the max quantity value when the product post is saved

The following code then saves the added quantity value when the product post is saved.

<?php
/**
 * Save the max quantity custom field value.
 */
function wcmqic_save_max_quantity_field( $post_id ) {
	
	// get the max quantity value from the posted data.
	$max_quantity = isset( $_POST['_max_quantity_in_cart'] ) ? sanitize_text_field( $_POST['_max_quantity_in_cart'] ) : '';

	// if we have a max quantity value.
	if ( ! empty( $max_quantity ) ) {

		// save the value as post meta data.
		update_post_meta( $post_id, '_max_quantity_in_cart', $max_quantity );

	} else {

		// delete the post meta data if empty.
		delete_post_meta( $post_id, '_max_quantity_in_cart' );
	}
}

// Save the custom field value
add_action( 'woocommerce_process_product_meta', 'wcmqic_save_max_quantity_field' );

I then used the following code to create validation when someone adds the product to their basket. The code checks whether the quantity selected is higher than the max quantity set, and if so, it prevents the item from being added to the basket and shows the user an error message.

Validate when a product is added to the basket, ensuring it is equal or less to the max quantity value

<?php
/**
 * Limits the number of a product that can be added to the cart.
 * When the product has a max quantity.
 */
function wcmqic_limit_product_quantity_in_cart( $passed, $product_id, $quantity ) {
    
	// get the max quantity from the product.
	$max_quantity = get_post_meta( $product_id, '_max_quantity_in_cart', true );

	//if we don't have a max quantity.
	if ( empty( $max_quantity ) ) {
		return $passed;
	}

    // Get the existing quantity of the product in the cart
    $existing_quantity = 0;
    foreach ( WC()->cart->get_cart() as $cart_item ) {
        if ( $cart_item['product_id'] == $product_id ) {
            $existing_quantity = $cart_item['quantity'];
            break;
        }
    }

    // Check if the new quantity exceeds the maximum allowed
    if ( ( $existing_quantity + $quantity ) > $max_quantity ) {
        wc_add_notice(
            sprintf(
                'You can only add up to %d of this product to your cart.',
                $max_quantity
            ),
            'error'
        );
        return false;
    }

    return $passed;
}

add_filter( 'woocommerce_add_to_cart_validation', 'wcmqic_limit_product_quantity_in_cart', 10, 3 );

Finally, the following code does the same thing but this time when a user updates an item in the their current basket.

Validate when the user updates their basket

<?php
/**
 * Limits the number of a product that can be updated in the cart.
 */
function wcmqic_limit_product_quantity_in_cart_update( $passed, $cart_item_key, $values, $quantity ) {

	// get the max quantity from the product.
	$max_quantity = get_post_meta( $values['product_id'], '_max_quantity_in_cart', true );

	//if we don't have a max quantity.
	if ( empty( $max_quantity ) ) {
		return $passed;
	}

    if ( $quantity > $max_quantity ) {
        wc_add_notice(
            sprintf(
                'You can only have a maximum of %d of this product in your basket.',
                $max_quantity
            ),
            'error'
        );
        return false;
    }

    return $passed;
}

add_filter( 'woocommerce_update_cart_validation', 'wcmqic_limit_product_quantity_in_cart_update', 10, 4 );

And there you have it, a plugin that sets a maximum quantity in basket for WooCommerce products.

You can view the entire code on Github.

The post Setting a max quantity for a WooCommerce product in the basket appeared first on Mark Wilkinson.

]]>
Enqueue stylesheet for any WordPress block https://markwilkinson.dev/code-snippets/enqueue-stylesheet-for-any-wordpress-block/ Thu, 31 Oct 2024 08:58:32 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7348 Placing the function below into your theme or even in a plugin means that you can easily add a stylesheet for any WordPress block, core or otherwise. Once the code is in place, you can simply add a stylesshet into the active theme in the /assets/blocks/ folder. The name of the file must block name with any […]

The post Enqueue stylesheet for any WordPress block appeared first on Mark Wilkinson.

]]>
Placing the function below into your theme or even in a plugin means that you can easily add a stylesheet for any WordPress block, core or otherwise.

Once the code is in place, you can simply add a stylesshet into the active theme in the /assets/blocks/ folder. The name of the file must block name with any forward slashes replaced with a hyphen.

For example, to ensure a custom stylesheet is loaded for the core heading block, create a stylsheet named core-heading.css and place it in the /assets/blocks/ folder in your theme. This stylesheet will then get enqueued.

<?php
/**
 * Enqueues a stylesheet for each block, if it exists in the theme.
 */
function hd_enqueue_block_styles() {

	// get all of the registered blocks.
	$blocks = WP_Block_Type_Registry::get_instance()->get_all_registered();

	// if we have block names.
	if ( ! empty( $blocks ) ) {

		// loop through each block name.
		foreach ( $blocks as $block ) {

			// replace slash with hyphen for filename.
			$slug = str_replace( '/', '-', $block->name );

			// get the file path for the block.
			$block_path = get_theme_file_path( "assets/blocks/{$slug}.css" );

			// if we have no file existing for this block.
			if ( ! file_exists( $block_path ) ) {
				continue;
			}

			// register a block stylesheet for this block.
			wp_register_style(
				'hd-block-' . $slug,
				get_theme_file_uri( "assets/blocks/{$slug}.css" ),
				[],
				filemtime( $block_path )
			);

			// enqueue a block stylesheet for buttons.
			wp_enqueue_block_style(
				$block->name,
				[
					'handle' => 'hd-block-' . $slug,
					'path'   => $block_path
				]
			);

		}

	}

}

add_action( 'init', 'hd_enqueue_block_styles' );

The post Enqueue stylesheet for any WordPress block appeared first on Mark Wilkinson.

]]>
Custom labels for Yoast SEO Breadcrumb https://markwilkinson.dev/code-snippets/custom-labels-for-yoast-seo-breadcrumb/ Wed, 16 Oct 2024 20:21:27 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7338 Like many others, I often use the Breadcrumb feature of the Yoast SEO plugin to output a breadcrumb on posts. By default the label given to a post in the Breadcrumb is the post title. Sometimes I find myself wanting a different, usually shorter label in the breadcrumb. For example, instead of “JobRelay Action Hooks” […]

The post Custom labels for Yoast SEO Breadcrumb appeared first on Mark Wilkinson.

]]>
Like many others, I often use the Breadcrumb feature of the Yoast SEO plugin to output a breadcrumb on posts.

By default the label given to a post in the Breadcrumb is the post title. Sometimes I find myself wanting a different, usually shorter label in the breadcrumb.

For example, instead of “JobRelay Action Hooks” I just want the label in the breadcrumb to say “Actions”.

The code below, added to your themes functions.php file or in a plugin will do just that.

<?php
/**
 * Allows for a custom breadcrumb label.
 *
 * @param  array $links The current breadcrumb links.
 * @return array $links The modified breadcrumb links.
 */
function hd_edit_docs_breadcrumb( $links ) {

	// if we have no links.
	if ( empty( $links ) ) {
		return $links;
	}

	// loop through each link.
	foreach ( $links as $link_key => $link ) {

		// if this link has an ID.
		if ( ! empty( $link['id'] ) ) {
			
			// get the custom link label from post meta.
			$custom_label = get_post_meta( $link['id'], 'custom_breadcrumb_label', true );

			// if we have a custom label.
			if ( ! empty( $custom_label ) ) {
				$links[ $link_key ]['text'] = $custom_label;
			}

		}

	}

	// return the breadcrumb.
	return $links;

}

add_filter( 'wpseo_breadcrumb_links', 'hd_edit_docs_breadcrumb' );

To ensure a post has a custom breadcrumb label, add a custom field to the post with the key custom_breadcrumb_label and the value is the label you want to show.

The post Custom labels for Yoast SEO Breadcrumb appeared first on Mark Wilkinson.

]]>
Load scripts with WordPress core blocks on the front end https://markwilkinson.dev/code-snippets/load-scripts-with-wordpress-core-blocks-on-the-front-end/ Mon, 11 Dec 2023 14:06:32 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7255 There may be time when you want to load a script with a block on the front-end of your site but not in the editor. This is much easier if this is your own block as you can add the script declaration in your block.json file. When it comes to core blocks it is a […]

The post Load scripts with WordPress core blocks on the front end appeared first on Mark Wilkinson.

]]>
There may be time when you want to load a script with a block on the front-end of your site but not in the editor. This is much easier if this is your own block as you can add the script declaration in your block.json file.

When it comes to core blocks it is a little more difficult. You could use the enqueue_block_editor_assets hook, but this adds to both the front end and the admin.

Here is one way you can do this using the block_type_metadata_settings filter in WordPress.

<?php
/**
 * Registers the JS for the embed block.
 */
function hd_register_embed_block_js() {

	wp_register_script(
		'hd-embed-js',
		get_stylesheet_directory_uri() . '/assets/js/hd-embed.js',
		[],
		'1.0',
		true
	);

}

add_action( 'wp_enqueue_scripts', 'hd_register_embed_block_js' );

/**
 * Filters the block.json for the core/embed block to add the js script.
 *
 * @param  array $settings The block settings.
 * @param  array $metadata The block metadata.
 *
 * @return array $settings The block settings.
 */
function hd_filter_embed_block_meta_settings( $settings, $metadata ) {

	// if this is not the embed block.
	if ( 'core/embed' !== $metadata['name'] ) {
		return $settings;
	}

	// set the script settings to the register script name.
	$settings['script'] = [ 'hd-embed-js' ];

	// return the settings.
	return $settings;

}

add_filter( 'block_type_metadata_settings', 'hd_filter_embed_block_meta_settings', 10, 2 );

The post Load scripts with WordPress core blocks on the front end appeared first on Mark Wilkinson.

]]>
Hide WordPress block editor for pages with a specific page template https://markwilkinson.dev/code-snippets/hide-wordpress-block-editor-for-pages-with-a-specific-page-template/ Wed, 08 Nov 2023 13:57:55 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7252 Recently I had the requirement in a project where I wanted to remove the block editor, only for pages that had a specific page template assigned. In my case the page template was inside the templates folder of the theme and the filename was page-content-hub.php. This meant the page template had a slug of: templates/page-content-hub.php […]

The post Hide WordPress block editor for pages with a specific page template appeared first on Mark Wilkinson.

]]>
Recently I had the requirement in a project where I wanted to remove the block editor, only for pages that had a specific page template assigned.

In my case the page template was inside the templates folder of the theme and the filename was page-content-hub.php. This meant the page template had a slug of:

templates/page-content-hub.php

The following code, in my case added to the themes functions.php file meant the editor did not show on pages with that template assigned.

<?php
/**
 * Removes the block editor on the content hub template.
 *
 * @param bool  $use_block_editor Whether to use the block editor.
 * @param mixed $post             The post object.
 *
 * @return bool
 */
function hd_disable_block_editor_for_content_hub( $use_block_editor, $post ) {

	// get the page template slug for this post.
	$template = get_page_template_slug( $post->ID );

	// if the template slug matches the content hub template.
	if ( 'templates/page-content-hub.php' === $template ) {
		
		// disable the block editor.
		$use_block_editor = false;

	}

	return $use_block_editor;

}

add_filter( 'use_block_editor_for_post', 'hd_disable_block_editor_for_content_hub', 10, 2 );

The post Hide WordPress block editor for pages with a specific page template appeared first on Mark Wilkinson.

]]>
WordPress search block to only search in a custom post type https://markwilkinson.dev/code-snippets/wordpress-search-block-to-only-search-in-a-custom-post-type/ Wed, 04 Oct 2023 19:52:24 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7246 There are times when I have wanted to add a search block to a post or page in WordPress and have it only return results from a specific post type, rather than searching all posts types that are included in search. After a lot of back and forth I have managed to do this with […]

The post WordPress search block to only search in a custom post type appeared first on Mark Wilkinson.

]]>
There are times when I have wanted to add a search block to a post or page in WordPress and have it only return results from a specific post type, rather than searching all posts types that are included in search.

After a lot of back and forth I have managed to do this with the following code.

The resolves around adding a custom CSS class to your search block in the following format:

post-type--{post_type}

For example, to restrict the results to just a post type called movie, you would add the class of:

post-type--movie

You can also add multiple classes for restricting to more than one post type. For example adding these classes you restrict the results to posts and movies.

post-type--movie post-type--post

Here is the code you need, you can add this to your themes functions.php file or indeed in a plugin.

<?php
/**
 * Ensures the search block only searches for posts in the post type specified in the block class.
 *
 * @param  string $block_content The block content.
 * @param  array  $block         The block.
 * @param  array  $instance      The block instance.
 *
 * @return string                The block content.
 */
function hd_search_block_specific_post_type_only( $block_content, $block, $instance ) {

	// if this block has a custom classes added under advanced - do nothing.
	if ( empty( $block['attrs']['className'] ) ) {
		return $block_content;
	}

	// create an array to store post types in.
	$post_types = [];

	// split the class name at the space into an array.
	$classes = explode( ' ', $block['attrs']['className'] );

	// loop through each class.
	foreach ( $classes as $class ) {

		// if this class contains our special class.
		if ( false === strpos( $class, 'post-type--' ) ) {
			continue;
		}

		// split the class at the double dash.
		$class_parts = explode( '--', $class );

		// get the last part of the class - the post name.
		$post_types[] = $class_parts[1];

	}

	// if we have no post types.
	if ( empty( $post_types ) ) {
		return $block_content;
	}

	// check the post types exist.
	$post_types = array_filter( $post_types, 'post_type_exists' );

	// if we have no post types.
	if ( empty( $post_types ) ) {
		return $block_content;
	}

	// loop through each post type.
	foreach ( $post_types as $post_type ) {

		// replace the standard input with one that adds the post type.
		$block_content = str_replace( '<button aria-label="Search"', '<input type="hidden" name="post_type[]" value="' . esc_attr( $post_type ) . '"><button aria-label="Search"', $block_content );

	}

	// return the block content.
	return $block_content;

}

add_filter( 'render_block_core/search', 'hd_search_block_specific_post_type_only', 10, 3 );

If you are worried about your clients having to add the specific classes to a block, just create a block pattern, give it a sensible name (Search Movies for example) and they can simply add the pattern when they want to add a movie search to a post.

Update!

As it happens, there is an easier way to do this, but it does involve using Javascript.

This method involves creating a block variation of the search block. This is done using the code below, first enqueuing some javascript in the editor.

<?php
function hd_register_search_variation_js() {
	wp_enqueue_script(
		'hd_search_variation_js',
		get_template_directory_uri() . '/js/movie-search.js',
		array( 'wp-blocks' )
	);
}
add_action( 'enqueue_block_editor_assets', 'hd_register_search_variation_js' );

Then create a javascript file called movie-search.js and add this to your theme directories js folder.

In that file place the following:

wp.blocks.registerBlockVariation( 'core/search', {
	name: 'hd-search-movies',
	title: 'Search Movies',
	description: 'Displays a search input to search movies.',
	icon: 'search',
	attributes: {
		namespace: 'hd-search-movies',
		query: {
			post_type: 'movie',
		}
	},
});

The important part of that code is the query part where we set the post type.

The post WordPress search block to only search in a custom post type appeared first on Mark Wilkinson.

]]>
Prevent WordPress custom post type having a permalink page https://markwilkinson.dev/code-snippets/prevent-wordpress-custom-post-type-having-a-permalink-page/ Tue, 03 Oct 2023 19:52:17 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7245 Sometimes you have a custom post type on a site and you don’t want it to have a permalink page on the front end of the site. If that is the case, you can use the following code to amend the post types registered arguments to make this change. In this example we prevent WooCommerce […]

The post Prevent WordPress custom post type having a permalink page appeared first on Mark Wilkinson.

]]>
Sometimes you have a custom post type on a site and you don’t want it to have a permalink page on the front end of the site.

If that is the case, you can use the following code to amend the post types registered arguments to make this change.

In this example we prevent WooCommerce products from having a single permalink page.

<?php
/**
 * Makes products not public to prevent them having a permalink page.
 *
 * @param array  $args      The post type args.
 * @param string $post_type The post type name.
 *
 * @return array $args The modified post type args.
 */
function hd_prevent_products_permalink_page( $args, $post_type ) {

	// if this is not the product post type.
	if ( 'product' !== $post_type ) {
		return $args;
	}

	// change the public arg.
	$args['public'] = false;

	// return the args.
	return $args;

}

add_filter( 'register_post_type_args', 'hd_prevent_products_permalink_page', 10, 2 );

The post Prevent WordPress custom post type having a permalink page appeared first on Mark Wilkinson.

]]>
Using the render_block filter to modify WordPress block output https://markwilkinson.dev/code-snippets/using-the-render_block-filter-to-modify-wordpress-block-output/ Tue, 03 Oct 2023 19:47:44 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7244 Most WordPress blocks output some sort of HTML markup to the page when rendered on the front end. Luckily, that to the excellent extensibility features of WordPress, all blocks markup when rendered is run through a filter. This allows developers like you and make to make changes to the block output. To do this you […]

The post Using the render_block filter to modify WordPress block output appeared first on Mark Wilkinson.

]]>
Most WordPress blocks output some sort of HTML markup to the page when rendered on the front end.

Luckily, that to the excellent extensibility features of WordPress, all blocks markup when rendered is run through a filter. This allows developers like you and make to make changes to the block output.

To do this you hook a function into the render_block filter, or you can use the render_block_{block_name} specific filters to targeting specific blocks.

For example, to target the core paragraph block you would hook into the render_block_core/paragraph filter hook.

Here is an example:

<?php
/**
 * Modify the core paragraph block.
 *
 * @param string $block_content The block content about to be rendered.
 * @param array  $block         The full block including name and attributes.
 * @param array  $instance      The block instance (unique ID).
 *
 * @return string               The maybe modified block content.
 */
function hd_modify_core_paragraph_block( $block_content, $block, $instance ) {

	// you can check for this here such as block attributes.
	// if the block has an additional class of 'my_classname' then do something.
	if ( 'my_classname' === $block['className'] ) {
		
		// wrap the block in a div for example.
		$block_content = '<div class="my-classname">' . $block_content . '</div>';

	}

	// return the block content.
	return $block_content;

}

add_filter( 'render_block_core/paragraph', 'hd_modify_core_paragraph_block', 10, 3 );

The post Using the render_block filter to modify WordPress block output appeared first on Mark Wilkinson.

]]>
Alter query args on the WordPress Query block https://markwilkinson.dev/code-snippets/alter-query-args-on-the-wordpress-query-block/ Tue, 03 Oct 2023 19:37:48 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7243 I was recently working on a project which utilised the WordPress Query Block in order to display jobs from a custom post type in WordPress. The custom post type in question was the job_listing post type provided by the WP Job Manager plugin. It registered a custom post status to assign to jobs called expired. […]

The post Alter query args on the WordPress Query block appeared first on Mark Wilkinson.

]]>
I was recently working on a project which utilised the WordPress Query Block in order to display jobs from a custom post type in WordPress.

The custom post type in question was the job_listing post type provided by the WP Job Manager plugin.

It registered a custom post status to assign to jobs called expired. This is assigned to a job when it passes it expiration date, sent as a meta field.

I noticed the output of the Query Block was outputting expired jobs and therefore wanted a way to prevent this happening.

It turns out there is a filter you can use to filter the query parameters used by the Query Block called query_loop_block_query_vars

Here is an example of how to ensured that only published jobs where displayed when a query block was used and the post type was querying job_listing posts.

<?php
/**
 * Alters the query loop query args if the post type is a job listing.
 * Ensures that the query loop block only shows published jobs.
 *
 * @param array $query The query args.
 * @return array $query The query args.
 */
function hd_edit_job_query_loop_block_params( $query ) {

	// if this block is not the jobs post type.
	if ( 'job_listing' !== $query['post_type'] ) {
		// return the query params.
		return $query;
	}
	
	// set post status.
	$query['post_status'] = 'publish';

	// return the query params.
	return $query;

}

add_filter( 'query_loop_block_query_vars', 'hd_edit_job_query_loop_block_params', 10, 1 );

The post Alter query args on the WordPress Query block appeared first on Mark Wilkinson.

]]>
Re-enable the customizer for block themes https://markwilkinson.dev/code-snippets/re-enable-the-customizer-for-block-themes/ Wed, 17 May 2023 10:47:55 +0000 https://markwilkinson.dev/?post_type=mw_code&p=7123 If you are using a block theme in WordPress, the customizer will be removed, unless you have other plugins etc. registering customizer items. I quite like the “additional CSS” part of the customizer as you get a live preview of your changes. Therefore, if you want to re-enable this, you can do so by adding […]

The post Re-enable the customizer for block themes appeared first on Mark Wilkinson.

]]>
If you are using a block theme in WordPress, the customizer will be removed, unless you have other plugins etc. registering customizer items.

I quite like the “additional CSS” part of the customizer as you get a live preview of your changes.

Therefore, if you want to re-enable this, you can do so by adding this code to a plugin or your theme functions file.

add_action( 'customize_register', '__return_true' );

The post Re-enable the customizer for block themes appeared first on Mark Wilkinson.

]]>