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 😉

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.

Custom byline markup for Co-Authors Plus

Co-Authors Plus is a nice plugin for assigning multiple authors to posts. However, I find its template tag for displaying byline rather limiting because sometimes we want to use fancy markup for the byline (think of avatars, publish/modification dates, etc). You can use the code below as the base for your theme’s byline markup (in this example, we’re overriding twentyfourteen_posted_on()):

function twentyfourteen_posted_on() {
	if ( is_sticky() && is_home() && ! is_paged() ) {
		echo '<span class="featured-post">' . __( 'Sticky', 'twentyfourteen' ) . '</span>';
	}

	// Set up and print post meta information.
	printf( '<span class="entry-date"><a href="%1$s" rel="bookmark"><time class="entry-date" datetime="%2$s">%3$s</time></a></span>',
		esc_url( get_permalink() ),
		esc_attr( get_the_date( 'c' ) ),
		esc_html( get_the_date() )
	);

	// get the co-authors
	if ( function_exists( 'get_coauthors' ) ) {
		$authors = get_coauthors();
	}

	// Fallback to WP users
	if ( empty( $authors ) || ! is_array( $authors ) ) {
		$authors = array( get_userdata( get_the_author_meta( 'ID' ) ) );
	}

	foreach ( $authors as $author ) {
		$_args = apply_filters(
			'coauthors_posts_link',
			array( 'href' => get_author_posts_url( intval( $author->ID ), $author->user_nicename ) )
		);

		printf(
			'<span class="byline"><span class="author vcard"><a class="url fn n" href="%1$s" rel="author">%2$s</a></span></span>',
			esc_url( $_args['href'] ),
			esc_html( $author->display_name )
		);
	}
}

“So how do I display the avatars” you ask? Add the following code to your theme’s functions.php file and use it to get the avatar image instead of the default get_avatar():

/**
 * Wrapper for get_avatar
 *
 * @param mixed  $user    User object, ID or email
 * @param int    $size    Avatar size
 * @param string $default Default avatar image
 * @param string $alt     Alt text
 */
function kucrut_get_avatar( $user, $size = 33, $default = '', $alt = false ) {
	if ( is_object( $user ) && function_exists( 'coauthors_get_avatar' ) ) {
		return coauthors_get_avatar( $user, $size, $default, $alt );
	}
	else {
		return get_avatar( $user, $size, $default, $alt );
	}
}