get_adjacent_post

The timeline below displays how wordpress function get_adjacent_post has changed across different WordPress versions. If a version is not listed, refer to the next available version below.

WordPress Version: 6.3

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty string.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 *                                     Default true.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return WP_Post|null|string Post object if successful. Null if global `$post` is not set.
 *                             Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    $post = get_post();
    if (!$post || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
        if (str_contains($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(
                /* translators: %s: The word 'and'. */
                __('Use commas instead of %s to separate excluded terms.'),
                "'and'"
            ));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_excluded_terms`
     *  - `get_previous_post_excluded_terms`
     *
     * @since 4.4.0
     *
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ($private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_join`
     *  - `get_previous_post_join`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string       $join           The JOIN clause in the SQL.
     * @param bool         $in_same_term   Whether post should be in the same taxonomy term.
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post      $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_where`
     *  - `get_previous_post_where`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string       $where          The `WHERE` clause in the SQL.
     * @param bool         $in_same_term   Whether post should be in the same taxonomy term.
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post      $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_sort`
     *  - `get_previous_post_sort`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $key = md5($query);
    $last_changed = wp_cache_get_last_changed('posts');
    if ($in_same_term || !empty($excluded_terms)) {
        $last_changed .= wp_cache_get_last_changed('terms');
    }
    $cache_key = "adjacent_post:{$key}:{$last_changed}";
    $result = wp_cache_get($cache_key, 'post-queries');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($cache_key, $result, 'post-queries');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 6.2

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty string.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 *                                     Default true.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return WP_Post|null|string Post object if successful. Null if global `$post` is not set.
 *                             Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    $post = get_post();
    if (!$post || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
        if (false !== strpos($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(
                /* translators: %s: The word 'and'. */
                __('Use commas instead of %s to separate excluded terms.'),
                "'and'"
            ));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_excluded_terms`
     *  - `get_previous_post_excluded_terms`
     *
     * @since 4.4.0
     *
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ($private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_join`
     *  - `get_previous_post_join`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string       $join           The JOIN clause in the SQL.
     * @param bool         $in_same_term   Whether post should be in the same taxonomy term.
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post      $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_where`
     *  - `get_previous_post_where`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string       $where          The `WHERE` clause in the SQL.
     * @param bool         $in_same_term   Whether post should be in the same taxonomy term.
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post      $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_sort`
     *  - `get_previous_post_sort`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $key = md5($query);
    $last_changed = wp_cache_get_last_changed('posts');
    if ($in_same_term || !empty($excluded_terms)) {
        $last_changed .= wp_cache_get_last_changed('terms');
    }
    $cache_key = "adjacent_post:{$key}:{$last_changed}";
    $result = wp_cache_get($cache_key, 'posts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($cache_key, $result, 'posts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 6.1

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty string.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return WP_Post|null|string Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    $post = get_post();
    if (!$post || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
        if (false !== strpos($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(
                /* translators: %s: The word 'and'. */
                __('Use commas instead of %s to separate excluded terms.'),
                "'and'"
            ));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_excluded_terms`
     *  - `get_previous_post_excluded_terms`
     *
     * @since 4.4.0
     *
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ($private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_join`
     *  - `get_previous_post_join`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string       $join           The JOIN clause in the SQL.
     * @param bool         $in_same_term   Whether post should be in a same taxonomy term.
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post      $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_where`
     *  - `get_previous_post_where`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string       $where          The `WHERE` clause in the SQL.
     * @param bool         $in_same_term   Whether post should be in a same taxonomy term.
     * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post      $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_sort`
     *  - `get_previous_post_sort`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 5.9

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty string.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return WP_Post|null|string Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    $post = get_post();
    if (!$post || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
        if (false !== strpos($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(
                /* translators: %s: The word 'and'. */
                __('Use commas instead of %s to separate excluded terms.'),
                "'and'"
            ));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_excluded_terms`
     *  - `get_previous_post_excluded_terms`
     *
     * @since 4.4.0
     *
     * @param array|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ($private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_join`
     *  - `get_previous_post_join`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_where`
     *  - `get_previous_post_where`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $where          The `WHERE` clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_sort`
     *  - `get_previous_post_sort`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 5.8

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty string.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    $post = get_post();
    if (!$post || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
        if (false !== strpos($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(
                /* translators: %s: The word 'and'. */
                __('Use commas instead of %s to separate excluded terms.'),
                "'and'"
            ));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_excluded_terms`
     *  - `get_previous_post_excluded_terms`
     *
     * @since 4.4.0
     *
     * @param array|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_join`
     *  - `get_previous_post_join`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_where`
     *  - `get_previous_post_where`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $where          The `WHERE` clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * Possible hook names include:
     *
     *  - `get_next_post_sort`
     *  - `get_previous_post_sort`
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 5.7

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty string.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    $post = get_post();
    if (!$post || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
        if (false !== strpos($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(
                /* translators: %s: The word 'and'. */
                __('Use commas instead of %s to separate excluded terms.'),
                "'and'"
            ));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 4.4.0
     *
     * @param array|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $where          The `WHERE` clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 5.3

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    $post = get_post();
    if (!$post || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
        if (false !== strpos($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(
                /* translators: %s: The word 'and'. */
                __('Use commas instead of %s to separate excluded terms.'),
                "'and'"
            ));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 4.4.0
     *
     * @param array $excluded_terms Array of excluded term IDs.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $where          The `WHERE` clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 5.2

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
        if (false !== strpos($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 4.4.0
     *
     * @param array $excluded_terms Array of excluded term IDs.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $where          The `WHERE` clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 5.1

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if (!empty($excluded_terms) && !is_array($excluded_terms)) {
        // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
        if (false !== strpos($excluded_terms, ' and ')) {
            _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
            $excluded_terms = explode(' and ', $excluded_terms);
        } else {
            $excluded_terms = explode(',', $excluded_terms);
        }
        $excluded_terms = array_map('intval', $excluded_terms);
    }
    /**
     * Filters the IDs of terms excluded from adjacent post queries.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 4.4.0
     *
     * @param array $excluded_terms Array of excluded term IDs.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    if ($in_same_term || !empty($excluded_terms)) {
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare('AND tt.taxonomy = %s', $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= ' AND tt.term_id IN (' . implode(',', $term_array) . ')';
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(' OR p.post_status = %s', $state);
            } else {
                $where .= $wpdb->prepare(' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state);
            }
        }
        $where .= ' )';
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $where          The `WHERE` clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 4.9

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if ($in_same_term || !empty($excluded_terms)) {
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        /**
         * Filters the IDs of terms excluded from adjacent post queries.
         *
         * The dynamic portion of the hook name, `$adjacent`, refers to the type
         * of adjacency, 'next' or 'previous'.
         *
         * @since 4.4.0
         *
         * @param string $excluded_terms Array of excluded term IDs.
         */
        $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(" OR p.post_status = %s", $state);
            } else {
                $where .= $wpdb->prepare(" OR (p.post_author = %d AND p.post_status = %s)", $user_id, $state);
            }
        }
        $where .= " )";
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $where          The `WHERE` clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     * @since 4.9.0 Added the `$order` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post, $order);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 4.6

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
 * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
 *                             corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if ($in_same_term || !empty($excluded_terms)) {
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3.0', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        /**
         * Filters the IDs of terms excluded from adjacent post queries.
         *
         * The dynamic portion of the hook name, `$adjacent`, refers to the type
         * of adjacency, 'next' or 'previous'.
         *
         * @since 4.4.0
         *
         * @param string $excluded_terms Array of excluded term IDs.
         */
        $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(" OR p.post_status = %s", $state);
            } else {
                $where .= $wpdb->prepare(" OR (p.post_author = %d AND p.post_status = %s)", $user_id, $state);
            }
        }
        $where .= " )";
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filters the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string $where          The `WHERE` clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     * @param string $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filters the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 4.5

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    $adjacent = $previous ? 'previous' : 'next';
    if ($in_same_term || !empty($excluded_terms)) {
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        /**
         * Filter the IDs of terms excluded from adjacent post queries.
         *
         * The dynamic portion of the hook name, `$adjacent`, refers to the type
         * of adjacency, 'next' or 'previous'.
         *
         * @since 4.4.0
         *
         * @param string $excluded_terms Array of excluded term IDs.
         */
        $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode(',', array_map('intval', $excluded_terms)) . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(" OR p.post_status = %s", $state);
            } else {
                $where .= $wpdb->prepare(" OR (p.post_author = %d AND p.post_status = %s)", $user_id, $state);
            }
        }
        $where .= " )";
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filter the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filter the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string $where          The `WHERE` clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     * @param string $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filter the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 4.4

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    if ($in_same_term || !empty($excluded_terms)) {
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
            $where .= $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode($excluded_terms, ',') . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(" OR p.post_status = %s", $state);
            } else {
                $where .= $wpdb->prepare(" OR (p.post_author = %d AND p.post_status = %s)", $user_id, $state);
            }
        }
        $where .= " )";
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filter the excluded term ids
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 4.4.0
     *
     * @param string $excluded_terms Array of excluded term IDs.
     */
    $excluded_terms = apply_filters("get_{$adjacent}_post_excluded_terms", $excluded_terms);
    /**
     * Filter the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string  $join           The JOIN clause in the SQL.
     * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
     * @param array   $excluded_terms Array of excluded term IDs.
     * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filter the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
     *
     * @param string $where          The `WHERE` clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     * @param string $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
     * @param WP_Post $post           WP_Post object.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms, $taxonomy, $post);
    /**
     * Filter the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     * @since 4.4.0 Added the `$post` parameter.
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     * @param WP_Post $post    WP_Post object.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1", $post);
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 4.3

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    if ($in_same_term || !empty($excluded_terms)) {
        $join = " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
        $where = $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode($excluded_terms, ',') . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(" OR p.post_status = %s", $state);
            } else {
                $where .= $wpdb->prepare(" OR (p.post_author = %d AND p.post_status = %s)", $user_id, $state);
            }
        }
        $where .= " )";
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filter the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $join           The JOIN clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms);
    /**
     * Filter the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $where          The `WHERE` clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms);
    /**
     * Filter the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1");
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 4.2

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    if ($in_same_term || !empty($excluded_terms)) {
        $join = " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
        $where = $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode($excluded_terms, ',') . ') )';
        }
    }
    // 'post_status' clause depends on the current user.
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $post_type_object = get_post_type_object($post->post_type);
        if (empty($post_type_object)) {
            $post_type_cap = $post->post_type;
            $read_private_cap = 'read_private_' . $post_type_cap . 's';
        } else {
            $read_private_cap = $post_type_object->cap->read_private_posts;
        }
        /*
         * Results should include private posts belonging to the current user, or private posts where the
         * current user has the 'read_private_posts' cap.
         */
        $private_states = get_post_stati(array('private' => true));
        $where .= " AND ( p.post_status = 'publish'";
        foreach ((array) $private_states as $state) {
            if (current_user_can($read_private_cap)) {
                $where .= $wpdb->prepare(" OR p.post_status = %s", $state);
            } else {
                $where .= $wpdb->prepare(" OR (p.post_author = %d AND p.post_status = %s)", $user_id, $state);
            }
        }
        $where .= " )";
    } else {
        $where .= " AND p.post_status = 'publish'";
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filter the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $join           The JOIN clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms);
    /**
     * Filter the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $where          The `WHERE` clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms);
    /**
     * Filter the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1");
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 4.1

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    if ($in_same_term || !empty($excluded_terms)) {
        $join = " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
        $where = $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode($excluded_terms, ',') . ') )';
        }
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filter the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $join           The JOIN clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms);
    /**
     * Filter the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $where          The `WHERE` clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s AND p.post_status = 'publish' {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms);
    /**
     * Filter the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, `$adjacent`, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $order_by The `ORDER BY` clause in the SQL.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1");
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 0.1

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    if ($in_same_term || !empty($excluded_terms)) {
        $join = " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
        $where = $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode($excluded_terms, ',') . ') )';
        }
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filter the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $join           The JOIN clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms);
    /**
     * Filter the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $where          The WHERE clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s AND p.post_status = 'publish' {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms);
    /**
     * Filter the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $order_by The ORDER BY clause in the SQL.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1");
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 4.0

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $where = '';
    if ($in_same_term || !empty($excluded_terms)) {
        $join = " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
        $where = $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
        if (!empty($excluded_terms) && !is_array($excluded_terms)) {
            // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
            if (false !== strpos($excluded_terms, ' and ')) {
                _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                $excluded_terms = explode(' and ', $excluded_terms);
            } else {
                $excluded_terms = explode(',', $excluded_terms);
            }
            $excluded_terms = array_map('intval', $excluded_terms);
        }
        if ($in_same_term) {
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            // Remove any exclusions from the term array to include.
            $term_array = array_diff($term_array, (array) $excluded_terms);
            $term_array = array_map('intval', $term_array);
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $where .= " AND tt.term_id IN (" . implode(',', $term_array) . ")";
        }
        if (!empty($excluded_terms)) {
            $where .= " AND p.ID NOT IN ( SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN (" . implode($excluded_terms, ',') . ') )';
        }
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filter the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $join           The JOIN clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms);
    /**
     * Filter the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $where          The WHERE clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s AND p.post_status = 'publish' {$where}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms);
    /**
     * Filter the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $order_by The ORDER BY clause in the SQL.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1");
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 3.9

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $posts_in_ex_terms_sql = '';
    if ($in_same_term || !empty($excluded_terms)) {
        $join = " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
        if ($in_same_term) {
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $join .= $wpdb->prepare(" AND tt.taxonomy = %s AND tt.term_id IN (" . implode(',', array_map('intval', $term_array)) . ")", $taxonomy);
        }
        $posts_in_ex_terms_sql = $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
        if (!empty($excluded_terms)) {
            if (!is_array($excluded_terms)) {
                // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
                if (false !== strpos($excluded_terms, ' and ')) {
                    _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                    $excluded_terms = explode(' and ', $excluded_terms);
                } else {
                    $excluded_terms = explode(',', $excluded_terms);
                }
            }
            $excluded_terms = array_map('intval', $excluded_terms);
            if (!empty($term_array)) {
                $excluded_terms = array_diff($excluded_terms, $term_array);
                $posts_in_ex_terms_sql = '';
            }
            if (!empty($excluded_terms)) {
                $posts_in_ex_terms_sql = $wpdb->prepare(" AND tt.taxonomy = %s AND tt.term_id NOT IN (" . implode($excluded_terms, ',') . ')', $taxonomy);
            }
        }
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    /**
     * Filter the JOIN clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $join           The JOIN clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms);
    /**
     * Filter the WHERE clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $where          The WHERE clause in the SQL.
     * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
     * @param array  $excluded_terms Array of excluded term IDs.
     */
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s AND p.post_status = 'publish' {$posts_in_ex_terms_sql}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms);
    /**
     * Filter the ORDER BY clause in the SQL for an adjacent post query.
     *
     * The dynamic portion of the hook name, $adjacent, refers to the type
     * of adjacency, 'next' or 'previous'.
     *
     * @since 2.5.0
     *
     * @param string $order_by The ORDER BY clause in the SQL.
     */
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1");
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 3.8

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term.
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category')
{
    global $wpdb;
    if (!($post = get_post()) || !taxonomy_exists($taxonomy)) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $posts_in_ex_terms_sql = '';
    if ($in_same_term || !empty($excluded_terms)) {
        $join = " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
        if ($in_same_term) {
            if (!is_object_in_taxonomy($post->post_type, $taxonomy)) {
                return '';
            }
            $term_array = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
            if (!$term_array || is_wp_error($term_array)) {
                return '';
            }
            $join .= $wpdb->prepare(" AND tt.taxonomy = %s AND tt.term_id IN (" . implode(',', array_map('intval', $term_array)) . ")", $taxonomy);
        }
        $posts_in_ex_terms_sql = $wpdb->prepare("AND tt.taxonomy = %s", $taxonomy);
        if (!empty($excluded_terms)) {
            if (!is_array($excluded_terms)) {
                // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
                if (false !== strpos($excluded_terms, ' and ')) {
                    _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded terms.'), "'and'"));
                    $excluded_terms = explode(' and ', $excluded_terms);
                } else {
                    $excluded_terms = explode(',', $excluded_terms);
                }
            }
            $excluded_terms = array_map('intval', $excluded_terms);
            if (!empty($term_array)) {
                $excluded_terms = array_diff($excluded_terms, $term_array);
                $posts_in_ex_terms_sql = '';
            }
            if (!empty($excluded_terms)) {
                $posts_in_ex_terms_sql = $wpdb->prepare(" AND tt.taxonomy = %s AND tt.term_id NOT IN (" . implode($excluded_terms, ',') . ')', $taxonomy);
            }
        }
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms);
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s AND p.post_status = 'publish' {$posts_in_ex_terms_sql}", $current_post_date, $post->post_type), $in_same_term, $excluded_terms);
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1");
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}

WordPress Version: 3.7

/**
 * Retrieve adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @param bool $in_same_cat Optional. Whether post should be in a same category.
 * @param array|string $excluded_categories Optional. Array or comma-separated list of excluded category IDs.
 * @param bool $previous Optional. Whether to retrieve previous post.
 * @return mixed Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 */
function get_adjacent_post($in_same_cat = false, $excluded_categories = '', $previous = true)
{
    global $wpdb;
    if (!$post = get_post()) {
        return null;
    }
    $current_post_date = $post->post_date;
    $join = '';
    $posts_in_ex_cats_sql = '';
    if ($in_same_cat || !empty($excluded_categories)) {
        $join = " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
        if ($in_same_cat) {
            if (!is_object_in_taxonomy($post->post_type, 'category')) {
                return '';
            }
            $cat_array = wp_get_object_terms($post->ID, 'category', array('fields' => 'ids'));
            if (!$cat_array || is_wp_error($cat_array)) {
                return '';
            }
            $join .= " AND tt.taxonomy = 'category' AND tt.term_id IN (" . implode(',', $cat_array) . ")";
        }
        $posts_in_ex_cats_sql = "AND tt.taxonomy = 'category'";
        if (!empty($excluded_categories)) {
            if (!is_array($excluded_categories)) {
                // back-compat, $excluded_categories used to be IDs separated by " and "
                if (strpos($excluded_categories, ' and ') !== false) {
                    _deprecated_argument(__FUNCTION__, '3.3', sprintf(__('Use commas instead of %s to separate excluded categories.'), "'and'"));
                    $excluded_categories = explode(' and ', $excluded_categories);
                } else {
                    $excluded_categories = explode(',', $excluded_categories);
                }
            }
            $excluded_categories = array_map('intval', $excluded_categories);
            if (!empty($cat_array)) {
                $excluded_categories = array_diff($excluded_categories, $cat_array);
                $posts_in_ex_cats_sql = '';
            }
            if (!empty($excluded_categories)) {
                $posts_in_ex_cats_sql = " AND tt.taxonomy = 'category' AND tt.term_id NOT IN (" . implode($excluded_categories, ',') . ')';
            }
        }
    }
    $adjacent = $previous ? 'previous' : 'next';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';
    $join = apply_filters("get_{$adjacent}_post_join", $join, $in_same_cat, $excluded_categories);
    $where = apply_filters("get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date {$op} %s AND p.post_type = %s AND p.post_status = 'publish' {$posts_in_ex_cats_sql}", $current_post_date, $post->post_type), $in_same_cat, $excluded_categories);
    $sort = apply_filters("get_{$adjacent}_post_sort", "ORDER BY p.post_date {$order} LIMIT 1");
    $query = "SELECT p.ID FROM {$wpdb->posts} AS p {$join} {$where} {$sort}";
    $query_key = 'adjacent_post_' . md5($query);
    $result = wp_cache_get($query_key, 'counts');
    if (false !== $result) {
        if ($result) {
            $result = get_post($result);
        }
        return $result;
    }
    $result = $wpdb->get_var($query);
    if (null === $result) {
        $result = '';
    }
    wp_cache_set($query_key, $result, 'counts');
    if ($result) {
        $result = get_post($result);
    }
    return $result;
}