Custom post type, organized by categories

This is a lot simpler than you might believe, first, I must dispel some misconceptions:

  • post_type is not a parameter of get_categories, isn’t listed in the documentation, and wouldn’t make sense. Categories are terms not posts, they don’t have a post type, and this API call retrieves terms, it has no business looking into the posts table
  • When the page just suddenly abruptly stops, it usually means there was a PHP fatal error. Check your PHP error logs for the reason, and look up debugging in WordPress
  • Reusing the post categories is messy, things would be much better if you used a custom taxonomy instead. Additionally it makes the APIs more useful, as you don’t have to figure out how many of a term are one post type, and how many are of another.
  • APIs such as get_categories are higher level helper functions, mostly there for backwards compatibility. As a result they tend to have “strings attached”. Use the terms API instead, it works the same for all taxonomies be they tags, categories, or something you registered yourself.
  • You never checked if what you were given was what you expected! What if no categories had been found! Or it returned a WP_Error object?
  • get_template_part is an amazing function, don’t ignore it and write super long files, break it up
  • There’s no escaping in your code example, this is a major security issue. Anybody can insert any HTML they want by putting it in a term title, use esc_html etc

So finally, you kind of had the answer to begin with:

grab the terms
for each term
    display the terms title
    grab the posts of type "attractions" that have that term
    for each post
        display it

So lets break that apart and fetch the terms:

$terms = get_terms([
    'taxonomy' => 'category',
    'order' => 'asc',
    'orderby' => 'name',
]);
if ( !empty( $terms ) && !is_wp_error( $terms ) ) {
    echo '<h3>'.esc_html( $term->name ).'</h3>';
    foreach ( $terms as $term ) {
        // .. display posts
    }
}

Then for your post loop:

$args = [
    'post_type' => 'attractions',
    'cat' => $term->term_id,
];
$query = new WP_Query( $args );
// etc..