Interlinking posts in WordPress (previous posts from category). Function 2

I want to share the improvement of my function for circular interlinking of articles in WordPress. The first version of the function does not differ from this one in general, the only difference is that with this version of the function, you can specify which taxonomy to work with and which post type you are interested in. The previous function only works with standard WordPress categories and posts, i.e. with the taxonomy category and the post type post.

This function completely replaces the previous one. The impetus for its creation was a request from one of the readers of this blog to create a function for circular interlinking for custom (arbitrary) pages provided on the site. The taxonomy was also created anew. Without much thought, I modified the previous function, changing the principle of obtaining the taxonomy: if previously the category(ies) in which the post is located was obtained using standard WordPress functions, now the taxonomy to which the post belongs is obtained directly from the Database, this approach seems more logical and faster (not verified).

The person who requested this function even thanked me with 10 conditional units to my WebMoney wallet, which is certainly nice, thanks to him, I hope he doesn't mind that I'm sharing this function publicly.

The function is installed as usual: copy the code to the theme file functions.php and then in the template, where you need to display the links, call the function with the necessary parameters.

GitHub
/**
 * Предыдущие записи из рубрики (относительно текущей записи) +
 * кольцевая перелинковка (можно указывать таксономию и тип записи).
 *
 * Кэширует результат в объектный кэш, если он включен.
 *
 * Вызываем функцию так:
 *
 *     echo kama_previous_posts_from_tax_lis( [
 *         'post_num' => 5,
 *         'format' => '{date:j.M.Y} - {a}{title}{/a}',
 *     ] );
 *
 * @param array|string $args {
 *     Parameters passed as array or query string.
 *
 *     @type int          $post_num      Количество ссылок.
 *     @type string       $format        {thumb} {date:j.M.Y} - {a}{title}{/a} ({comments})
 *     @type string       $list_tag      Тег-обертка каждой ссылки.
 *     @type string       $tax           Таксономия. Пр. category.
 *     @type string|array $exclude_terms Элементы таксономии посты из которых нужно исключить (через запятую или в массиве).
 *                                       Указываем term_id или term_slug.
 *     @type string       $post_type     Тип записи. пр. post.
 * }
 *
 * @version 1.3
 */
