• Resolved GatorDog

    (@gatordog)


    We’ve had a request to add compatibility with your plugin to the GatorCache plugin. I took a look at your code and it probably would be incompatible with any caching plugin or any other plugin that performs output buffering. The issue is here:

    add_action('template_redirect', 'jch_buffer_start', 0);
    add_action('shutdown', 'jch_buffer_end', 0);

    You’re manually shutting down the buffering when you don’t really have to. Additionally, you can’t be really sure what buffer you’re ending in the jch_buffer_end function if there are other output buffers present. The following minor adjustment would work better, accomplish the same thing and be more compatible with other plugins that also do output buffering:

    add_action('template_redirect', 'jch_buffer_start', 0);
    // remove this line
    // add_action('shutdown', 'jch_buffer_end', 0);
    // add the buffer end function as a callback to ob_start
    function jch_buffer_start()
    {
            ob_start('jch_buffer_end');
    }
    // modify this so it uses the passed buffer contents
    function jch_buffer_end($sHtml)
    {
            // remove this section, it is no longer needed
            /*$sHtml = '';
    
            while(ob_get_level())
            {
                    $sHtml = ob_get_clean() . $sHtml;
            }*/
    
            if (JchOptimizeHelper::validateHtml($sHtml))
            {
                    return jchoptimize($sHtml);
            }
            /*else
            {*/
                    return $sHtml;
            //}
    }

    As mentioned this would make your plugin compatible with caching plugins, since you hook your buffer after they do.

    https://ww.wp.xz.cn/plugins/jch-optimize/

