diff --git a/lib/ajax/ajaxlib.php b/lib/ajax/ajaxlib.php index c57e6c04014..c9f21f85344 100644 --- a/lib/ajax/ajaxlib.php +++ b/lib/ajax/ajaxlib.php @@ -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; +} diff --git a/lib/tests/ajaxlib_test.php b/lib/tests/ajaxlib_test.php new file mode 100644 index 00000000000..9e80697b586 --- /dev/null +++ b/lib/tests/ajaxlib_test.php @@ -0,0 +1,113 @@ +. + +/** + * 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); + } + +}