0

The basic challenge: I have a 'normal' custom query, e.g. of news posts from a certain category and want to add in several elements (CTAs, generally speaking) at every nth position, while the loop of cta_elements (see below) is being repeated when more news posts exist than cta_elements could fill up. HTML example below, with a CTA element after every fourth (news) post:

<div class="news_list">
    <div class="news_item"></div>
    <div class="news_item"></div>
    <div class="news_item"></div>
    <div class="news_item"></div>

    <div class="cta_element" id="cta_1">Some different content</div>

    <div class="news_item"></div>
    <div class="news_item"></div>
    <div class="news_item"></div>
    <div class="news_item"></div>

    <div class="cta_element" id="cta_2">Some different content</div>


    <div class="news_item"></div>
    <div class="news_item"></div>
    <div class="news_item"></div>
    <div class="news_item"></div>

    <div class="cta_element" id="cta_3">Some different content</div>

    <div class="news_item"></div>
    <div class="news_item"></div>
    <div class="news_item"></div>
    <div class="news_item"></div>
    <!-- 1st CTA again, since there are only 3 CTAs, but more posts in news query. -->
    <div class="cta_element" id="cta_1">Some different content</div>
    ...
</div>

This means in fact that a query would have to be 'paused' in order to insert single elements of another type/loop (generally speaking), which themselves repeat once there are not enough cta_elements elements that could fill in at every further nth position (see example above).

Ideally, the cta_elements would be widgets from a custom widget area, so the user can add a) add different kinds of predefined 'widgets' to the list of cta_elements to be inserted and b) order them according to their wishes with a simple drag'n'drop and c) could have different widget areas for different loops, e.g. front page CTAs, archive page CTAs, author archive page CTAs.

Alternatively, one could possibly go with letting out the complications widgets possibly introduce and use a custom post type (and use categories in order to have different loops on different archive pages).

Is there any way of solving this in a simple way? Pardon me if the question itself is too complex and should be subdivided into several questions. I still want to find the most user-friendly and efficient way to solve this.

2

If you can get an index of your post then utilize % for CTA breaks.

<?php

// Your CTA Content

$cta_array = array(
    "Some different content 1",
    "Some different content ABC",
    "Some different content XYZ",
);
$cta_inx = 0; // index
$cta_len = count($cta_array); // max
$cta_interval = 5; // breaks every x posts

// Query

$args = array(
    'posts_per_page'   => 25, 
    'post_type'        => 'post', 
    'post_status'      => 'publish',
    'suppress_filters' => true 
);

$posts_array = get_posts( $args );

// LOOP

?><div class="news_list"><?php // news_list START

foreach ($posts_array as $inx => $post ){

    // News Item
    ?><div class="news_item"><?php echo $post->post_title; ?></div><?php

        // CTA
        if( $inx && ! ( $inx % $cta_interval )) {

            if( $cta_inx == $cta_len) $cta_inx = 0; // reset the index

            ?><div class="cta_element" id="cta_<?php echo $cta_inx; ?>"><?php echo $cta_array[$cta_inx]; ?></div><?php

            $cta_inx ++; // advance the cta
        }
    } 
?></div><?php // news_list END

That should output:

> Post Title
> Post Title
> Post Title
> Post Title
> Post Title
Some different content 1
> Post Title
> Post Title
> Post Title
> Post Title
> Post Title
Some different content ABC
> Post Title
> Post Title
> Post Title
> Post Title
> Post Title
Some different content XYZ
> Post Title
> Post Title
> Post Title
> Post Title
> Post Title
Some different content 1
> Post Title
> Post Title
> Post Title
> Post Title
> Post Title
Some different content ABC
> Post Title
> Post Title
> Post Title
> Post Title
> Post Title
Some different content XYZ
  • Mh, that doesn’t really solve the problem, as the CTAs should not be static content inserted into the template file, but rather another post loop/query (widgets at best, but that causes too many problems after all it seems). Thanks anyway, this would supposively work well with an array of static elements! – physalis Dec 1 '15 at 7:57
  • If you get the CTAs with get_posts then you're golden. – jgraup Dec 1 '15 at 15:07
  • Also, if it helps, use the loop to collect the data into an array and then run your logic on it. Nothing states you HAVE to output to the screen within an array. – jgraup Dec 1 '15 at 15:39
  • Sorry for getting back so late, hope you had a fine holiday time everyone! I think this is way is by far the most practical way, since one does not have to fill in breaks manually. However, with my limited knowledge I have a hard time creating an array from a second loop or even from widgets from a specific widget area [best even with a non-fixed number of widgets, depending on how much are added to the area] to replace the simple string array from above. Also, for the first CTA the count is wrong; the first CTA shows after six news posts, the following correctly after five news posts. – physalis Jan 4 '16 at 17:17
0

I'd go the custom post type / custom taxonomy route. Then you can set up your archives with a for/each loop to count off the number of posts and insert the CTAs from the relevant CPT/taxonomy query.

I've found the plugin Intuitive Custom Post Order works well for this kind of thing; you can tell your users to filter the CTAs by the CTA-group taxonomy they want to organize, then drag-and-drop the CTAs into the order they want. Just make sure your CPT query is ordered by menu_order and voila.

  • The CPT route seems more doable even to my knowledge, and the ordering via plugin is something I already do on other occasions, thank you :). Any hints on how to do the for/each loop that inserts CPT posts into the main query? – physalis Nov 30 '15 at 16:33
0

What about sth like this?

<?php $max_posts = get_option('posts_per_page '); ?>

<?php global $paged; ?>
//Get $paged to have a good pagination index
<?php 
  if ( get_query_var('paged') ) { $paged = get_query_var('paged'); }
  elseif ( get_query_var('page') ) { $paged = get_query_var('page'); }
  else { $paged = 1; }  
?>

//Max news per block
<?php $num_news = 4; ?>

//First 4 news
<section class="news">
  <?php $args = array('posts_per_page' => $max_posts,
                      'paged' => $paged ); ?>
  <?php $cont = 0; ?>
  <?php $query = new WP_Query( $args ); ?>          
  <?php if ( $query->have_posts() ) : ?>            
    <?php while ( $query->have_posts() ) : $query->the_post(); ?>        
    <article class="news_item">
      Your code here
    </article>
    <?php if($cont++ >= $num_news) break; //Only 4 posts/news in this block ?>
    <?php endwhile ?>    
  <?php endif; ?>      
</section>

//After break I show the first widget area  
<div class="cta_element" id="cta_1">Your widget area 1</div>

//Second loop, another 4 items...
<?php if ( $query->have_posts() ) : ?>      
  <?php $cont = 0; ?>
  <section class="news">      
    <?php while ( $query->have_posts() ) : $query->the_post(); ?>
    <article class="news_item">
      Your code here
    </article>
    <?php if($cont++ >= $num_news) break; //Only 4 posts/news in this block ?>
    <?php endwhile ?>          
  </section>          
<?php endif; ?>

//Second widget area
<div class="cta_element" id="cta_1">Your widget area 1</div>

Edit: To get a specific widget in a sidebar. Not too clean but I think it should work.

<?php
function get_widget_from_sidebar($sidebar,$widget_index){
  ob_start();
  $widgets = dynamic_sidebar($sidebar);
  if($widgets){
    $html = ob_get_contents();
    $widgets_array = explode("<li",$html);
    ob_end_clean();
    return $widgets_array[$widget_index];
  }
  return "";
}

