Forum

Thread tagged as: Question, Problem, Suggestions

Bug in perch_blog_custom when skipping template and returning HTML ...

(This may affect more than just perch_blog_custom, as I think other perch functions probably use the same underlying code) (EDIT: markdown is nerfing the indentation whitespace in my codeblocks, so the patch below may not work -- let me know if there's another way to send it to you)

Here is what I am doing:

$html = perch_blog_custom(array(
    'count' => 5,
    'template' => 'pavlus_posts.html',
    'sort' => 'postDateTime',
    'sort-order' => 'DESC',
    'skip-template' => true,
    'return-html' => true,
));

And then what I get inside of the $html variable is this -- it's leaving the unprocessed <perch:form, :input, :error, :label>, etc. tags in the html output.

<div class="blog-entry">

    <h2><a href="/my-blog/post.php?s=2016-08-26-my-post-title" rel="bookmark" class="entry-title">My Post Title</a></h2>

    <p class="entry-published date">26 August 2016</p>

    <div class="description e-content">
        <p>Lorem ipsum dolor sit amet ... etc.</p>
    </div>
    <p class='comment-count'>Comments: 0</p>

    <perch:form template="/addons/apps/perch_blog/templates/blog/pavlus_posts.html" id="comment" method="post" app="perch_blog" class="comment-form form-horizontal" action="/my-blog/post.php?s=2016-08-26-my-post-title">
        <fieldset>
            <legend>Leave a comment</legend>
            <div>
                <perch:input type="text" id="commentName" required="true" label="Name" antispam="name" placeholder="Your Name" />
                <perch:error for="commentName" type="required">Required</perch:error>
            </div>
            <div>
                <perch:input type="email" id="commentEmail" required="true" label="Email" antispam="email" placeholder="Email Address" />
                <perch:error for="commentEmail" type="required">Required</perch:error>
                <perch:error for="commentEmail" type="format">Check format of address</perch:error>
            </div>
            <div>
                <perch:label for="commentHTML">Comments</perch:label>
                <br />
                <perch:input type="textarea" id="commentHTML" required="true" label="Message" antispam="body" />
                <perch:error for="commentHTML" type="required">Required</perch:error>
            </div>
            <div>
                <perch:input type="hidden" id="postID" value="71" />
                <perch:input type="submit" id="submitComment" value="Submit" />
            </div>

        </fieldset>
        <perch:success>
            <p>Thank you. Your comment has been submitted and will appear on the site once approved.</p>
        </perch:success>

    </perch:form>

    <hr />

</div>
...

<div class="paging">
Page 1 of 14

<a href="/my-blog/?page=2">Next</a>

</div>

(FWIW, here are the contents of pavlus_posts.html):

<div class="blog-entry">
        <h2><a href="<perch:blog id="postURL" />" rel="bookmark" class="entry-title"><perch:blog id="postTitle" /></a></h2>

        <perch:if exists="image"><img src="<perch:blog id="image" type="image" width="50" height="50" crop="true" />" alt="<perch:blog id="postTitle" />" /></perch:if>

        <p class="entry-published date"><perch:blog id="postDateTime" format="%d %B %Y" /></p>

        <div class="description e-content">
            <perch:blog id="postDescHTML" type="textarea" label="Post" order="2" editor="markitup" markdown="true" size="xxl autowidth" required="true" />
        </div>
        <p class='comment-count'>Comments: <perch:blog id="postCommentCount"/></p>
        <perch:if id="postAllowComments" value="1">

            <perch:form id="comment" method="post" app="perch_blog" class="comment-form form-horizontal" action="<perch:blog id="postURL" />">
                <fieldset>
                   <legend>Leave a comment</legend>
                    <div>
                        <perch:input type="text" id="commentName" required="true" label="Name" antispam="name" placeholder="Your Name" />
                        <perch:error for="commentName" type="required">Required</perch:error>
                    </div>
                    <div>
                        <perch:input type="email" id="commentEmail" required="true" label="Email" antispam="email" placeholder="Email Address" />
                        <perch:error for="commentEmail" type="required">Required</perch:error>
                        <perch:error for="commentEmail" type="format">Check format of address</perch:error>
                    </div>
                    <div>
                        <perch:label for="commentHTML">Comments</perch:label>
                        <br />
                        <perch:input type="textarea" id="commentHTML" required="true" label="Message" antispam="body" />
                        <perch:error for="commentHTML" type="required">Required</perch:error>
                    </div>
                    <div>
                        <perch:input type="hidden" id="postID" value="<perch:blog id="postID" />" />
                        <perch:input type="submit" id="submitComment" value="Submit" />
                    </div>

                </fieldset>
                <perch:success>
                    <p>Thank you. Your comment has been submitted and will appear on the site once approved.</p>
                </perch:success>

            </perch:form>
        </perch:if>
        <hr />
    </div>

<perch:after>
    <perch:if exists="paging">
        <div class="paging">
            Page <perch:blog id="current_page" /> of <perch:blog id="number_of_pages" />
            <perch:blog id="page_links" encode="false" />
            <perch:if exists="not_first_page">
                <a href="<perch:blog id="prev_url" encode="false" />">Previous</a>
            </perch:if>
            <perch:if exists="not_last_page">
                <a href="<perch:blog id="next_url" encode="false" />">Next</a>
            </perch:if>
        </div>
    </perch:if>
</perch:after>

If I turn off the 'skip-template' option, it processes those correctly, and turns them into what they need to be ... but then I can't save the content into a variable like I want to.

I tried to work around the problem by using output buffering, like so:

ob_start();
perch_blog_custom(array(
    'count' => 5,
    'template' => 'pavlus_posts.html',
    'sort' => 'postDateTime',
    'sort-order' => 'DESC',
    //'skip-template' => true,
    //'return-html' => true,
));
$html = ob_end_flush();

But for some reason I couldn't get it to work; I think perch is doing something that doesn't allow me to output buffer it (frustrating! Is it? If so, why??).

So I looked into the source code, and I found that in PerchFactory.class.php, in get_filtered_listing_from_index(), and get_filtered_listing(), if the 'skip-template' option is set, it returns from the function before doing its post-processing on the HTML ... this seems like a bug to me. Shouldn't it be doing that either way?

I was using an older version of Perch (2.7.10), but found the exact same code in the latest version (3.0.4) and the latest 2.8 version, 2.8.34.

Anyway, so I went ahead and fixed the logic in that function, so that it will call its post-processing if necessary whether or not skip-template is set. I can't seem to attach files here, otherwise I would attach the git repository I made from it so you could see the commits with notes, but here is contents of a patch I created (in git patch format):

diff --git a/perch/core/lib/PerchFactory.class.php b/perch/core/lib/PerchFactory.class.php
index cbc0099..8a37cab 100644
--- a/perch/core/lib/PerchFactory.class.php
+++ b/perch/core/lib/PerchFactory.class.php
@@ -670,98 +670,101 @@ class PerchFactory

             if (PerchUtil::count($items)) {
                 $html = $Template->render_group($items, true);
             }else{
                 $Template->use_noresults();
                 $html = $Template->render(array());
             }
         }


