AutorÃa | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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('<p>There are no more open containers. This suggests there is a nesting problem.</p>' .
$this->output_log(), DEBUG_DEVELOPER);
return;
}
$container = array_pop($this->opencontainers);
if ($container->type != $type) {
debugging('<p>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.</p>' .
$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 <body>)
* 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 <body>.
*/
public function pop_all_but_last($shouldbenone = false) {
if ($shouldbenone && count($this->opencontainers) != 1) {
debugging('<p>Some HTML tags were opened in the body of the page but not closed.</p>' .
$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[] = '<li>' . $action . ' ' . $type . ' at:' .
format_backtrace(debug_backtrace()) . '</li>';
}
/**
* Outputs the log's contents as a HTML list.
*
* @return string HTML list of the log
*/
protected function output_log() {
return '<ul>' . implode("\n", $this->log) . '</ul>';
}
}
// Alias this class to the old name.
// This file will be autoloaded by the legacyclasses autoload system.
// In future all uses of this class will be corrected and the legacy references will be removed.
class_alias(xhtml_container_stack::class, \xhtml_container_stack::class);