function kama_previous_posts_from_tax_lis( $args = array() ){
	global $post, $wpdb;

	$rg = (object) wp_parse_args( $args, [
		'post_num'      => 5,
		'format'        => '',
		'list_tag'      => 'li',
		'tax'           => 'category',
		'post_type'     => 'post',
		'exclude_terms' => '',
	] );

	if( wp_using_ext_object_cache() ){
		$cache_key = md5( __FUNCTION__ . $post->ID );
		$cache_flag = __FUNCTION__;

		if( $cache_out = wp_cache_get( $cache_key, $cache_flag ) ){
			return $cache_out;
		}
	}

	// wp_parse_list() analog to work with WP < 5.1
	if ( is_string( $rg->exclude_terms ) ) {
		$rg->exclude_terms = preg_split( '/[\s,]+/', $rg->exclude_terms, -1, PREG_SPLIT_NO_EMPTY );
	}
	if( 'exclude_terms' ){
		$exclude_terms = $rg->exclude_terms;
		foreach( $exclude_terms as & $term_id ){
			if( ! is_numeric( $term_id ) ){
				$term = get_term_by( 'slug', $term_id, $rg->tax );
				$term_id = $term ? $term->term_id : 0;
			}
			else {
				$term_id = (int) $term_id;
			}
		}
		unset( $term_id );
		$exclude_terms = array_map( 'intval', array_filter( $exclude_terms ) );

		$AND_NOT_IN = $exclude_terms ? sprintf( ' AND term_id NOT IN (%s)', implode( ',', $exclude_terms ) ) : '';
	}

	$sub_query_term_id = $wpdb->prepare(
		"(SELECT term_id FROM $wpdb->term_relationships rl
			LEFT JOIN $wpdb->term_taxonomy tx ON (rl.term_taxonomy_id = tx.term_taxonomy_id)
			WHERE object_id = %d AND tx.taxonomy = %s $AND_NOT_IN
			LIMIT 1)",
		$post->ID, $rg->tax
	);

	$WHERE_arr = [
		"p.post_status = 'publish'",
		"tax.term_id = $sub_query_term_id",
		$wpdb->prepare( 'p.post_date < %s', $post->post_date ),
		$wpdb->prepare( 'tax.taxonomy = %s', $rg->tax ),
		$wpdb->prepare( 'p.post_type = %s', $rg->post_type ),
	];

	$SELECT = "SELECT ID, post_title, post_date, comment_count, guid
		FROM $wpdb->posts p
		LEFT JOIN $wpdb->term_relationships rel ON (p.ID = rel.object_id)
		LEFT JOIN $wpdb->term_taxonomy tax ON (rel.term_taxonomy_id = tax.term_taxonomy_id)";

	$WHERE = implode( ' AND ', $WHERE_arr );
	$ORDER_BY = 'ORDER BY p.post_date DESC';

	$sql = "$SELECT WHERE $WHERE $ORDER_BY LIMIT " . (int) $rg->post_num;

	$posts_data = $wpdb->get_results( $sql );

	$posts_count = count( $posts_data );

	// если количество меньше нужного, делаем 2-й запрос (кольцевая перелинковка)
	if( ! $posts_data || $posts_count < $rg->post_num ){
		$NOT_IN = $post->ID;
		foreach( $posts_data as $pdata ){
			$NOT_IN .= ",$pdata->ID";
		}

		$WHERE_arr[] = "p.ID NOT IN ($NOT_IN)";
		$WHERE = implode( ' AND ', $WHERE_arr );

		$sql = "$SELECT WHERE $WHERE $ORDER_BY LIMIT " . (int) ( $rg->post_num - $posts_count );

		$res2 = $wpdb->get_results( $sql );

		$posts_data = array_merge( $posts_data, $res2 );
	}

	if( ! $posts_data ){
		return '';
	}

	// вывод
	$date_match = false;
	if( $rg->format ){
		preg_match( '!{date:(.*?)}!', $rg->format, $date_match );
	}

	$add_thumb = false !== strpos( $rg->format, '{thumb}' );

	$out = '';
	foreach( $posts_data as $pdata ){
		$x = ( ( $x ?? '' ) === 'li1' ) ? 'li2' : 'li1';

		$a = sprintf( '<a href="%s" title="%s">', get_permalink( $pdata->ID ), esc_attr( $pdata->post_title ) );

		if( $rg->format ){
			$formated = strtr( $rg->format, [
				'{title}'    => esc_html( $pdata->post_title ),
				'{a}'        => $a,
				'{/a}'       => '</a>',
				'{comments}' => $pdata->comment_count ?: '',
			] );

			if( $add_thumb ){
				$formated = str_replace( '{thumb}', get_the_post_thumbnail( $pdata->ID, 'thumbnail' ), $formated );
			}

			// есть дата
			if( $date_match ){
				$formated = str_replace( $date_match[0], apply_filters( 'the_time', mysql2date( $date_match[1], $pdata->post_date ) ), $formated );
			}
		}
		else{
			$formated = $a . esc_html( $pdata->post_title ) . '</a>';
		}

		$out .= "\t<li class=\"$x\">$formated</li>\n";
	}

	if( wp_using_ext_object_cache() ){
		wp_cache_add( $cache_key, $out, $cache_flag );
	}

	return $out;
}

Parameters passed to the function

  1. post_num - the number of links to be displayed. Default is 5.

  2. format - format of the displayed links. Default is "{a}{title}{/a}".

  3. tax - taxonomy to which the post belongs. Default is category.

  4. post_type - post type for which interlinking will occur. Default is post.

Several examples of usage

#1. Display 10 previous links from the category for regular WordPress posts:

<?php echo kama_previous_posts_from_tax('post_num=10'); ?>

#2. Example of passing parameters and specifying the format

Display 6 previous links for posts from the movies taxonomy, with a post type of movie. Also, in the output format, add the publication date of the post and a thumbnail:

<?php
echo kama_previous_posts_from_tax( array(
	'post_num'  => 6,
	'format'    => '{thumb} {a}{title}{/a} - {date:j.M.Y}',
	'tax'       => 'movies',
	'post_type' => 'movie',
);
?>

For the full list of parameters and shortcodes for the format parameter, refer to the function code (at the very beginning).