Enable Gutenberg only for Pages

Here’s a quick way to enable Gutenberg only for pages and keep using the “Classic Editor” on other post types:

add_filter( 'gutenberg_can_edit_post_type', function ( $can_edit, $post_type ) {
    $can_edit = $post_type === 'page';

    return $can_edit;
}, 99, 2 );

Enqueue scripts & styles responsibly

So you have a fancy plugin that’s doing something awesome on a WordPress admin page. The plugin needs some CSS and/or JS to work so you enqueue them in the admin_enqueue_scripts hook. All good™. Hmm, not really. What you did is correct, but not very responsible. The fact is, I rarely see plugins that do their stuff on all admin pages, so why enqueue the scripts and/or styles everywhere?

Here’s a simple trick to make a plugin enqueue its scripts & styles only on certain admin pages.

Let’s say we have a plugin that enhances the new and edit post screens:

/**
 * Enqueue admin script on new and edit post screens.
 */
function myplugin_enqueue_admin_scripts() {
	wp_enqueue_script(
		'myplugin-admin-script',
		plugin_dir_url( __FILE__ ) . 'script.js',
		array( 'jquery' ),
		'0.1.0',
		true
	);
}

add_action( 'load-post.php', 'myplugin_enqueue_admin_scripts' );
add_action( 'load-post-new.php', 'myplugin_enqueue_admin_scripts' );

Notice that we’re not using the admin_enqueue_scripts hook? That’s the trick! Instead, we’re using the load-(page) hook so that our script will only load on those pages.

Further Reading

Get menu items as multidimensional array

There are times when we need to get menu items as a big multidimensional array so we can do some processing before finally displaying them. We can’t simply use wp_get_nav_menu_items() because it returns a flat array. The solution for this is pretty simple: use a custom walker.

/**
 * Nav menu walker
 */
class Bridge_Walker_Nav_Menu extends Walker_Nav_Menu {

	/**
	 * Prepare item
	 *
	 * @param  object $item  Menu Item.
	 * @param  array  $args  Arguments passed to walk().
	 * @param  int    $depth Item's depth.
	 * @return array
	 */
	protected function prepare_item( $item, $args, $depth ) {
		$title   = apply_filters( 'the_title', $item->title, $item->ID );
		$title   = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth );
		$classes = apply_filters( 'nav_menu_css_class', array_filter( $item->classes ), $item, $args, $depth );

		return array(
			'id'          => absint( $item->ID ),
			'order'       => (int) $item->menu_order,
			'parent'      => absint( $item->menu_item_parent ),
			'title'       => $title,
			'url'         => $item->url,
			'attr'        => $item->attr_title,
			'target'      => $item->target,
			'classes'     => $classes,
			'xfn'         => $item->xfn,
			'description' => $item->description,
			'object_id'   => absint( $item->object_id ),
			'object'      => $item->object,
			'type'        => $item->type,
			'type_label'  => $item->type_label,
			'children'    => array(),
		);
	}


	/**
	 * Traverse elements to create list from elements.
	 *
	 * This method should not be called directly, use the walk() method instead.
	 *
	 * @param object $element           Data object.
	 * @param array  $children_elements List of elements to continue traversing.
	 * @param int    $max_depth         Max depth to traverse.
	 * @param int    $depth             Depth of current element.
	 * @param array  $args              An array of arguments.
	 * @param array  $output            Passed by reference. Used to append additional content.
	 */
	public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
		if ( ! $element ) {
			return;
		}

		if ( ! is_array( $output ) ) {
			$output = array();
		}

		$id_field = $this->db_fields['id'];
		$id       = $element->$id_field;
		$item     = $this->prepare_item( $element, $args, $depth );

		if ( ! empty( $children_elements[ $id ] ) ) {
			foreach ( $children_elements[ $id ] as $child ) {
				$this->display_element(
					$child,
					$children_elements,
					1,
					( $depth + 1 ),
					$args,
					$item['children']
				);
			}

			unset( $children_elements[ $id ] );
		}

		$output[] = $item;
	}
}

Example Usage:

$menu_id   = 2;
$max_depth = 0; // All level.
$walker    = new Bridge_Walker_Nav_Menu;
$items     = $walker->walk( wp_get_nav_menu_items( $menu_id ), $max_depth );

Conditionally disable Jetpack’s Infinite Scroll

So you implemented Jetpack’s Infinite Scroll feature in your theme, but you don’t want it in a particular page, say a category archive page. Here’s the code you need to add to your theme’s functions.php file:

/**
 * Disable Jetpack's Infinite Scroll Conditionally
 *
 */
function _kucrut_disable_jetpack_infinite_scroll_conditionally() {
	if ( true === my_conditionals() ) {
		remove_theme_support( 'infinite-scroll' );
	}
}
add_action( 'template_redirect', '_kucrut_disable_jetpack_infinite_scroll_conditionally', 9 );

Just remember to change my_conditionals() with your actual conditionals and to never add the action callback after priority 9, otherwise it will be too late 😉

Forward requests to another host with iptables

Like the cool kids in the block, I too am using Vagrant in my web development workflow. Everything is fine until I need to test the sites with real mobile browsers. Since the sites I’m working on are hosted inside a virtual machine, it’s not accessible from outside of the host machine.

Here’s how we can solve this using iptables. Let’s assume that the host machine’s IP is 192.168.1.2 and the guest/virtual machine’s IP is 10.86.73.80. These commands are to be run in the host machine as root:

