diff --git a/.upgradenotes/MDL-86168-2025073101410885.yml b/.upgradenotes/MDL-86168-2025073101410885.yml new file mode 100644 index 00000000000..e93135c8845 --- /dev/null +++ b/.upgradenotes/MDL-86168-2025073101410885.yml @@ -0,0 +1,17 @@ +issueNumber: MDL-86168 +notes: + core: + - message: > + The `user_preference_allow_ajax_update()` has been removed. It was + deprecated without replacement in Moodle 4.3. + type: deprecated + - message: | + The following functions have been replaced with class methods. + + | Old function name | New method name | + | --- | --- | + | `\ajax_capture_output()` | `\core\ajax::capture_output()` | + | `\ajax_check_captured_output()` | `\core\ajax::check_captured_output()` | + + It is no longer necessary to include `lib/ajax/ajaxlib.php` in any code. + type: improved diff --git a/public/lib/ajax/ajaxlib.php b/public/lib/ajax/ajaxlib.php index 633f89b80b8..a570144930a 100644 --- a/public/lib/ajax/ajaxlib.php +++ b/public/lib/ajax/ajaxlib.php @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +defined('MOODLE_INTERNAL') || die(); /** * Library functions to facilitate the use of ajax JavaScript in Moodle. @@ -23,25 +24,23 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -/** - * @deprecated since Moodle 4.3 - */ -#[\core\attribute\deprecated('\'core_user/repository\' module', since: '4.3', mdl: 'MDL-76974', final: true)] -function user_preference_allow_ajax_update() { - \core\deprecation::emit_deprecation(__FUNCTION__); -} - /** * 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. * - * @return Boolean Returns true on success or false on failure. + * @return bool Returns true on success or false on failure. + * @deprecated since Moodle 5.1, use \core\ajax::capture_output() instead. */ -function ajax_capture_output() { - // Start capturing output in case of broken plugins. - return ob_start(); +#[\core\attribute\deprecated( + replacement: "\core\ajax::capture_output()", + since: '5.1', + mdl: 'MDL-86168', +)] +function ajax_capture_output(): bool { + \core\deprecation::emit_deprecation(__FUNCTION__); + return \core\ajax::capture_output(); } /** @@ -50,24 +49,15 @@ function ajax_capture_output() { * exception which can be captured by the Y.IO request and displayed to the * user. * - * @return Any output that was captured. + * @return bool|string Any output that was captured. + * @deprecated since Moodle 5.1, use \core\ajax::check_captured_output() instead. */ -function ajax_check_captured_output() { - global $CFG; - - // Retrieve the output - there should be none. - $output = ob_get_contents(); - ob_end_clean(); - - if (!empty($output)) { - $message = 'Unexpected output whilst processing AJAX request. ' . - 'This could be caused by trailing whitespace. Output received: ' . - var_export($output, true); - if ($CFG->debugdeveloper && !empty($output)) { - // Only throw an error if the site is in debugdeveloper. - throw new coding_exception($message); - } - error_log('Potential coding error: ' . $message); - } - return $output; +#[\core\attribute\deprecated( + replacement: "\core\ajax::check_captured_output()", + since: '5.1', + mdl: 'MDL-86168', +)] +function ajax_check_captured_output(): bool|string { + \core\deprecation::emit_deprecation(__FUNCTION__); + return \core\ajax::check_captured_output(); } diff --git a/public/lib/ajax/getsiteadminbranch.php b/public/lib/ajax/getsiteadminbranch.php index bce7948504f..1fec5bccd71 100644 --- a/public/lib/ajax/getsiteadminbranch.php +++ b/public/lib/ajax/getsiteadminbranch.php @@ -38,7 +38,7 @@ if ($branchtype !== navigation_node::TYPE_SITE_ADMIN) { } // Start capturing output in case of broken plugins. -ajax_capture_output(); +\core\ajax::capture_output(); $PAGE->set_context(context_system::instance()); $PAGE->set_url('/lib/ajax/getsiteadminbranch.php', array('type'=>$branchtype)); @@ -49,5 +49,5 @@ $sitenavigation = new settings_navigation_ajax($PAGE); $converter = new navigation_json(); $branch = $sitenavigation->get('root'); -ajax_check_captured_output(); +\core\ajax::check_captured_output(); echo $converter->convert($branch); diff --git a/public/lib/classes/ajax.php b/public/lib/classes/ajax.php new file mode 100644 index 00000000000..c985b0eab9e --- /dev/null +++ b/public/lib/classes/ajax.php @@ -0,0 +1,71 @@ +. + +namespace core; + +use core\exception\coding_exception; + +/** + * Ajax helpers. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class ajax { + /** + * 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. + * + * @return bool Returns true on success or false on failure. + */ + public static function capture_output(): bool { + // 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 bool|string Any output that was captured. + * @throws coding_exception If unexpected output is found and the debug level is set to debugdeveloper. + */ + public static function check_captured_output(): bool|string { + global $CFG; + + // Retrieve the output - there should be none. + $output = ob_get_contents(); + ob_end_clean(); + + if (!empty($output)) { + $message = 'Unexpected output whilst processing AJAX request. '; + $message .= 'This could be caused by trailing whitespace. Output received: '; + $message .= var_export($output, true); + if ($CFG->debugdeveloper) { + // Only throw an error if the site is in debugdeveloper. + throw new coding_exception($message); + } + error_log('Potential coding error: ' . $message); // phpcs:ignore moodle.PHP.ForbiddenFunctions.FoundWithAlternative + } + + return $output; + } +} diff --git a/public/lib/setup.php b/public/lib/setup.php index 88c4c236ec7..cc38062035d 100644 --- a/public/lib/setup.php +++ b/public/lib/setup.php @@ -622,7 +622,6 @@ require_once($CFG->libdir .'/setuplib.php'); // Functions that MUST be lo // Load up standard libraries. require_once($CFG->libdir .'/filterlib.php'); // Functions for filtering test as it is output. -require_once($CFG->libdir .'/ajax/ajaxlib.php'); // Functions for managing our use of JavaScript and YUI. require_once($CFG->libdir .'/weblib.php'); // Functions relating to HTTP and content. require_once($CFG->libdir .'/outputlib.php'); // Functions for generating output. require_once($CFG->libdir .'/navigationlib.php'); // Class for generating Navigation structure. diff --git a/public/lib/tests/ajaxlib_test.php b/public/lib/tests/ajax_test.php similarity index 72% rename from public/lib/tests/ajaxlib_test.php rename to public/lib/tests/ajax_test.php index dc5b0a45140..4aea38a3467 100644 --- a/public/lib/tests/ajaxlib_test.php +++ b/public/lib/tests/ajax_test.php @@ -22,14 +22,14 @@ namespace core; * @package core * @category test * @copyright 2013 Andrew Nicols - * @license http://www.gnu.org/copyleft/gpl.html GNU Public License - * @covers ::ajax_capture_output - * @covers ::ajax_check_captured_output + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -final class ajaxlib_test extends \advanced_testcase { +#[\PHPUnit\Framework\Attributes\CoversClass(\core\ajax::class)] +final class ajax_test extends \advanced_testcase { /** @var string Original error log */ - protected $oldlog; + protected string $oldlog; + #[\Override] protected function setUp(): void { global $CFG; @@ -39,32 +39,41 @@ final class ajaxlib_test extends \advanced_testcase { ini_set('error_log', "$CFG->dataroot/testlog.log"); } + #[\Override] protected function tearDown(): void { ini_set('error_log', $this->oldlog); parent::tearDown(); } - protected function helper_test_clean_output() { + /** + * Assert that the output buffer is clean. + */ + protected function assert_clean_output(): void { $this->resetAfterTest(); - $result = ajax_capture_output(); + $result = ajax::capture_output(); - // ob_start should normally return without issue. + // The ob_start function should normally return without issue. $this->assertTrue($result); - $result = ajax_check_captured_output(); + $result = ajax::check_captured_output(); $this->assertEmpty($result); } - protected function helper_test_dirty_output($expectexception = false) { + /** + * Assert that the output buffer is dirty. + * + * @param bool $expectexception Whether to expect an exception to be thrown. + */ + protected function assert_dirty_output(bool $expectexception = false): void { $this->resetAfterTest(); // Keep track of the content we will output. $content = "Some example content"; - $result = ajax_capture_output(); + $result = ajax::capture_output(); - // ob_start should normally return without issue. + // The ob_start function should normally return without issue. $this->assertTrue($result); // Fill the output buffer. @@ -72,59 +81,58 @@ final class ajaxlib_test extends \advanced_testcase { if ($expectexception) { $this->expectException('coding_exception'); - ajax_check_captured_output(); + ajax::check_captured_output(); } else { - $result = ajax_check_captured_output(); - $this->assertEquals($result, $content); + $result = ajax::check_captured_output(); + $this->assertEquals($content, $result); } } public function test_output_capture_normal_debug_none(): void { // 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(); + $this->assert_clean_output(); } public function test_output_capture_normal_debug_normal(): void { // 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(); + $this->assert_clean_output(); } public function test_output_capture_normal_debug_all(): void { // 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(); + $this->assert_clean_output(); } public function test_output_capture_normal_debugdeveloper(): void { // 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(); + $this->assert_clean_output(); } public function test_output_capture_error_debug_none(): void { // 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(); + $this->assert_dirty_output(); } public function test_output_capture_error_debug_normal(): void { // 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(); + $this->assert_dirty_output(); } public function test_output_capture_error_debug_all(): void { // In error conditions, and with DEBUG_ALL set, we should throw an exceptions. set_debugging(DEBUG_ALL); - $this->helper_test_dirty_output(true); + $this->assert_dirty_output(true); } public function test_output_capture_error_debugdeveloper(): void { // With DEBUG_DEVELOPER set, we should throw an exception. set_debugging(DEBUG_DEVELOPER); - $this->helper_test_dirty_output(true); + $this->assert_dirty_output(true); } - } diff --git a/public/r.php b/public/r.php index 9f965cfb490..55ba9b5020e 100644 --- a/public/r.php +++ b/public/r.php @@ -33,7 +33,6 @@ require_once("{$CFG->libdir}/setuplib.php"); // Functions that MUST be lo // Load up standard libraries. require_once("{$CFG->libdir}/filterlib.php"); // Functions for filtering test as it is output. -require_once("{$CFG->libdir}/ajax/ajaxlib.php"); // Functions for managing our use of JavaScript and YUI. require_once("{$CFG->libdir}/weblib.php"); // Functions relating to HTTP and content. require_once("{$CFG->libdir}/outputlib.php"); // Functions for generating output. require_once("{$CFG->libdir}/navigationlib.php"); // Class for generating Navigation structure. diff --git a/public/repository/repository_ajax.php b/public/repository/repository_ajax.php index aa6c6c66334..6706fc459a4 100644 --- a/public/repository/repository_ajax.php +++ b/public/repository/repository_ajax.php @@ -77,7 +77,7 @@ $repooptions = array( 'mimetypes' => $accepted_types ); -ajax_capture_output(); +\core\ajax::capture_output(); $repo = repository::get_repository_by_id($repo_id, $contextid, $repooptions); // Check permissions @@ -101,7 +101,7 @@ switch ($action) { if ($repo->check_login()) { $listing = repository::prepare_listing($repo->get_listing($req_path, $page)); $listing['repo_id'] = $repo_id; - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($listing); break; } else { @@ -110,27 +110,27 @@ switch ($action) { case 'login': $listing = $repo->print_login(); $listing['repo_id'] = $repo_id; - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($listing); break; case 'logout': $logout = $repo->logout(); $logout['repo_id'] = $repo_id; - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($logout); break; case 'searchform': $search_form['repo_id'] = $repo_id; $search_form['form'] = $repo->print_search(); $search_form['allowcaching'] = true; - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($search_form); break; case 'search': $search_result = repository::prepare_listing($repo->search($search_text, (int)$page)); $search_result['repo_id'] = $repo_id; $search_result['issearchresult'] = true; - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($search_result); break; case 'download': @@ -179,7 +179,7 @@ switch ($action) { $info['file'] = $saveas_filename; $info['type'] = 'link'; $info['url'] = $link; - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($info); die; } else { @@ -272,7 +272,7 @@ switch ($action) { // You can cache reository file in this callback // or complete other tasks. $repo->cache_file_by_reference($reference, $storedfile); - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($event); die; } else if ($repo->has_moodle_files()) { @@ -283,7 +283,7 @@ switch ($action) { // {@link repository::copy_to_area()}. $fileinfo = $repo->copy_to_area($reference, $record, $maxbytes, $areamaxbytes); - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($fileinfo); die; } else { @@ -319,14 +319,14 @@ switch ($action) { $info['e'] = get_string('error', 'moodle'); } } - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($info); die; } break; case 'upload': $result = $repo->upload($saveas_filename, $maxbytes); - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($result); break; @@ -339,7 +339,7 @@ switch ($action) { $newfilename = required_param('newfilename', PARAM_FILE); $info = repository::overwrite_existing_draftfile($itemid, $filepath, $filename, $newfilepath, $newfilename); - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode($info); break; @@ -347,7 +347,7 @@ switch ($action) { // delete tmp file $newfilepath = required_param('newfilepath', PARAM_PATH); $newfilename = required_param('newfilename', PARAM_FILE); - ajax_check_captured_output(); + \core\ajax::check_captured_output(); echo json_encode(repository::delete_tempfile_from_draft($itemid, $newfilepath, $newfilename)); break;