Viewing 11 replies - 1 through 11 (of 11 total)
  • Plugin Author codealfa

    (@codealfa)

    Thanks for your feedback. When you say ‘compatible’ I’m assuming you mean having the JCH optimize plugin run before the caching plugin so the output of JCH optimize is cached by the plugin.

    Coming from a Joomla! background, one of the challenges I have with WordPress is that there is no native way of accessing the processed HTML. Therefore, developers who need to access some or all parts of the HTML have to resort to PHP’s buffering functions to do so. I’ve found that different developers are doing all sorts of things so compatibility between plugins that uses output buffering is a major challenge.

    The problem with your solution, which I’ve tried before, is that the plugin needs the complete HTML that is enclosed between (and including) the <html> tags in order to work. When the optimize function is called in the buffer callback, there’s no guarantee what is in the current buffer. If throughout the page requests the buffers are not properly nested, which I have encountered, the function gets called before the full HTML is ready. I’ve even seen different parts of the HTML in different levels of buffer, which is why I’m looping through all the levels.

    I’m shutting down the buffer just before WordPress does it anyhow. I do it in the shutdown hook using a priority of ‘0’ and WordPress calls this function in the shutdown hook using a ‘1’ priority in the wp-includes/functions.php file:

    function wp_ob_end_flush_all() {
    	$levels = ob_get_level();
    	for ($i=0; $i<$levels; $i++)
    		ob_end_flush();
    }

    In the absence of a native buffer API and a way for users to order which plugins should run first, I think as developers we should work together on a convention that makes our plugins work well together. I would propose my solution with two small changes. I could hook into shutdown a little earlier but restarting the buffer after running my functions so caching plugins can hook into shutdown after and cache the optimized HTML. So my codes would look like this:

    add_action('template_redirect', 'jch_buffer_start', 0);
     add_action('shutdown', 'jch_buffer_end', -1);

    and

    function jch_buffer_end()
    {
            $sHtml = '';
    
            while (ob_get_level())
            {
                    $sHtml = ob_get_clean() . $sHtml;
            }
    
            ob_start();
    
            if (JchOptimizeHelper::validateHtml($sHtml))
            {
                    echo jchoptimize($sHtml);
            }
            else
            {
                    echo $sHtml;
            }
    }

    Plugin Author codealfa

    (@codealfa)

    I should also add that I am aware of the ‘incompatibility’ with caching plugins. I generally test the plugin with WP Super Cache so I regrettably noted that with the last change the caching plugin no longer cached the optimized HTML, something I would prefer to have happen myself. The ‘consolation’ though was that JCH Optimize comes with its own level of caching.

    I’ve put in a lot of effort to have the plugin run as fast as possible. The results of the combining and minification functions are cached so on subsequent requests the plugin should run under 100ms.

    Anyway I’ll continue to research to see if there’s a way I can have my plugin compatible with other plugins including caching plugins.

    Thread Starter GatorDog

    (@gatordog)

    Hmmmm, I’m not really sure you understand how PHP output buffering works. Regarding your statement, “having the JCH optimize plugin run before the caching plugin so the output of JCH optimize is cached by the plugin” — is the exact opposite of how PHP buffering functions. In the context of WordPress or even a simple php script, if you want your output to be cached by another buffer, you’d start your buffer after the preceding cache buffer. Your buffering callback would change the output which in turn is fed to the preceeding buffer, the cache – so in effect your “optimized” page would be used for the cache or main buffer.

    Plugin Author codealfa

    (@codealfa)

    Yes I’m quite aware of that actually. When I say ‘having the JCH Optimize plugin run’, I’m referring to the jchoptimize function that gets called when the buffer closes. That’s where the optimization happens. So generally yes that would mean starting the caching buffer before the JCH Optimize buffer as you have rightly pointed out.

    With the current implementation though, assuming the caching occurs whenever your buffer closes, then the caching will occur before the optimize function runs even if you started your buffer first since I’m looping through and closing all the levels of buffering and concatenating them. There’s no harm done here as I explained earlier since WordPress does the exact same thing one step later anyway so me doing it shouldn’t affect anybody in anyway that WordPress hasn’t.

    I’m trying to ensure that the full HTML is available whenever I call the optimize function. Your solution doesn’t work for me as I’ve seen plugins starting buffers they’re not closing, and I’ve seen plugins closing buffers that haven’t started. I’ve also seen different parts of the HTML in different levels so I can’t rely on a callback to ensure I’m getting the HTML when my buffer closes.

    Plugin Author codealfa

    (@codealfa)

    What I actually meant was to have the optimize process run before the caching process. Thought you would have understood that since one plugin can be hooking into various functions running at different times so you wouldn’t necessarily have one plugin running in its entirety before another in WordPress.

    Thread Starter GatorDog

    (@gatordog)

    Using the callback I described, your optimizer will get the full site html generated by WordPress, that’s what will be returned when the output buffering ends on it’s own and your callback is ran. Then if there is a cache buffer after yours, your html is fed to their callback and saved to a cache – it may do some other things, add debug info etc, but this is on top of your optimized page.

    Consider the following code:

    ob_start('my_cache_function');
    ob_start('my_optimizing_function');
    echo '<pre>Hello World</pre>';
    function my_optimizing_function($html);
        return $html . "\n" . '<!-- Optimized by great optimizer -->';
    }
    function my_cache_function($html){
       return $html . "\n" . '<!-- Cached by mighty cacher -->';
    }

    This will simply return:

    <pre><Hello World></pre>
    <!-- Optimized by great optimizer -->
    <!-- Cached by mighty cacher -->
    Plugin Author codealfa

    (@codealfa)

    I don’t think you understand what I am trying to explain so maybe I need to work on how to explain myself clearer. While the callback works beautifully in a simple script where you are in control of all the codes, that’s not the case in WordPress where users can add may different plugins that are handling output buffering in many different ways.

    The callback is not an option for me as I simply cannot release an update that will knowingly break the functionality of the plugin on some sites. I hope you can understand that.

    I want the plugin to be compatible with caching as much as you do so I may have a solution that works for both of us. I’ve tested it with GatorCache and it works. I’ve edited the jch_buffer_end function as such:

    function jch_buffer_end()
    {
            $sHtml = '';
            $bFlag = FALSE;
    
            while (ob_get_level())
            {
                    $sHtml = ob_get_clean() . $sHtml;
    
                    ob_start();
    
                    if (JchOptimizeHelper::validateHtml($sHtml))
                    {
                            $bFlag = TRUE;
    
                            break;
                    }
            }
    
            if ($bFlag)
            {
                    echo jchoptimize($sHtml);
            }
            else
            {
                    echo $sHtml;
            }
    }

    If you can confirm that this works then this will be included in the next release.

    Plugin Author codealfa

    (@codealfa)

    I just thought I’d give one use case scenario that I’ve encountered where the callback implementation does not work for me. Remember I need the full HTML in order for the plugin to work:

    ob_start('my_optimizing_function');
    
    echo '<html>';
    echo '<head></head>';
    echo '<body>';
    echo '<pre>Hello World</pre>';
    
    ob_end_flush();
    
    echo '<pre>Hello again world</pre>';
    echo '</body>';
    echo '</html>';
    
    function my_optimizing_function($html)
    {
            $buffer = '<!--Optimization start-->' . "\n";
            $buffer .= $html . "\n";
            $buffer .= '<!--Optimization ends-->' . "\n";
    
            return $buffer;
    }

    Will return:

    <!--Optimization start-->
    <html><head></head><body><pre>Hello World</pre>
    <!--Optimization ends-->
    <pre>Hello again world</pre></body></html>

    With my solution:

    ob_start();
    
    echo '<html>';
    echo '<head></head>';
    echo '<body>';
    echo '<pre>Hello World</pre>';
    
    ob_end_flush();
    
    echo '<pre>Hello again world</pre>';
    echo '</body>';
    echo '</html>';
    
    $sHtml = '';
    
    while (ob_get_level())
    {
            $sHtml = ob_get_clean() . $sHtml;
    
            ob_start();
    
            if($sHtml)
            {
                    break;
            }
    }
    
    echo my_optimizing_function($sHtml);
    
    function my_optimizing_function($html)
    {
            $buffer = '<!--Optimization start-->' . "\n";
            $buffer .= $html . "\n";
            $buffer .= '<!--Optimization ends-->' . "\n";
    
            return $buffer;
    }

    returns:

    <!--Optimization start-->
    <html><head></head><body><pre>Hello World</pre><pre>Hello again world</pre></body></html>
    <!--Optimization ends-->

    And there are others.

    My solution is also compatible with your plugin:

    ob_start('my_cache_function');
    ob_start();//Optimization buffer starts
    
    echo '<pre>Hello World</pre>';
    
    $sHtml = '';
    
    while (ob_get_level())
    {
            $sHtml = ob_get_clean() . $sHtml;
    
            ob_start();
    
            if($sHtml)
            {
                    break;
            }
    }
    
    echo my_optimizing_function($sHtml);
    
    function my_optimizing_function($html)
    {
            return $html . "\n" . '<!-- Optimized by great optimizer -->';
    }
    
    function my_cache_function($html)
    {
            return $html . "\n" . '<!-- Cached by mighty cacher -->';
    }

    returns:

    <pre>Hello World</pre>
    <!-- Optimized by great optimizer -->
    <!-- Cached by mighty cacher -->

    Of course, if you need the full HTML for your plugin to work correctly too then your plugin will fail in the first scenario.

    Plugin Author codealfa

    (@codealfa)

    Update released with compatibility with caching plugins.

    Thread Starter GatorDog

    (@gatordog)

    This may work in some cases where other plugins don’t use output buffering. Not sure why you wouldn’t want to do this correctly and use a callback from ob_start. This is what every other successful WordPress plugin does to modify the output – including AutoOptimize, WPMinify, WordPress Https, NExtgen, the list goes on…

    Plugin Author codealfa

    (@codealfa)

    I don’t know why you have completely ignored and disregarded what I have said about encountering cases where the callback doesn’t work as expected. This works well if the buffers are properly nested. The PHP Manual also points this out.

    http://php.net/ob_start/
    “Output buffers are stackable, that is, you may call ob_start() while another ob_start() is active. Just make sure that you call ob_end_flush() the appropriate number of times.”

    In WordPress you can’t guarantee that this will be the case when users can install any number of arbitrary plugins that are doing all sorts of things with buffering. These plugins are hooking into all manner of different actions and have little control of the order in which hooked actions are called with respect to all the other plugins.

    If using the callback works for the other plugins then that’s fine. You may consider what I’m doing to be unorthodox but for me “to do this correctly” means to do it in a way that works. I’ve tested it with GatorCache and other plugins using output buffering and it works in all scenarios when the callback fails in some.

    Have you tested the latest version at all? As I have already stated I am also interested in having the plugin compatible with other plugins, not just GatorCache, so confirming whether this works or not for you would be more helpful than insisting I write codes in a particular way.

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

The topic ‘Compatilbity with caching plugins’ is closed to new replies.