LAYOUTS - Question re: sub virtual pages of a single template

Hey there! This question may be pretty complex since we have a lot going on here, but here it is. I have a CPT and I originally built the single-cpt.php template to display the single CPT page. I have since upgraded to utilizing PRO LAYOUTS to render the single CPT page and it looks great. Here’s the issue I’m running into, I’m running what I call “virtual pages” as a child of the single CPT page.

Examples:
domain.com/cpt/single-cpt/ - that’s the regular single-cpt.php layout (or PRO Layouts)
domain.com/cpt/single-cpt/specials/ - that’s the “virtual page” I’m running as a child of the single CPT page.

This worked when I used single-cpt.php to output the single page but now it seems that PRO Layouts is no longer allowing me to use the virtual child page. Instead it’s just outputting the LAYOUT.

Here’s how I’m handling the child page:

public function load_filters() {
  add_filter('rewrite_rules_array', [$this, 'fsp_insertrules']);
  add_filter('query_vars', [$this, 'fsp_insertqv']);
  remove_filter('wp_head', [$this, 'rel_canonical']);
  add_filter('wp_head', [$this, 'fsp_rel_canonical']);
}

...

/**
   * Rules for Pages and rewrite rules
   *
   * @param $rules
   *
   * @return $rules
   */
  public function fsp_insertrules($rules) {
    $newrules = array();

    foreach (BM_Studio_Pages::$VIRTUAL_PAGE_SLUGS as $slug => $title) {
      $newrules['locations/([^/]+)/' . $slug . '/?$'] = 'index.php?locations=$matches[1]&fpage=' . $slug;
    }

    return $newrules + $rules;
  }

  /**
   * Tell WordPress to accept our custom query variable
   *
   * @param $vars
   *
   * @return $vars
   */
  public function fsp_insertqv($vars) {
    array_push($vars, 'fpage');
    return $vars;
  }

  /**
   * Remove WordPress's default canonical handling function
   *
   * @return string
   */
  public function fsp_rel_canonical() {
    global $current_fp, $wp_the_query;

    if (!is_singular()) {
      return;
    }

    if (!$id = $wp_the_query->get_queried_object_id()) {
      return;
    }

    $link = trailingslashit(get_permalink($id));

    // Make sure virtual page permalinks are canonical
    if (!empty($current_fp)) {
      $link .= user_trailingslashit($current_fp);
    }

    echo '<link rel="canonical" href="' . $link . '" />';
  }

THEN, in my single-cpt.php file, I would call the various templates as such:

$current_fp  = get_query_var('fpage');
if ($current_fp == 'specials' ) {
  x_get_view('studio', 'promo');
}

Wondering how I can get this to work with layouts. I don’t necessarily need LAyouts to be used for the virtual child page (though if it could, that would be AWESOME!) but I definitely need to be able to point the virtual page ($current_fp === 'specials') to a different PHP template. Is there a filter that runs before LAYOUTS init’s on the page to decide if the LAYOUT is used or if I can point to a different spot?

Thank you!

Hi @mattmintun,

Thanks for reaching out. Regretfully, I can’t really think of a way that this would work together because the Layout Builder works like this:

  • When Layouts are saved, cache a list of conditions
  • On each page load, check those conditions against the current state (URL, post type, user, etc.)
  • In the template_include hook, return a path to a PHP file for the resolved Layout
  • If not Layout is resolved, allow the theme to continue determining which PHP file to load.

This means the Layout builder will in fact prevent your file from running unfortunately. It sounds like you might have some specific reasons for these virtual pages, but you may just want to consider a hierarchical custom post type instead.

Thanks @alexander for the direction. That helped me figure this one out. I checked how PRO is using template_include and added my filter right after to change it if need be. Here’s my code (in case others want to try this out). The only caveat is that I’m setting the query_varfpage which informs what page the user is trying to access. If it’s a regular page (ie: the LAYOUTS page), fpage is empty/false. If the user is accessing a “virtual page” like /special/, then fpage = special.

add_filter('template_include', [$this, 'alter_template_include'], 98); // right after PROs template_include
/**
   * Alter Template include if the current page is a Virtual Page.
   * This will skip the LAYOUTS and go straight to the PHP file.
   *
   * @param string $template
   * @return $template
   */
  public function alter_template_include($template) {
    $current_fp = get_query_var('fpage'); // handles Virtual studio pages
    if ($current_fp !== '') {
      return get_stylesheet_directory() . '/single-locations.php';
    }
    return $template;
  }

I guess the only thing to make ultra clear is that this could have the capability of loading single-locations.php template for multiple page loads if fpage is ever filled in with anything on other page loads. But I know for a fact, I’m only setting that for these virtual CPT pages.

Overall, this is working! Regular SINGLE post type shows LAYOUTS and the virtual pages load the PHP template. THANK YOU!

Also, I can’t use hierarchical, I have 120+ studios that have their own “post” in the locations CPT. Adding a sub page to every single one of those would be nuts, maybe there’s a way to do that in one swoop that I overlooked though!

Hi @mattmintun,

Nice job! Glad to hear you got this working. Quick thought on your concern about the variable. $wp->request always contains the current URL path. You could check to see if it starts with your custom post type before checking the contents of the query var.

Quick aside: When we get to the Theme Options reboot cycle, we might adjust some of the template routing from the Layout builder. So if anything breaks down the road, I would recommend double checking the hook priority. Your technique should still work but there’s a chance we may need to adjust some priorities.

aww yes! Thanks @alexander - that makes sense re: hook priority in later updates. Thanks for your help!

You’re most welcome!

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.