//Call the function like this
echo get_widget_from_sidebar('id_of_sidebar', $cont);
  • Hey Carlos, looks good as a basis to solving the problem. Actually the CTA thing should each be one widget in the order they are arranged by the backend user. Let’s say we have three widgets in that order: 1x "Get the app here", 1x "Subscribe now", 1x "Get in touch". After the first four news posts, the first widget named "Get the app here" should appear, after the next four news the "Subscribe now" appears, next four news, then "Get in touch" appears, after the next four news, the first "Get the app" appears again (infinite loop of widgets until the end of the news loop has been reached). – physalis Dec 1 '15 at 8:02
  • So it would be more like, "after 4 posts, show me each one widget (in the order they are saved to the widget area in the backend, one after another) from widget area X, while repeating widgets until the news query posts ended". – physalis Dec 1 '15 at 8:21
  • Check out my edit ;D – Carlos Faria Dec 2 '15 at 8:46
0

For anyone interested in something similar, this is what a fellow pro did come up with, it’s 1. registering a specific sidebar for my widget insertion, 2. getting the DOM node from the sidebar as HTML string and 3. putting it together while looping through the available widgets.

Put this into your functions.php:

// Register sidebar for repeated widgets
function register_custom_sidebar() {

    register_sidebar(array(
        'name' => "CTAs — Home",
        'id' => 'widgets_home',
        'description' => "Widgets will be displayed after every 3rd post",
        'before_widget' => '<li id="%1$s" class="widget %2$s">',
        'after_widget' => '</li>',
        'before_title' => '<h2 class="widgettitle">',
        'after_title' => '</h2>',
    ));
}
add_action('widgets_init', 'register_custom_sidebar');

// Return dom node from other document as html string
function return_dom_node_as_html($element) {

    $newdoc = new DOMDocument();
    $newdoc->appendChild($newdoc->importNode($element, TRUE));

    return $newdoc->saveHTML();
}

And then create or adapt a template (e.g. page template like in this example, whatever works for you) with the following:

<?php 
/* Template Name: My CTA loop page
 *
 */
get_header();
?>

<div id="primary" class="content-area">
    <main id="main" class="site-main" role="main">

        <?php while (have_posts()) : the_post(); ?>

            <?php
            // Your custom query
            $args = [
                "post_type" => "post",
                "posts_per_page" => "-1"
            ];
            $custom_posts = new WP_Query($args);



            // Catch output of sidebar in string
            ob_start();
            dynamic_sidebar("widgets_home");
            $sidebar_output = ob_get_clean();

            // Create DOMDocument with string (and set encoding to utf-8)
            $dom = new DOMDocument;
            $dom->loadHTML('<?xml encoding="utf-8" ?>' . $sidebar_output);

            // Get IDs of the elements in your sidebar, e.g. "text-2"
            global $_wp_sidebars_widgets;
            $sidebar_element_ids = $_wp_sidebars_widgets["widgets_home"]; // Use ID of your sidebar
            // Save single widgets as html string in array
            $sidebar_elements = [];

            foreach ($sidebar_element_ids as $sidebar_element_id):

                // Get widget by ID
                $element = $dom->getElementById($sidebar_element_id);
                // Convert it to string (function return_dom_node_as_html() must be in functions.php)
                $sidebar_elements[] = return_dom_node_as_html($element);

            endforeach;

            $widget_intervall = 3; // After how many post a widget appears
            $post_count = 0;
            $element_count = 0;

            while ($custom_posts->have_posts()): $custom_posts->the_post();

                echo "<p>" . the_title() . "</p>"; // Whatever you want to display from your news posts (= main loop)

                $post_count++;

                if (!empty($sidebar_elements) && $post_count % $widget_intervall === 0):

                    // Echo the widget
                    echo $sidebar_elements[$element_count];
                    $element_count++;
                    // Restart after the last widget
                    if ($element_count == count($sidebar_elements)):
                        $element_count = 0;
                    endif;

                endif;

            endwhile;

            wp_reset_postdata();
            ?>

            <?php
            // If comments are open or we have at least one comment, load up the comment template
            if (comments_open() || get_comments_number()) :
                comments_template();
            endif;
            ?>

        <?php endwhile; // end of the loop.    ?>

    </main><!-- #main -->
</div><!-- #primary -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>

I had some minor trouble with the RSS widget when I insert it without data, but apart from that it seems to work fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.