Buffer.php 3.42 KB
Newer Older
Pham Huy committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
<?php
/**
 * For buffering accidental output caused by themes and other plugins.
 * Also used in template rendering.
 */
class Loco_output_Buffer {
    
    /**
     * The output buffering level opened by this instance
     * @var int usually 1 unless another buffer was opened before this one.
     */    
    private $ob_level;

    /**
     * Content buffered while our buffer was buffering
     * @var string
     */    
    private $output = '';

    /**
     * @return string
     */    
    public function __toString(){
         return $this->output;
    }


    /**
     * @return Loco_output_Buffer
     */
    public static function start(){
        $buffer = new Loco_output_Buffer;
        return $buffer->open();
    }


    /**
     * @internal
     * Ensure buffers closed if something terminates before we close gracefully
     */
    public function __destruct(){
        $this->close();
    }


    /**
     * @return Loco_output_Buffer
     */
    public function open(){
        self::check();
        if( ! ob_start() ){
            throw new Loco_error_Exception('Failed to start output buffering');
        }
        $this->ob_level = ob_get_level();
        return $this;
    }


    /**
     * @return Loco_output_Buffer
     */
    public function close(){
        if( is_int($this->ob_level) ){
            // collect output from our nested buffers
            $this->output = self::collect( $this->ob_level );
            $this->ob_level = null;
        }
        return $this;
    }


	/**
	 * Trash all open buffers, logging any junk output collected
	 * @return void
	 */
    public function discard(){
    	$this->close();
	    if( '' !== $this->output ){
		    self::log_junk( $this->output );
		    $this->output = '';
	    }
    }


    /**
     * Collect output buffered to a given level
     * @param int highest buffer to flush, 0 being the root
     * @return string
     */
    public static function collect( $min ){
        $last = 0;
        $output = '';
        while( $level = ob_get_level() ){
            // @codeCoverageIgnoreStart
            if( $level === $last ){
                throw new Loco_error_Exception('Failed to close output buffer');
            }
            // @codeCoverageIgnoreEnd
            if( $level < $min ){
                break;
            }
            // output is appended inside out:
            $output = ob_get_clean().$output;
            $last = $level;
        }
        return $output;
    }


    /**
     * Forcefully destroy all open buffers and log any bytes already buffered.
     * @return void
     */
    public static function clear(){
        $junk = self::collect(0);
	    if( '' !== $junk ){
		    self::log_junk($junk);
	    }
    }


    /**
     * Check output has not already been flushed.
     * @throws Loco_error_Exception
     */
    public static function check(){
	    if( headers_sent($file,$line) && 'cli' !== PHP_SAPI ){
		    $file = str_replace( trailingslashit( loco_constant('ABSPATH') ), '', $file );
		    throw new Loco_error_Exception( sprintf( __('Loco interrupted by output from %s:%u','loco-translate'), $file, $line ) );
	    }
    }


	/**
	 * Debug collection of junk output
	 * @param string
	 */
    private static function log_junk( $junk ){
    	$bytes = strlen($junk);
		$message = sprintf("Cleared %s of buffered output", Loco_mvc_FileParams::renderBytes($bytes) );
		Loco_error_AdminNotices::debug( $message );
		do_action( 'loco_buffer_cleared', $junk );
	}

}