. namespace core\output; use stdClass; /** * This class keeps track of which HTML tags are currently open. * * This makes it much easier to always generate well formed XHTML output, even * if execution terminates abruptly. Any time you output some opening HTML * without the matching closing HTML, you should push the necessary close tags * onto the stack. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class xhtml_container_stack { /** * @var array Stores the list of open containers. */ protected $opencontainers = []; /** * @var array In developer debug mode, stores a stack trace of all opens and * closes, so we can output helpful error messages when there is a mismatch. */ protected $log = []; /** * @var bool Store whether we are developer debug mode. We need this in * several places including in the destructor where we may not have access to $CFG. */ protected $isdebugging; /** * Constructor */ public function __construct() { global $CFG; $this->isdebugging = $CFG->debugdeveloper; } /** * Push the close HTML for a recently opened container onto the stack. * * @param string $type The type of container. This is checked when {@see pop()} * is called and must match, otherwise a developer debug warning is output. * @param string $closehtml The HTML required to close the container. */ public function push($type, $closehtml) { $container = new stdClass(); $container->type = $type; $container->closehtml = $closehtml; if ($this->isdebugging) { $this->log('Open', $type); } array_push($this->opencontainers, $container); } /** * Pop the HTML for the next closing container from the stack. The $type * must match the type passed when the container was opened, otherwise a * warning will be output. * * @param string $type The type of container. * @return ?string the HTML required to close the container. */ public function pop($type) { if (empty($this->opencontainers)) { debugging('
There are no more open containers. This suggests there is a nesting problem.
' . $this->output_log(), DEBUG_DEVELOPER); return; } $container = array_pop($this->opencontainers); if ($container->type != $type) { debugging('The type of container to be closed (' . $container->type . ') does not match the type of the next open container (' . $type . '). This suggests there is a nesting problem.
' . $this->output_log(), DEBUG_DEVELOPER); } if ($this->isdebugging) { $this->log('Close', $type); } return $container->closehtml; } /** * Close all but the last open container. This is useful in places like error * handling, where you want to close all the open containers (apart from ) * before outputting the error message. * * @param bool $shouldbenone assert that the stack should be empty now - causes a * developer debug warning if it isn't. * @return string the HTML required to close any open containers inside . */ public function pop_all_but_last($shouldbenone = false) { if ($shouldbenone && count($this->opencontainers) != 1) { debugging('Some HTML tags were opened in the body of the page but not closed.
' . $this->output_log(), DEBUG_DEVELOPER); } $output = ''; while (count($this->opencontainers) > 1) { $container = array_pop($this->opencontainers); $output .= $container->closehtml; } return $output; } /** * You can call this function if you want to throw away an instance of this * class without properly emptying the stack (for example, in a unit test). * Calling this method stops the destruct method from outputting a developer * debug warning. After calling this method, the instance can no longer be used. */ public function discard() { $this->opencontainers = null; } /** * Adds an entry to the log. * * @param string $action The name of the action * @param string $type The type of action */ protected function log($action, $type) { $this->log[] = '