iptables -F
iptables -t nat -F
iptables -X
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.86.73.80:80
iptables -A FORWARD -d 10.86.73.80 -p tcp --dport 80 -j ACCEPT
iptables -t nat -A POSTROUTING -j MASQUERADE

Now, add the site’s domain to your rooted phone’s /etc/hosts file or your router’s configs:

192.168.1.2  mylocalsite.dev

You can save the rules above permanently with:

iptables-save > /etc/iptables/iptables.rules

Set attachment’s default “Link To” value

attachment-display-settings When inserting an attachment into a WordPress post, we can customize how it should be displayed. There are three settings; Alignment, Link To and Size. Here’s how we can tell WordPress to select None as the default value of Link To:

/**
 * Set default media link to 'none'
 *
 * @param   string $value Option value
 * @wp_hook filter pre_option_image_default_link_type
 * @return  string
 */
function _kucrut_image_default_link_type( $value ) {
    return 'none';
}
add_filter( 'pre_option_image_default_link_type', '_kucrut_image_default_link_type' );

As of WordPress 4.0, there are four values available: file (default), post, custom and none.

Get ACF Repeater field value

So you’re using ACF‘s repeater field to store your data and are wondering if there’s an easier way to get its value? Here’s how:

/**
 * Get ACF repeater field value
 *
 * @param int|object $post          Post ID/object
 * @param string     $meta_key      Meta key
 * @param array      $subfield_keys Subfield keys
 *
 * return mixed
 */
function kucrut_get_acf_repeater_field_value( $post, $meta_key, Array $subfield_keys ) {
    $post = get_post( $post );
    if ( ! ( $post instanceof WP_Post ) ) {
        return false;
    }

    $count = absint( get_post_meta( $post->ID, $meta_key, true ) );
    if ( empty( $count ) ) {
        return false;
    }

    $values = array();
    for ( $i = 0; $i < $count; $i++ ) {
        $item = array();
        foreach ( $subfield_keys as $sub_key ) {
            $item[ $sub_key ] = get_post_meta(
                $post->ID,
                sprintf( '%s_%d_%s', $meta_key, $i, $sub_key ),
                true
            );
        }
        $values[] = $item;
    }

    return $values;
}

Add custom image sizes, the right way

Since version 2.9, WordPress supports custom image sizes that will be used to create thumbnails of image attachments. Adding new image sizes is pretty easy; in your theme’s functions.php file, add this block of code:

function mytheme_add_image_sizes() {
    add_image_size( 'small_thumb', 50, 50, true );
}
add_action( 'after_setup_theme', 'mytheme_add_image_sizes' );

… and you’re done. Now, everytime a new image is uploaded, a 50×50 thumbnail with be created for you.

However, this new image size won’t show up when you want to insert an image into a post:

Image size dropdown on Insert Media frame
Image size dropdown on Insert Media frame

The solution is pretty simple too; add this bit of code into your theme’s functions.php file:

function mytheme_image_size_names( $sizes ) {
    $sizes['small_thumb'] = __( 'Small Thumb', 'mytheme' );

    return $sizes;
}
add_filter( 'image_size_names_choose', 'mytheme_image_size_names' );

Now you can select your custom image size from the dropdown:

Image size dropdown on Insert Media frame, with custom image sizes.
Image size dropdown on Insert Media frame, with custom image sizes.

Notes:

Additional Fields for Soliloquy Images

So… I was asked to convert an HTML template into a a WordPress page template, and it has two sliders. A friend of mine recommended me to use Soliloquy, which is a very nice plugin. However, since I didn’t want to mess with the markup and styling, I opted to only use Soliloquy’s backend.

Now, the markup contains an additional text which is not suitable for the existing image fields (title, alt, caption, etc), so I needed to add an additional field to the image meta frame, and here’s how I did it:

<?php
/**
 * Add extra custom fields to slideshow images
 */
function _my_soliloquy_slide_source_field( $id, $data, $post_id ) {
	$source = ! empty( $data['source'] ) ? $data['source'] : '';
	?>
		<tr id="soliloquy-source-box-<?php echo esc_attr( $id ) ?>" valign="middle">
			<th scope="row"><label for="soliloquy-source-<?php echo esc_attr( $id ) ?>"><?php _e( 'Source', 'mytheme' ); ?></label></th>
			<td>
				<input id="soliloquy-source-<?php echo esc_attr( $id ) ?>" class="soliloquy-source" type="text" name="_soliloquy[meta_source]" value="<?php echo esc_attr( $source ); ?>" data-soliloquy-meta="source" />
			</td>
		</tr>
	<?php
}
add_action( 'soliloquy_after_image_meta_settings', '_my_soliloquy_slide_source_field', 1, 3 );

/**
 * Save slideshow images's extra custom fields
 */
function _my_soliloquy_ajax_save_meta( $slider_data, $meta, $attach_id, $post_id ) {
	if ( ! empty( $meta['source'] ) ) {
		$slider_data['slider'][ $attach_id ]['source'] = sanitize_text_field( $meta['source'] );
	}
	else {
		unset( $slider_data['slider'][ $attach_id ]['source'] );
	}

	return $slider_data;
}
add_action( 'soliloquy_ajax_save_meta', '_my_soliloquy_ajax_save_meta', 10, 4 );

_sol_slider_data now contains the source field’s value.