-        if (isset($opts['skip-template']) && $opts['skip-template']==true) {
+        // this post-processing should happen whether we skip-template or not
+        if (strpos($html, '<perch:')!==false) {
+            $Template = new PerchTemplate();
+            $html        = $Template->apply_runtime_post_processing($html);
+        }
+

-            if (isset($opts['api']) && $opts['api']==true) {
-                $api = true;
+        if (!isset($opts['skip-template']) || $opts['skip-template']==false) {
+            return $html;
+        }
+
+        // things below only applicable when skip-template is set
+        if (isset($opts['api']) && $opts['api']==true) {
+            $api = true;
+        } else {
+            $api = false;
+        }
+
+        if ($single_mode) {
+            if ($api) {
+                return $Item->to_array_for_api();
             } else {
-                $api = false;
+                return $Item->to_array();
             }
+        }

-            if ($single_mode) {
+        $processed_vars = array();
+        if (PerchUtil::count($items)) {
+            foreach($items as $Item) {
                 if ($api) {
-                    return $Item->to_array_for_api();
+                    $Item->prefix_vars = false;
+                    $processed_vars[] = $Item->to_array_for_api();
                 } else {
-                    return $Item->to_array();
-                }
-            } 
-
-            $processed_vars = array();
-            if (PerchUtil::count($items)) {
-                foreach($items as $Item) {
-                    if ($api) {
-                        $Item->prefix_vars = false;
-                        $processed_vars[] = $Item->to_array_for_api();
-                    } else {
-                        $processed_vars[] = $Item->to_array();    
-                    }
+                    $processed_vars[] = $Item->to_array();
                 }
             }
+        }

-        
-            if (PerchUtil::count($processed_vars)) {

-                if ($api) {
-                    $field_type_map = $Template->get_field_type_map($this->namespace);
-                }
+        if (PerchUtil::count($processed_vars)) {

-                $category_field_ids    = $Template->find_all_tag_ids('categories');
+            if ($api) {
+                $field_type_map = $Template->get_field_type_map($this->namespace);
+            }

-                foreach($processed_vars as &$item) {
-                    if (PerchUtil::count($item)) {
-                        foreach($item as $key => &$field) {
+            $category_field_ids    = $Template->find_all_tag_ids('categories');

-                            if ($api) {
+            foreach($processed_vars as &$item) {
+                if (PerchUtil::count($item)) {
+                    foreach($item as $key => &$field) {

-                                if (array_key_exists($key, $field_type_map)) {
-                                    $field = $field_type_map[$key]->get_api_value($field);
-                                    continue;
-                                }
-                            } else {
+                        if ($api) {

-                                if (in_array($key, $category_field_ids)) {
-                                    $field = $this->_process_category_field($field);
-                                }
+                            if (array_key_exists($key, $field_type_map)) {
+                                $field = $field_type_map[$key]->get_api_value($field);
+                                continue;
                             }
+                        } else {

-                            if (is_array($field) && isset($field['processed'])) {
-                                $field = $field['processed'];
-                            }
-                            if (is_array($field) && isset($field['_default'])) {
-                                $field = $field['_default'];
+                            if (in_array($key, $category_field_ids)) {
+                                $field = $this->_process_category_field($field);
                             }
                         }
+
+                        if (is_array($field) && isset($field['processed'])) {
+                            $field = $field['processed'];
+                        }
+                        if (is_array($field) && isset($field['_default'])) {
+                            $field = $field['_default'];
+                        }
                     }
                 }
             }
-
-            if (isset($opts['return-html'])&& $opts['return-html']==true) {
-                $processed_vars['html'] = $html;
-            }
-
-            return $processed_vars;
         }

-        if (strpos($html, '<perch:')!==false) {
-            $Template = new PerchTemplate();
-            $html        = $Template->apply_runtime_post_processing($html);
+        if (isset($opts['return-html'])&& $opts['return-html']==true) {
+            $processed_vars['html'] = $html;
         }

-        return $html;
+        return $processed_vars;
+
     }

     public function get_filtered_listing_from_index($opts, $where_callback, $pre_template_callback=null)
     {
         $Perch = Perch::fetch();

         $index_table = PERCH_DB_PREFIX.$this->index_table;

         $where = array();

@@ -1254,109 +1257,112 @@ class PerchFactory
                 }

             }else{
                 $Template->use_noresults();
                 $html = $Template->render(array());
             }

         }


-        if (isset($opts['skip-template']) && $opts['skip-template']==true) {
+        // make sure this happens before return whether we skip template or not
+        if (is_array($html)) {
+            // split-items
+            if (PerchUtil::count($html)) {
+                $Template = new PerchTemplate();
+                foreach($html as &$html_item) {
+                    if (strpos($html_item, '<perch:')!==false) {
+                        $html_item = $Template->apply_runtime_post_processing($html_item);
+                    }
+                }
+            }
+        }else{
+            if (strpos($html, '<perch:')!==false) {
+                $Template = new PerchTemplate();
+                $html     = $Template->apply_runtime_post_processing($html);
+            }
+        }
+

-            if (isset($opts['api']) && $opts['api']==true) {
-                $api = true;
+        // if not skipping template, we can return html now
+        if (! isset($opts['skip-template']) || $opts['skip-template']==false) {
+            return $html;
+        }
+
+        if (isset($opts['api']) && $opts['api']==true) {
+            $api = true;
+        } else {
+            $api = false;
+        }
+
+        if ($single_mode) {
+            if ($api) {
+                return $Item->to_array_for_api();
             } else {
-                $api = false;
+                return $Item->to_array();
             }
+        }

-            if ($single_mode) {
-                if ($api) {
-                    return $Item->to_array_for_api();
+        $processed_vars = array();
+        if (PerchUtil::count($items)) {
+            foreach($items as $Item) {
+                if (isset($opts['api']) && $opts['api']) {
+                    $Item->prefix_vars = false;
+                    $processed_vars[] = $Item->to_array_for_api();
                 } else {
-                    return $Item->to_array();      
+                    $processed_vars[] = $Item->to_array();
                 }
-            } 

-            $processed_vars = array();
-            if (PerchUtil::count($items)) {
-                foreach($items as $Item) {
-                    if (isset($opts['api']) && $opts['api']) {
-                        $Item->prefix_vars = false;
-                        $processed_vars[] = $Item->to_array_for_api();
-                    } else {
-                        $processed_vars[] = $Item->to_array();    
-                    }
-                    
-                }
             }
+        }

-            if (PerchUtil::count($processed_vars)) {
+        if (PerchUtil::count($processed_vars)) {

-                if ($api) {
-                    $field_type_map = $Template->get_field_type_map($this->namespace);
-                }
+            if ($api) {
+                $field_type_map = $Template->get_field_type_map($this->namespace);
+            }

-                $category_field_ids    = $Template->find_all_tag_ids('categories');
-                //PerchUtil::debug($category_field_ids, 'notice');
-
-                foreach($processed_vars as &$item) {
-                    if (PerchUtil::count($item)) {
-                        foreach($item as $key => &$field) {
-                            
-                            if ($api) {
-                                if (array_key_exists($key, $field_type_map)) {
-                                    $field = $field_type_map[$key]->get_api_value($field);
-                                    continue;
-                                }
-                            } else {
-                                if (in_array($key, $category_field_ids)) {
-                                    $field = $this->_process_category_field($field);
-                                }
-                            }
+            $category_field_ids    = $Template->find_all_tag_ids('categories');
+            //PerchUtil::debug($category_field_ids, 'notice');

-                            if (is_array($field) && isset($field['processed'])) {
-                                $field = $field['processed'];
+            foreach($processed_vars as &$item) {
+                if (PerchUtil::count($item)) {
+                    foreach($item as $key => &$field) {
+
+                        if ($api) {
+                            if (array_key_exists($key, $field_type_map)) {
+                                $field = $field_type_map[$key]->get_api_value($field);
+                                continue;
                             }
-                            if (is_array($field) && isset($field['_default'])) {
-                                $field = $field['_default'];
+                        } else {
+                            if (in_array($key, $category_field_ids)) {
+                                $field = $this->_process_category_field($field);
                             }
                         }
+
+                        if (is_array($field) && isset($field['processed'])) {
+                            $field = $field['processed'];
+                        }
+                        if (is_array($field) && isset($field['_default'])) {
+                            $field = $field['_default'];
+                        }
                     }
                 }
             }
-
-            if (isset($opts['return-html'])&& $opts['return-html']==true) {
-                $processed_vars['html'] = $html;
-            }
-
-            return $processed_vars;
         }

-        if (is_array($html)) {
-            // split-items
-            if (PerchUtil::count($html)) {
-                $Template = new PerchTemplate();
-                foreach($html as &$html_item) {
-                    if (strpos($html_item, '<perch:')!==false) {
-                        $html_item = $Template->apply_runtime_post_processing($html_item);
-                    }
-                }
-            }
-        }else{
-            if (strpos($html, '<perch:')!==false) {
-                $Template = new PerchTemplate();
-                $html     = $Template->apply_runtime_post_processing($html);
-            }
+        if (isset($opts['return-html'])&& $opts['return-html']==true) {
+            $processed_vars['html'] = $html;
         }

-        return $html;
+        return $processed_vars;
+
     }

     private function _get_filter_sub_sql($field, $items, $negative_match=false, $match, $fuzzy, $where_clause)
     {
         if (count($items)) {

             $index_table = PERCH_DB_PREFIX.$this->index_table;

             $cat_sql = 'SELECT DISTINCT idx.itemID FROM '.$index_table.' idx JOIN '.$this->table.' main ON idx.itemID=main.'.$this->pk.' AND '.$where_clause. ' AND ';

Aaron Wallentine

Aaron Wallentine 0 points

  • 4 years ago
Drew McLellan

Drew McLellan 2638 points
Perch Support

I think perch is doing something that doesn't allow me to output buffer it (frustrating! Is it? If so, why??).

It is, yes. Here's why and how to turn it off: https://grabaperch.com/blog/archive/progressive-output-flushing

Thank you for that, Drew ... that is helpful to know about output buffering.

I still think the above is a bug. Any feedback on that?

I'm able to work around it, however, if I pass the third parameter, 'return', as true, instead of relying on the 'return-html' flag. Seems like behavior should be identical either way, but it's not.

$perch_content['blog_posts'] = perch_blog_custom(array(
    'count' => 5,
    'template' => 'my_posts.html',
    'sort' => 'postDateTime',
    'sort-order' => 'DESC',
    //'skip-template' => true,
    //'return-html' => true,
), true);
Drew McLellan

Drew McLellan 2638 points
Perch Support

Yes, I think that's right.