MDL-34182: Add functions to warn of output in AJAX scripts

We need to be able to capture output when including contributed code as it
may add additional content which makes makes the json we output invalid.

We only warn of these errors if the site is in debugdeveloper, otherwise
we silently drop them.
This commit is contained in:
Andrew Nicols
2013-12-13 14:15:09 +08:00
parent 8703e6530c
commit 207f2c8880
2 changed files with 150 additions and 0 deletions
+37
View File
@@ -67,3 +67,40 @@ function ajaxenabled(array $browsers = null) {
return false;
}
}
/**
* Starts capturing output whilst processing an AJAX request.
*
* This should be used in combination with ajax_check_captured_output to
* report any captured output to the user.
*
* @retrun Boolean Returns true on success or false on failure.
*/
function ajax_capture_output() {
// Start capturing output in case of broken plugins.
return ob_start();
}
/**
* Check captured output for content. If the site has a debug level of
* debugdeveloper set, and the content is non-empty, then throw a coding
* exception which can be captured by the Y.IO request and displayed to the
* user.
*
* @return Any output that was captured.
*/
function ajax_check_captured_output() {
global $CFG;
// Retrieve the output - there should be none.
$output = ob_get_contents();
ob_end_clean();
if ($CFG->debugdeveloper && !empty($output)) {
// Only throw an error if the site is in debugdeveloper.
throw new coding_exception('Unexpected output whilst processing AJAX request. ' .
'This could be caused by trailing whitespace. Output received: ' .
var_export($output, true));
}
return $output;
}
+113
View File
@@ -0,0 +1,113 @@
<?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/>.
/**
* Code quality unit tests that are fast enough to run each time.
*
* @package core
* @category phpunit
* @copyright 2013 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
defined('MOODLE_INTERNAL') || die();
class core_ajaxlib_testcase extends advanced_testcase {
protected function helper_test_clean_output() {
$this->resetAfterTest();
$result = ajax_capture_output();
// ob_start should normally return without issue.
$this->assertTrue($result);
$result = ajax_check_captured_output();
$this->assertEmpty($result);
}
protected function helper_test_dirty_output($expectexception = false) {
$this->resetAfterTest();
// Keep track of the content we will output.
$content = "Some example content";
$result = ajax_capture_output();
// ob_start should normally return without issue.
$this->assertTrue($result);
// Fill the output buffer.
echo $content;
if ($expectexception) {
$this->setExpectedException('coding_exception');
ajax_check_captured_output();
} else {
$result = ajax_check_captured_output();
$this->assertEquals($result, $content);
}
}
public function test_output_capture_normal_debug_none() {
// In normal conditions, and with DEBUG_NONE set, we should not receive any output or throw any exceptions.
set_debugging(DEBUG_NONE);
$this->helper_test_clean_output();
}
public function test_output_capture_normal_debug_normal() {
// In normal conditions, and with DEBUG_NORMAL set, we should not receive any output or throw any exceptions.
set_debugging(DEBUG_NORMAL);
$this->helper_test_clean_output();
}
public function test_output_capture_normal_debug_all() {
// In normal conditions, and with DEBUG_ALL set, we should not receive any output or throw any exceptions.
set_debugging(DEBUG_ALL);
$this->helper_test_clean_output();
}
public function test_output_capture_normal_debugdeveloper() {
// In normal conditions, and with DEBUG_DEVELOPER set, we should not receive any output or throw any exceptions.
set_debugging(DEBUG_DEVELOPER);
$this->helper_test_clean_output();
}
public function test_output_capture_error_debug_none() {
// With DEBUG_NONE set, we should not throw any exception, but the output will be returned.
set_debugging(DEBUG_NONE);
$this->helper_test_dirty_output();
}
public function test_output_capture_error_debug_normal() {
// With DEBUG_NORMAL set, we should not throw any exception, but the output will be returned.
set_debugging(DEBUG_NORMAL);
$this->helper_test_dirty_output();
}
public function test_output_capture_error_debug_all() {
// In error conditions, and with DEBUG_ALL set, we should not receive any output or throw any exceptions.
set_debugging(DEBUG_ALL);
$this->helper_test_dirty_output();
}
public function test_output_capture_error_debugdeveloper() {
// With DEBUG_DEVELOPER set, we should throw an exception.
set_debugging(DEBUG_DEVELOPER);
$this->helper_test_dirty_output(true);
}
}