Support

Account

Home Forums ACF PRO have_rows causing infinite loop when getting repeater values from another page

Solved

have_rows causing infinite loop when getting repeater values from another page

  • There wasn’t enough space in the title to fully explain what I’m doing here, so here goes…

    There are two pages, both using flexible content layouts (sections). One layout within sections allows you to get a section from another page based on a custom field in the layout itself (section_id).

    That works nicely – I can get simple layouts from the target page as long as I don’t use have_rows().

    I’ve simplified my code quite a bit for troubleshooting, so all it’ll do is spit out “Has Callouts”.

    <?php
    if( have_rows('sections') ) :
      while ( have_rows('sections') ) : the_row();
        if( $row_layout == 'layout_reference' ) :
          // get the other page
          $target_page = get_sub_field('target_page');
          $target_section = get_sub_field('target_section');
    
          if( have_rows('sections', $target_page) ) :
            while ( have_rows('sections', $target_page) ) : the_row();
              if ( get_sub_field('section_id') == $target_section ) {
                if( have_rows('callout') ) :  // this is where the infinite loop happens
                  echo 'Has Callouts';
                endif;
              }
            endwhile;
          endif;  // have_rows
        endif;  // row_layout == layout_reference
      endwhile;
    endif;
    ?>

    Commenting out the if( have_rows(‘callout’) ) block causes the page to load normally.

    It seems that this last have_rows causes the while ( have_rows(‘sections’) ) to reset, and displays all of the flexible layouts again, triggering the loop.

    Any thoughts?

  • I don’t know if this is a bug or it’s something else, but I don’t think I could can figure it out unless I see it in action. Can you export the field group or groups involved and attach them, you might need to put them into a .zip file. Then I can import them into a test site to see what happens.

  • Hi John,
    Please see attached! I’ve re-created a simplified version of the field group, and have re-tested using updated code. It’s still doing the same – it resets the first loop, causing the page to keep spitting everything out again and again.

    Thanks!

  • I love recursive code, but I have to tell you that this is recursive in a way the makes my head hurt just a bit.

    While I was looking at it and trying to figure out how to make it into a single recursive function that would be easier to follow I noticed that the block_id is a text field and it represents a post ID and I’m pretty sure that it needs to be an integer. It’s a stab in the dark but try changing to this

    
    $target_page = intval(get_sub_field('target_page'));
    

    If that doesn’t do it I’d suggest trying to turn this into a recursive function, there could be a problem with using different template parts.

    Also, I don’t see any way that it can stop if the pages reference each other back and forth, which is another reason I would suggest using a recursive function. It would make it easier to add a safety switch, some type of check to make sure you are not looping forever because of what’s been entered.

  • Hi John,
    I’ve drawn up a little diagram to further explain what I’m doing here.

    Here’s a point-by-point explanation:

    • Both pages use flexible content layouts, so they both include have_rows()
    • Page 2 includes a flexible content layout that has a repeater inside
    • That block has a text field “block_id”, where a user can give it a unique name (we’re using “targetblock”)
    • We want to include “targetblock” on another page
    • Page 1 includes a flexible content layout, “block_reference” that includes two fields:
      • “target_block” – text field where users can enter the unique name of the target layout to include
      • “target_page” – post object field where users can select the page that has the target layout on it
    • “block_reference” does a few things:
      • Loops through the flexible layouts on the target_page
      • Checks if there’s a “block_id” field that matches “target_block”
      • If found, it includes whatever that flexible content layout would normally include (in this case, a repeater field)

    All of this is working as long as the block that we’re trying to pull from Page 2 doesn’t include another have_rows() call. I’ve explored the plugin code a bit, and it looks like there’s a case where it breaks in api/api-template.php around line 489.

    I’m going to do some more testing, but the main problem is that it’s resetting the have_rows() loop for Page 1, causing the entire page to be re-generated, which includes the item from Page 2 again, causing the infinite loop.

    I’m aware of the potential for including another “block_reference” from another page, and have added some code to avoid this.

    Thanks again for your help,
    Scott

  • Okay, ultra simple version of this, with a temporary workaround.

    The problem is that have_rows uses $post_id from the current page when you don’t specifically set it, even if it’s nested in a have_rows that has it specifically set.

    1. have_rows(‘field’) <- uses current (original) $post_id
      1. have_rows(‘field’, $otherpostobj) <- uses $otherpostobj
        1. have_rows(‘otherfield’) <- uses original $post_id instead of $otherpostobj

    The workaround is to setup_postdata before the third-level have_rows, and wp_reset_postdata it once we’re done with it.

    Can this be updated to use the parent have_rows post if it’s been specifically set?

  • I’m not the developer, I just do my best to answer questions here. I can mark this thread for the developer to look at when he get time.

    My suspicion is that it has something to do with a combination of not providing a post id for the repeater and the fact that you’re using get_template_part() which creates a disconnect.

    get_template_part() includes a lot of global variable values so that the template part has access to them, like the current $post and $wp_query. This might have something to do with the post id being reset, this is likely the reason your post id value is being reset.

    You could try using php include instead of get_template_part which would rule this out since the variables will transfer to the included file and won’t be overridden by the global calls in get_template_part.

    But this causes another problem where $target_page and $target_section will be overwritten every time that _block_reference.php is included so you’ll end up with a different problem.

    Like I said above, you’ll either need to do the work around that you outline or create a recursive function that would eliminate both problems.

  • Hi John,
    We’ve tried it without get_template_part, and it still does the same thing. My first message in this thread included that code.

    Yes, please pass this up – would be great to have a fix that doesn’t include setup_postdata.

    Thanks!

  • I’ve done what i can do, and I also let it peculate in my head for a while.

    The reason that I wasn’t seeing how to make this recursive was that I was over complicating it, trying to get your initial loop into the recursion and do the recursion at the same places that you were loading alternate files and also including a check for the target section, but all that really wasn’t needed. Instead the only place it really needs to recurse is when doing a nested block reference.

    I haven’t tested this out 100%, but it looks like it should work.

    Yeah, the whiles and ifs are nested a bit deep, but I couldn’t see a way to avoid that.

    
    <?php 
      
      // start the blocks content
      // this is the initial loop
      if (have_rows('blocks')) {
        while (have_rows('blocks')) {
          the_row();
          $layout = get_row_layout();
          if ($layout == 'block_reference') {
            $target_page = get_sub_field('target_page');
            $target_section = get_sub_field('target_block_id');
            flexible_block_reference($target_page, $target_section);
          } else {
            // flexible_repeater_block
            if (have_rows('repeater_items')) {
              while (have_rows('repeater_items')) {
                the_row();
                echo 'Item: ' . get_sub_field('title') . '<br />';
              } // end while
            } // end if
          } // end if/else
        } // end while
      } // end if
      
      function flexible_block_reference($post_id, $section) {
        // recurstive function 
        if (have_rows('blocks', $post_id)) {
          while (have_rows('blocks', $post_id)) {
            the_row();
            if (get_sub_field('block_id') == $section) {
              $layout = get_row_layout();
              if ($layout == 'block_reference') {
                $target_page = get_sub_field('target_page');
                $target_section = get_sub_field('target_block_id');
                // recurse
                flexible_block_reference($target_page, $target_section);
              } else {
                // flexible_repeater_block
                if (have_rows('repeater_items')) {
                  while (have_rows('repeater_items')) {
                    the_row();
                    echo 'Item: ' . get_sub_field('title') . '<br />';
                  } // end while
                } // end if
              } // end if/else
            } // end while
          } // end if target section
        } // end if
      } // end function
      
      
    ?>
    
  • Hi John,
    This is an interesting workaround, but in the end, all it’s doing is passing in the post_id to have_rows(). I’ve accomplished this in a simplified manner using setup_postdata() a few comments back. This method also avoids having to update all of the other templates I’m using for the large number of flexible content layouts included on each site.

    I think we can call this one closed for now, but thanks for investigating it with me!

Viewing 11 posts - 1 through 11 (of 11 total)

You must be logged in to reply to this topic.