1<?php
2/**
3 * Output buffering wrapper
4 */
5
6declare(strict_types=1);
7
8namespace PhpMyAdmin;
9
10use function defined;
11use function flush;
12use function function_exists;
13use function header;
14use function ini_get;
15use function ob_end_clean;
16use function ob_flush;
17use function ob_get_contents;
18use function ob_get_length;
19use function ob_get_level;
20use function ob_get_status;
21use function ob_start;
22use function register_shutdown_function;
23
24/**
25 * Output buffering wrapper class
26 */
27class OutputBuffering
28{
29    /** @var self */
30    private static $instance;
31
32    /** @var int */
33    private $mode;
34
35    /** @var string */
36    private $content;
37
38    /** @var bool */
39    private $on;
40
41    /**
42     * Initializes class
43     */
44    private function __construct()
45    {
46        $this->mode = $this->getMode();
47        $this->on = false;
48    }
49
50    /**
51     * This function could be used eventually to support more modes.
52     *
53     * @return int the output buffer mode
54     */
55    private function getMode()
56    {
57        $mode = 0;
58        if ($GLOBALS['cfg']['OBGzip'] && function_exists('ob_start')) {
59            if (ini_get('output_handler') === 'ob_gzhandler') {
60                // If a user sets the output_handler in php.ini to ob_gzhandler, then
61                // any right frame file in phpMyAdmin will not be handled properly by
62                // the browser. My fix was to check the ini file within the
63                // PMA_outBufferModeGet() function.
64                $mode = 0;
65            } elseif (function_exists('ob_get_level') && ob_get_level() > 0) {
66                // happens when php.ini's output_buffering is not Off
67                ob_end_clean();
68                $mode = 1;
69            } else {
70                $mode = 1;
71            }
72        }
73
74        // Zero (0) is no mode or in other words output buffering is OFF.
75        // Follow 2^0, 2^1, 2^2, 2^3 type values for the modes.
76        // Useful if we ever decide to combine modes.  Then a bitmask field of
77        // the sum of all modes will be the natural choice.
78        return $mode;
79    }
80
81    /**
82     * Returns the singleton OutputBuffering object
83     *
84     * @return OutputBuffering object
85     */
86    public static function getInstance()
87    {
88        if (empty(self::$instance)) {
89            self::$instance = new OutputBuffering();
90        }
91
92        return self::$instance;
93    }
94
95    /**
96     * This function will need to run at the top of all pages if output
97     * output buffering is turned on.  It also needs to be passed $mode from
98     * the PMA_outBufferModeGet() function or it will be useless.
99     *
100     * @return void
101     */
102    public function start()
103    {
104        if ($this->on) {
105            return;
106        }
107
108        if ($this->mode && function_exists('ob_gzhandler')) {
109            ob_start('ob_gzhandler');
110        }
111        ob_start();
112        if (! defined('TESTSUITE')) {
113            header('X-ob_mode: ' . $this->mode);
114        }
115        register_shutdown_function(
116            [
117                self::class,
118                'stop',
119            ]
120        );
121        $this->on = true;
122    }
123
124    /**
125     * This function will need to run at the bottom of all pages if output
126     * buffering is turned on.  It also needs to be passed $mode from the
127     * PMA_outBufferModeGet() function or it will be useless.
128     *
129     * @return void
130     */
131    public static function stop()
132    {
133        $buffer = self::getInstance();
134        if (! $buffer->on) {
135            return;
136        }
137
138        $buffer->on = false;
139        $buffer->content = ob_get_contents();
140        if (ob_get_length() <= 0) {
141            return;
142        }
143
144        ob_end_clean();
145    }
146
147    /**
148     * Gets buffer content
149     *
150     * @return string buffer content
151     */
152    public function getContents()
153    {
154        return $this->content;
155    }
156
157    /**
158     * Flushes output buffer
159     *
160     * @return void
161     */
162    public function flush()
163    {
164        if (ob_get_status() && $this->mode) {
165            ob_flush();
166        } else {
167            flush();
168        }
169    }
170}
171