| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 | // This file is part of Moodle - https://moodle.org/
 | 
        
           |  |  | 3 | //
 | 
        
           |  |  | 4 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 5 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 6 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 7 | // (at your option) any later version.
 | 
        
           |  |  | 8 | //
 | 
        
           |  |  | 9 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 12 | // GNU General Public License for more details.
 | 
        
           |  |  | 13 | //
 | 
        
           |  |  | 14 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 15 | // along with Moodle.  If not, see <https://www.gnu.org/licenses/>.
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 | namespace core\hook;
 | 
        
           |  |  | 18 |   | 
        
           |  |  | 19 | use core\di;
 | 
        
           | 1441 | ariadna | 20 | use core\tests\fake_plugins_test_trait;
 | 
        
           | 1 | efrain | 21 |   | 
        
           |  |  | 22 | /**
 | 
        
           |  |  | 23 |  * Hooks tests.
 | 
        
           |  |  | 24 |  *
 | 
        
           |  |  | 25 |  * @package   core
 | 
        
           |  |  | 26 |  * @author    Petr Skoda
 | 
        
           |  |  | 27 |  * @copyright 2022 Open LMS
 | 
        
           |  |  | 28 |  * @license   https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 29 |  * @covers \core\hook\manager
 | 
        
           |  |  | 30 |  */
 | 
        
           |  |  | 31 | final class manager_test extends \advanced_testcase {
 | 
        
           | 1441 | ariadna | 32 |   | 
        
           |  |  | 33 |     use fake_plugins_test_trait;
 | 
        
           |  |  | 34 |   | 
        
           | 1 | efrain | 35 |     /**
 | 
        
           |  |  | 36 |      * Test public factory method to get hook manager.
 | 
        
           |  |  | 37 |      */
 | 
        
           |  |  | 38 |     public function test_get_instance(): void {
 | 
        
           |  |  | 39 |         $manager = manager::get_instance();
 | 
        
           |  |  | 40 |         $this->assertInstanceOf(manager::class, $manager);
 | 
        
           |  |  | 41 |   | 
        
           |  |  | 42 |         $this->assertSame($manager, manager::get_instance());
 | 
        
           |  |  | 43 |     }
 | 
        
           |  |  | 44 |   | 
        
           |  |  | 45 |     /**
 | 
        
           |  |  | 46 |      * Test getting of manager test instance.
 | 
        
           |  |  | 47 |      */
 | 
        
           |  |  | 48 |     public function test_phpunit_get_instance(): void {
 | 
        
           |  |  | 49 |         $testmanager = manager::phpunit_get_instance([]);
 | 
        
           |  |  | 50 |         $this->assertSame([], $testmanager->get_hooks_with_callbacks());
 | 
        
           |  |  | 51 |   | 
        
           |  |  | 52 |         // We get a new instance every time.
 | 
        
           |  |  | 53 |         $this->assertNotSame($testmanager, manager::phpunit_get_instance([]));
 | 
        
           |  |  | 54 |   | 
        
           |  |  | 55 |         $componentfiles = [
 | 
        
           |  |  | 56 |             'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php',
 | 
        
           |  |  | 57 |         ];
 | 
        
           |  |  | 58 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 59 |         $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());
 | 
        
           |  |  | 60 |     }
 | 
        
           |  |  | 61 |   | 
        
           |  |  | 62 |     /**
 | 
        
           |  |  | 63 |      * Test loading and parsing of callbacks from files.
 | 
        
           |  |  | 64 |      */
 | 
        
           |  |  | 65 |     public function test_callbacks(): void {
 | 
        
           |  |  | 66 |         $componentfiles = [
 | 
        
           |  |  | 67 |             'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php',
 | 
        
           |  |  | 68 |             'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',
 | 
        
           |  |  | 69 |         ];
 | 
        
           |  |  | 70 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 71 |         $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());
 | 
        
           |  |  | 72 |         $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');
 | 
        
           |  |  | 73 |         $this->assertCount(2, $callbacks);
 | 
        
           |  |  | 74 |         $this->assertSame([
 | 
        
           |  |  | 75 |             'callback' => 'test_plugin\\callbacks::test2',
 | 
        
           |  |  | 76 |             'component' => 'test_plugin2',
 | 
        
           |  |  | 77 |             'disabled' => false,
 | 
        
           |  |  | 78 |             'priority' => 200,
 | 
        
           |  |  | 79 |         ], $callbacks[0]);
 | 
        
           |  |  | 80 |         $this->assertSame([
 | 
        
           |  |  | 81 |             'callback' => 'test_plugin\\callbacks::test1',
 | 
        
           |  |  | 82 |             'component' => 'test_plugin1',
 | 
        
           |  |  | 83 |             'disabled' => false,
 | 
        
           |  |  | 84 |             'priority' => 100,
 | 
        
           |  |  | 85 |         ], $callbacks[1]);
 | 
        
           |  |  | 86 |   | 
        
           |  |  | 87 |         $this->assertDebuggingNotCalled();
 | 
        
           |  |  | 88 |         $componentfiles = [
 | 
        
           |  |  | 89 |             'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_broken.php',
 | 
        
           |  |  | 90 |         ];
 | 
        
           |  |  | 91 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 92 |         $this->assertSame([], $testmanager->get_hooks_with_callbacks());
 | 
        
           |  |  | 93 |         $debuggings = $this->getDebuggingMessages();
 | 
        
           |  |  | 94 |         $this->resetDebugging();
 | 
        
           |  |  | 95 |         $this->assertSame(
 | 
        
           |  |  | 96 |             'Hook callback definition requires \'hook\' name in \'test_plugin1\'',
 | 
        
           |  |  | 97 |             $debuggings[0]->message
 | 
        
           |  |  | 98 |         );
 | 
        
           |  |  | 99 |         $this->assertSame(
 | 
        
           |  |  | 100 |             'Hook callback definition requires \'callback\' callable in \'test_plugin1\'',
 | 
        
           |  |  | 101 |             $debuggings[1]->message
 | 
        
           |  |  | 102 |         );
 | 
        
           |  |  | 103 |         $this->assertSame(
 | 
        
           |  |  | 104 |             'Hook callback definition contains invalid \'callback\' static class method string in \'test_plugin1\'',
 | 
        
           |  |  | 105 |             $debuggings[2]->message
 | 
        
           |  |  | 106 |         );
 | 
        
           |  |  | 107 |         $this->assertCount(3, $debuggings);
 | 
        
           |  |  | 108 |     }
 | 
        
           |  |  | 109 |   | 
        
           |  |  | 110 |     /**
 | 
        
           |  |  | 111 |      * Test hook dispatching, that is callback execution.
 | 
        
           |  |  | 112 |      */
 | 
        
           |  |  | 113 |     public function test_dispatch(): void {
 | 
        
           |  |  | 114 |         require_once(__DIR__ . '/../fixtures/hook/hook.php');
 | 
        
           |  |  | 115 |         require_once(__DIR__ . '/../fixtures/hook/callbacks.php');
 | 
        
           |  |  | 116 |   | 
        
           |  |  | 117 |         $componentfiles = [
 | 
        
           |  |  | 118 |             'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php',
 | 
        
           |  |  | 119 |             'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',
 | 
        
           |  |  | 120 |         ];
 | 
        
           |  |  | 121 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 122 |         \test_plugin\callbacks::$calls = [];
 | 
        
           |  |  | 123 |         $hook = new \test_plugin\hook\hook();
 | 
        
           |  |  | 124 |         $result = $testmanager->dispatch($hook);
 | 
        
           |  |  | 125 |         $this->assertSame($hook, $result);
 | 
        
           |  |  | 126 |         $this->assertSame(['test2', 'test1'], \test_plugin\callbacks::$calls);
 | 
        
           |  |  | 127 |         \test_plugin\callbacks::$calls = [];
 | 
        
           |  |  | 128 |         $this->assertDebuggingNotCalled();
 | 
        
           |  |  | 129 |     }
 | 
        
           |  |  | 130 |   | 
        
           |  |  | 131 |     /**
 | 
        
           |  |  | 132 |      * Test hook dispatching, that is callback execution.
 | 
        
           |  |  | 133 |      */
 | 
        
           |  |  | 134 |     public function test_dispatch_with_exception(): void {
 | 
        
           |  |  | 135 |         require_once(__DIR__ . '/../fixtures/hook/hook.php');
 | 
        
           |  |  | 136 |         require_once(__DIR__ . '/../fixtures/hook/callbacks.php');
 | 
        
           |  |  | 137 |   | 
        
           |  |  | 138 |         $componentfiles = [
 | 
        
           |  |  | 139 |             'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_exception.php',
 | 
        
           |  |  | 140 |             'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',
 | 
        
           |  |  | 141 |         ];
 | 
        
           |  |  | 142 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 143 |   | 
        
           |  |  | 144 |         $hook = new \test_plugin\hook\hook();
 | 
        
           |  |  | 145 |   | 
        
           |  |  | 146 |         $this->expectException(\Exception::class);
 | 
        
           |  |  | 147 |         $this->expectExceptionMessage('grrr');
 | 
        
           |  |  | 148 |   | 
        
           |  |  | 149 |         $testmanager->dispatch($hook);
 | 
        
           |  |  | 150 |     }
 | 
        
           |  |  | 151 |   | 
        
           |  |  | 152 |     /**
 | 
        
           |  |  | 153 |      * Test hook dispatching, that is callback execution.
 | 
        
           |  |  | 154 |      */
 | 
        
           |  |  | 155 |     public function test_dispatch_with_invalid(): void {
 | 
        
           |  |  | 156 |         require_once(__DIR__ . '/../fixtures/hook/hook.php');
 | 
        
           |  |  | 157 |         require_once(__DIR__ . '/../fixtures/hook/callbacks.php');
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 |         // Missing callbacks is ignored.
 | 
        
           |  |  | 160 |         $componentfiles = [
 | 
        
           |  |  | 161 |             'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_missing.php',
 | 
        
           |  |  | 162 |             'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',
 | 
        
           |  |  | 163 |         ];
 | 
        
           |  |  | 164 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 165 |         \test_plugin\callbacks::$calls = [];
 | 
        
           |  |  | 166 |   | 
        
           |  |  | 167 |         $hook = new \test_plugin\hook\hook();
 | 
        
           |  |  | 168 |   | 
        
           |  |  | 169 |         $testmanager->dispatch($hook);
 | 
        
           |  |  | 170 |         $this->assertDebuggingCalled(
 | 
        
           |  |  | 171 |             "Hook callback definition contains invalid 'callback' method name in 'test_plugin1'. Callback method not found.",
 | 
        
           |  |  | 172 |         );
 | 
        
           |  |  | 173 |         $this->assertSame(['test2'], \test_plugin\callbacks::$calls);
 | 
        
           |  |  | 174 |     }
 | 
        
           |  |  | 175 |   | 
        
           |  |  | 176 |     /**
 | 
        
           |  |  | 177 |      * Test stoppping of hook dispatching.
 | 
        
           |  |  | 178 |      */
 | 
        
           |  |  | 179 |     public function test_dispatch_stoppable(): void {
 | 
        
           |  |  | 180 |         require_once(__DIR__ . '/../fixtures/hook/stoppablehook.php');
 | 
        
           |  |  | 181 |         require_once(__DIR__ . '/../fixtures/hook/callbacks.php');
 | 
        
           |  |  | 182 |   | 
        
           |  |  | 183 |         $componentfiles = [
 | 
        
           |  |  | 184 |             'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_stoppable.php',
 | 
        
           |  |  | 185 |             'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_stoppable.php',
 | 
        
           |  |  | 186 |         ];
 | 
        
           |  |  | 187 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 188 |         \test_plugin\callbacks::$calls = [];
 | 
        
           |  |  | 189 |         $hook = new \test_plugin\hook\stoppablehook();
 | 
        
           |  |  | 190 |         $result = $testmanager->dispatch($hook);
 | 
        
           |  |  | 191 |         $this->assertSame($hook, $result);
 | 
        
           |  |  | 192 |         $this->assertSame(['stop1'], \test_plugin\callbacks::$calls);
 | 
        
           |  |  | 193 |         \test_plugin\callbacks::$calls = [];
 | 
        
           |  |  | 194 |         $this->assertDebuggingNotCalled();
 | 
        
           |  |  | 195 |     }
 | 
        
           |  |  | 196 |   | 
        
           |  |  | 197 |     /**
 | 
        
           |  |  | 198 |      * Tests callbacks can be overridden via CFG settings.
 | 
        
           |  |  | 199 |      */
 | 
        
           |  |  | 200 |     public function test_callback_overriding(): void {
 | 
        
           |  |  | 201 |         global $CFG;
 | 
        
           |  |  | 202 |         $this->resetAfterTest();
 | 
        
           |  |  | 203 |   | 
        
           |  |  | 204 |         $componentfiles = [
 | 
        
           |  |  | 205 |             'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php',
 | 
        
           |  |  | 206 |             'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',
 | 
        
           |  |  | 207 |         ];
 | 
        
           |  |  | 208 |   | 
        
           |  |  | 209 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 210 |         $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());
 | 
        
           |  |  | 211 |         $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');
 | 
        
           |  |  | 212 |         $this->assertCount(2, $callbacks);
 | 
        
           |  |  | 213 |         $this->assertSame([
 | 
        
           |  |  | 214 |             'callback' => 'test_plugin\\callbacks::test2',
 | 
        
           |  |  | 215 |             'component' => 'test_plugin2',
 | 
        
           |  |  | 216 |             'disabled' => false,
 | 
        
           |  |  | 217 |             'priority' => 200,
 | 
        
           |  |  | 218 |         ], $callbacks[0]);
 | 
        
           |  |  | 219 |         $this->assertSame([
 | 
        
           |  |  | 220 |             'callback' => 'test_plugin\\callbacks::test1',
 | 
        
           |  |  | 221 |             'component' => 'test_plugin1',
 | 
        
           |  |  | 222 |             'disabled' => false,
 | 
        
           |  |  | 223 |             'priority' => 100,
 | 
        
           |  |  | 224 |         ], $callbacks[1]);
 | 
        
           |  |  | 225 |   | 
        
           |  |  | 226 |         $CFG->hooks_callback_overrides = [
 | 
        
           |  |  | 227 |             'test_plugin\\hook\\hook' => [
 | 
        
           |  |  | 228 |                 'test_plugin\\callbacks::test2' => ['priority' => 33],
 | 
        
           |  |  | 229 |             ],
 | 
        
           |  |  | 230 |         ];
 | 
        
           |  |  | 231 |   | 
        
           |  |  | 232 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 233 |         $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());
 | 
        
           |  |  | 234 |         $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');
 | 
        
           |  |  | 235 |         $this->assertCount(2, $callbacks);
 | 
        
           |  |  | 236 |         $this->normalise_callbacks($callbacks);
 | 
        
           |  |  | 237 |         $this->assertSame([
 | 
        
           |  |  | 238 |             'callback' => 'test_plugin\\callbacks::test1',
 | 
        
           |  |  | 239 |             'component' => 'test_plugin1',
 | 
        
           |  |  | 240 |             'disabled' => false,
 | 
        
           |  |  | 241 |             'priority' => 100,
 | 
        
           |  |  | 242 |         ], $callbacks[0]);
 | 
        
           |  |  | 243 |         $this->assertSame([
 | 
        
           |  |  | 244 |             'callback' => 'test_plugin\\callbacks::test2',
 | 
        
           |  |  | 245 |             'component' => 'test_plugin2',
 | 
        
           |  |  | 246 |             'defaultpriority' => 200,
 | 
        
           |  |  | 247 |             'disabled' => false,
 | 
        
           |  |  | 248 |             'priority' => 33,
 | 
        
           |  |  | 249 |         ], $callbacks[1]);
 | 
        
           |  |  | 250 |   | 
        
           |  |  | 251 |         $CFG->hooks_callback_overrides = [
 | 
        
           |  |  | 252 |             'test_plugin\\hook\\hook' => [
 | 
        
           |  |  | 253 |                 'test_plugin\\callbacks::test2' => ['priority' => 33, 'disabled' => true],
 | 
        
           |  |  | 254 |             ],
 | 
        
           |  |  | 255 |         ];
 | 
        
           |  |  | 256 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 257 |         $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());
 | 
        
           |  |  | 258 |         $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');
 | 
        
           |  |  | 259 |         $this->assertCount(2, $callbacks);
 | 
        
           |  |  | 260 |         $this->normalise_callbacks($callbacks);
 | 
        
           |  |  | 261 |         $this->assertSame([
 | 
        
           |  |  | 262 |             'callback' => 'test_plugin\\callbacks::test1',
 | 
        
           |  |  | 263 |             'component' => 'test_plugin1',
 | 
        
           |  |  | 264 |             'disabled' => false,
 | 
        
           |  |  | 265 |             'priority' => 100,
 | 
        
           |  |  | 266 |         ], $callbacks[0]);
 | 
        
           |  |  | 267 |         $this->assertSame([
 | 
        
           |  |  | 268 |             'callback' => 'test_plugin\\callbacks::test2',
 | 
        
           |  |  | 269 |             'component' => 'test_plugin2',
 | 
        
           |  |  | 270 |             'defaultpriority' => 200,
 | 
        
           |  |  | 271 |             'disabled' => true,
 | 
        
           |  |  | 272 |             'priority' => 33,
 | 
        
           |  |  | 273 |         ], $callbacks[1]);
 | 
        
           |  |  | 274 |   | 
        
           |  |  | 275 |         $CFG->hooks_callback_overrides = [
 | 
        
           |  |  | 276 |             'test_plugin\\hook\\hook' => [
 | 
        
           |  |  | 277 |                 'test_plugin\\callbacks::test2' => ['disabled' => true],
 | 
        
           |  |  | 278 |             ],
 | 
        
           |  |  | 279 |         ];
 | 
        
           |  |  | 280 |         $testmanager = manager::phpunit_get_instance($componentfiles);
 | 
        
           |  |  | 281 |         $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());
 | 
        
           |  |  | 282 |         $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');
 | 
        
           |  |  | 283 |         $this->assertCount(2, $callbacks);
 | 
        
           |  |  | 284 |         $this->assertSame([
 | 
        
           |  |  | 285 |             'callback' => 'test_plugin\\callbacks::test2',
 | 
        
           |  |  | 286 |             'component' => 'test_plugin2',
 | 
        
           |  |  | 287 |             'disabled' => true,
 | 
        
           |  |  | 288 |             'priority' => 200,
 | 
        
           |  |  | 289 |         ], $callbacks[0]);
 | 
        
           |  |  | 290 |         $this->assertSame([
 | 
        
           |  |  | 291 |             'callback' => 'test_plugin\\callbacks::test1',
 | 
        
           |  |  | 292 |             'component' => 'test_plugin1',
 | 
        
           |  |  | 293 |             'disabled' => false,
 | 
        
           |  |  | 294 |             'priority' => 100,
 | 
        
           |  |  | 295 |         ], $callbacks[1]);
 | 
        
           |  |  | 296 |   | 
        
           |  |  | 297 |         require_once(__DIR__ . '/../fixtures/hook/hook.php');
 | 
        
           |  |  | 298 |         require_once(__DIR__ . '/../fixtures/hook/callbacks.php');
 | 
        
           |  |  | 299 |   | 
        
           |  |  | 300 |         \test_plugin\callbacks::$calls = [];
 | 
        
           |  |  | 301 |         $hook = new \test_plugin\hook\hook();
 | 
        
           |  |  | 302 |         $result = $testmanager->dispatch($hook);
 | 
        
           |  |  | 303 |         $this->assertSame($hook, $result);
 | 
        
           |  |  | 304 |         $this->assertSame(['test1'], \test_plugin\callbacks::$calls);
 | 
        
           |  |  | 305 |         \test_plugin\callbacks::$calls = [];
 | 
        
           |  |  | 306 |         $this->assertDebuggingNotCalled();
 | 
        
           |  |  | 307 |         $CFG->hooks_callback_overrides = [];
 | 
        
           |  |  | 308 |     }
 | 
        
           |  |  | 309 |   | 
        
           |  |  | 310 |     /**
 | 
        
           |  |  | 311 |      * Register a fake plugin called hooktest in the component manager.
 | 
        
           |  |  | 312 |      *
 | 
        
           |  |  | 313 |      * Tests consuming this helpers must run in a separate process.
 | 
        
           |  |  | 314 |      */
 | 
        
           |  |  | 315 |     protected function setup_hooktest_plugin(): void {
 | 
        
           |  |  | 316 |         global $CFG;
 | 
        
           |  |  | 317 |   | 
        
           |  |  | 318 |         $this->add_mocked_plugintype('fake', "{$CFG->dirroot}/lib/tests/fixtures/hook/fakeplugins");
 | 
        
           |  |  | 319 |         $this->add_mocked_plugin('fake', 'hooktest', "{$CFG->dirroot}/lib/tests/fixtures/hook/fakeplugins/hooktest");
 | 
        
           |  |  | 320 |     }
 | 
        
           |  |  | 321 |   | 
        
           |  |  | 322 |     /**
 | 
        
           |  |  | 323 |      * Call a plugin callback that has been replaced by a hook, but has no hook callback.
 | 
        
           |  |  | 324 |      *
 | 
        
           |  |  | 325 |      * The original callback should be called, but a debugging message should be output.
 | 
        
           |  |  | 326 |      *
 | 
        
           |  |  | 327 |      * @runInSeparateProcess
 | 
        
           |  |  | 328 |      */
 | 
        
           |  |  | 329 |     public function test_migrated_callback(): void {
 | 
        
           |  |  | 330 |         $this->resetAfterTest(true);
 | 
        
           |  |  | 331 |         // Include plugin hook discovery agent, and the hook that replaces the callback.
 | 
        
           |  |  | 332 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hooks.php');
 | 
        
           |  |  | 333 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook/hook_replacing_callback.php');
 | 
        
           |  |  | 334 |         // Register the fake plugin with the component manager.
 | 
        
           |  |  | 335 |         $this->setup_hooktest_plugin();
 | 
        
           |  |  | 336 |   | 
        
           |  |  | 337 |         // Register the fake plugin with the hook manager, but don't define any hook callbacks.
 | 
        
           |  |  | 338 |         di::set(
 | 
        
           |  |  | 339 |             manager::class,
 | 
        
           |  |  | 340 |             manager::phpunit_get_instance(
 | 
        
           |  |  | 341 |                 [
 | 
        
           |  |  | 342 |                     'fake_hooktest' => __DIR__ . '/../fixtures/hook/fakeplugins/hooktest/db/hooks_nocallbacks.php',
 | 
        
           |  |  | 343 |                 ],
 | 
        
           |  |  | 344 |             ),
 | 
        
           |  |  | 345 |         );
 | 
        
           |  |  | 346 |   | 
        
           |  |  | 347 |         // Confirm a non-deprecated callback is called as expected.
 | 
        
           |  |  | 348 |         $this->assertEquals('Called current callback', component_callback('fake_hooktest', 'current_callback'));
 | 
        
           |  |  | 349 |   | 
        
           |  |  | 350 |         // Confirm the deprecated callback is called as expected.
 | 
        
           |  |  | 351 |         $this->assertEquals(
 | 
        
           |  |  | 352 |             'Called deprecated callback',
 | 
        
           |  |  | 353 |             component_callback('fake_hooktest', 'old_callback', [], null, true)
 | 
        
           |  |  | 354 |         );
 | 
        
           | 1441 | ariadna | 355 |         $this->assertDebuggingNotCalled();
 | 
        
           |  |  | 356 |   | 
        
           |  |  | 357 |         // Forcefully modify the PHPUnit flag on the manager to ensure the debugging message is output.
 | 
        
           |  |  | 358 |         $manager = di::get(manager::class);
 | 
        
           |  |  | 359 |         $rp = new \ReflectionProperty($manager, 'phpunit');
 | 
        
           |  |  | 360 |         $rp->setValue($manager, false);
 | 
        
           |  |  | 361 |   | 
        
           |  |  | 362 |         component_callback('fake_hooktest', 'old_callback', [], null, true);
 | 
        
           |  |  | 363 |   | 
        
           | 1 | efrain | 364 |         $this->assertDebuggingCalled(
 | 
        
           |  |  | 365 |             'Callback old_callback in fake_hooktest component should be migrated to new hook ' .
 | 
        
           | 1441 | ariadna | 366 |                 'callback for fake_hooktest\hook\hook_replacing_callback',
 | 
        
           | 1 | efrain | 367 |         );
 | 
        
           |  |  | 368 |     }
 | 
        
           |  |  | 369 |   | 
        
           |  |  | 370 |     /**
 | 
        
           |  |  | 371 |      * Call a plugin callback that has been replaced by a hook, and has a hook callback.
 | 
        
           |  |  | 372 |      *
 | 
        
           |  |  | 373 |      * The original callback should not be called, and no debugging should be output.
 | 
        
           |  |  | 374 |      *
 | 
        
           |  |  | 375 |      * @runInSeparateProcess
 | 
        
           |  |  | 376 |      */
 | 
        
           |  |  | 377 |     public function test_migrated_callback_with_replacement(): void {
 | 
        
           |  |  | 378 |         $this->resetAfterTest(true);
 | 
        
           |  |  | 379 |         // Include plugin hook discovery agent, and the hook that replaces the callback, and a hook callback for the hook.
 | 
        
           |  |  | 380 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hooks.php');
 | 
        
           |  |  | 381 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook/hook_replacing_callback.php');
 | 
        
           |  |  | 382 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook_callbacks.php');
 | 
        
           |  |  | 383 |         // Register the fake plugin with the component manager.
 | 
        
           |  |  | 384 |         $this->setup_hooktest_plugin();
 | 
        
           |  |  | 385 |   | 
        
           |  |  | 386 |         // Register the fake plugin with the hook manager, including the hook callback.
 | 
        
           |  |  | 387 |         di::set(
 | 
        
           |  |  | 388 |             manager::class,
 | 
        
           |  |  | 389 |             manager::phpunit_get_instance([
 | 
        
           |  |  | 390 |                 'fake_hooktest' => __DIR__ . '/../fixtures/hook/fakeplugins/hooktest/db/hooks.php',
 | 
        
           |  |  | 391 |             ]),
 | 
        
           |  |  | 392 |         );
 | 
        
           |  |  | 393 |   | 
        
           |  |  | 394 |         // Confirm a non-deprecated callback is called as expected.
 | 
        
           |  |  | 395 |         $this->assertEquals('Called current callback', component_callback('fake_hooktest', 'current_callback'));
 | 
        
           |  |  | 396 |   | 
        
           |  |  | 397 |         // Confirm the deprecated callback is not called, as expected.
 | 
        
           |  |  | 398 |         $this->assertNull(component_callback('fake_hooktest', 'old_callback', [], null, true));
 | 
        
           |  |  | 399 |         $this->assertDebuggingNotCalled();
 | 
        
           |  |  | 400 |     }
 | 
        
           |  |  | 401 |   | 
        
           |  |  | 402 |     /**
 | 
        
           |  |  | 403 |      * Call a plugin class callback that has been replaced by a hook, but has no hook callback.
 | 
        
           |  |  | 404 |      *
 | 
        
           |  |  | 405 |      * The original class callback should be called, but a debugging message should be output.
 | 
        
           |  |  | 406 |      *
 | 
        
           |  |  | 407 |      * @runInSeparateProcess
 | 
        
           |  |  | 408 |      */
 | 
        
           |  |  | 409 |     public function test_migrated_class_callback(): void {
 | 
        
           |  |  | 410 |         $this->resetAfterTest(true);
 | 
        
           |  |  | 411 |         // Include plugin hook discovery agent, the class containing callbacks, and the hook that replaces the class callback.
 | 
        
           |  |  | 412 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/callbacks.php');
 | 
        
           |  |  | 413 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hooks.php');
 | 
        
           |  |  | 414 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook/hook_replacing_class_callback.php');
 | 
        
           |  |  | 415 |         // Register the fake plugin with the component manager.
 | 
        
           |  |  | 416 |         $this->setup_hooktest_plugin();
 | 
        
           |  |  | 417 |   | 
        
           |  |  | 418 |         // Register the fake plugin with the hook manager, but don't define any hook callbacks.
 | 
        
           |  |  | 419 |         di::set(
 | 
        
           |  |  | 420 |             manager::class,
 | 
        
           |  |  | 421 |             manager::phpunit_get_instance([
 | 
        
           |  |  | 422 |                 'fake_hooktest' => __DIR__ . '/../fixtures/hook/fakeplugins/hooktest/db/hooks_nocallbacks.php',
 | 
        
           |  |  | 423 |             ]),
 | 
        
           |  |  | 424 |         );
 | 
        
           |  |  | 425 |   | 
        
           |  |  | 426 |         // Confirm a non-deprecated class callback is called as expected.
 | 
        
           |  |  | 427 |         $this->assertEquals(
 | 
        
           |  |  | 428 |             'Called current class callback',
 | 
        
           |  |  | 429 |             component_class_callback('fake_hooktest\callbacks', 'current_class_callback', [])
 | 
        
           |  |  | 430 |         );
 | 
        
           |  |  | 431 |   | 
        
           |  |  | 432 |         // Confirm the deprecated class callback is called as expected.
 | 
        
           |  |  | 433 |         $this->assertEquals(
 | 
        
           |  |  | 434 |             'Called deprecated class callback',
 | 
        
           |  |  | 435 |             component_class_callback('fake_hooktest\callbacks', 'old_class_callback', [], null, true)
 | 
        
           |  |  | 436 |         );
 | 
        
           | 1441 | ariadna | 437 |         $this->assertDebuggingNotCalled();
 | 
        
           |  |  | 438 |   | 
        
           |  |  | 439 |         // Forcefully modify the PHPUnit flag on the manager to ensure the debugging message is output.
 | 
        
           |  |  | 440 |         $manager = di::get(manager::class);
 | 
        
           |  |  | 441 |         $rp = new \ReflectionProperty($manager, 'phpunit');
 | 
        
           |  |  | 442 |         $rp->setValue($manager, false);
 | 
        
           |  |  | 443 |   | 
        
           |  |  | 444 |         component_class_callback('fake_hooktest\callbacks', 'old_class_callback', [], null, true);
 | 
        
           | 1 | efrain | 445 |         $this->assertDebuggingCalled(
 | 
        
           |  |  | 446 |             'Callback callbacks::old_class_callback in fake_hooktest component should be migrated to new hook ' .
 | 
        
           | 1441 | ariadna | 447 |                 'callback for fake_hooktest\hook\hook_replacing_class_callback',
 | 
        
           | 1 | efrain | 448 |         );
 | 
        
           |  |  | 449 |     }
 | 
        
           |  |  | 450 |   | 
        
           |  |  | 451 |     /**
 | 
        
           |  |  | 452 |      * Call a plugin class callback that has been replaced by a hook, and has a hook callback.
 | 
        
           |  |  | 453 |      *
 | 
        
           |  |  | 454 |      * The original callback should not be called, and no debugging should be output.
 | 
        
           |  |  | 455 |      *
 | 
        
           |  |  | 456 |      * @runInSeparateProcess
 | 
        
           |  |  | 457 |      */
 | 
        
           |  |  | 458 |     public function test_migrated_class_callback_with_replacement(): void {
 | 
        
           |  |  | 459 |         $this->resetAfterTest(true);
 | 
        
           |  |  | 460 |         // Include plugin hook discovery agent, the class containing callbacks, the hook that replaces the class callback,
 | 
        
           |  |  | 461 |         // and a hook callback for the new hook.
 | 
        
           |  |  | 462 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/callbacks.php');
 | 
        
           |  |  | 463 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hooks.php');
 | 
        
           |  |  | 464 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook/hook_replacing_class_callback.php');
 | 
        
           |  |  | 465 |         require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook_callbacks.php');
 | 
        
           |  |  | 466 |         // Register the fake plugin with the component manager.
 | 
        
           |  |  | 467 |         $this->setup_hooktest_plugin();
 | 
        
           |  |  | 468 |   | 
        
           |  |  | 469 |         // Register the fake plugin with the hook manager, including the hook callback.
 | 
        
           |  |  | 470 |         di::set(
 | 
        
           |  |  | 471 |             manager::class,
 | 
        
           |  |  | 472 |             manager::phpunit_get_instance([
 | 
        
           |  |  | 473 |                 'fake_hooktest' => __DIR__ . '/../fixtures/hook/fakeplugins/hooktest/db/hooks.php',
 | 
        
           |  |  | 474 |             ]),
 | 
        
           |  |  | 475 |         );
 | 
        
           |  |  | 476 |   | 
        
           |  |  | 477 |         // Confirm a non-deprecated class callback is called as expected.
 | 
        
           |  |  | 478 |         $this->assertEquals(
 | 
        
           |  |  | 479 |             'Called current class callback',
 | 
        
           |  |  | 480 |             component_class_callback('fake_hooktest\callbacks', 'current_class_callback', [])
 | 
        
           |  |  | 481 |         );
 | 
        
           |  |  | 482 |   | 
        
           |  |  | 483 |         // Confirm the deprecated class callback is not called, as expected.
 | 
        
           |  |  | 484 |         $this->assertNull(component_class_callback('fake_hooktest\callbacks', 'old_class_callback', [], null, true));
 | 
        
           |  |  | 485 |         $this->assertDebuggingNotCalled();
 | 
        
           |  |  | 486 |     }
 | 
        
           |  |  | 487 |   | 
        
           |  |  | 488 |     /**
 | 
        
           | 1441 | ariadna | 489 |      * Test verifying that callbacks for deprecated plugins are not returned and hook dispatching won't call into these plugins.
 | 
        
           |  |  | 490 |      *
 | 
        
           |  |  | 491 |      * @runInSeparateProcess
 | 
        
           |  |  | 492 |      * @return void
 | 
        
           |  |  | 493 |      */
 | 
        
           |  |  | 494 |     public function test_get_callbacks_for_hook_deprecated_plugintype(): void {
 | 
        
           |  |  | 495 |         $this->resetAfterTest();
 | 
        
           |  |  | 496 |   | 
        
           |  |  | 497 |         // Inject the fixture 'fake' plugin type into component sources, which includes a single 'fake_fullfeatured' plugin.
 | 
        
           |  |  | 498 |         // This 'fake_fullfeatured' plugin is an available plugin at this stage (not yet deprecated).
 | 
        
           |  |  | 499 |         $this->add_full_mocked_plugintype(
 | 
        
           |  |  | 500 |             plugintype: 'fake',
 | 
        
           |  |  | 501 |             path: 'lib/tests/fixtures/fakeplugins/fake',
 | 
        
           |  |  | 502 |         );
 | 
        
           |  |  | 503 |   | 
        
           |  |  | 504 |         // Force reset the static instance cache \core\hook\manager::$instance so that a fresh instance is instantiated, ensuring
 | 
        
           |  |  | 505 |         // the component lists are re-run and the hook manager can see the injected mock plugin and it's callbacks.
 | 
        
           |  |  | 506 |         // Note: we can't use \core\hook\manager::phpunit_get_instance() because that doesn't load in component callbacks from disk.
 | 
        
           |  |  | 507 |         $hookmanrc = new \ReflectionClass(\core\hook\manager::class);
 | 
        
           |  |  | 508 |         $hookmanrc->setStaticPropertyValue('instance', null);
 | 
        
           |  |  | 509 |         $manager = \core\hook\manager::get_instance();
 | 
        
           |  |  | 510 |   | 
        
           |  |  | 511 |         // Get all registered callbacks for the hook listened to by the mock plugin (after_course_created).
 | 
        
           |  |  | 512 |         $listeners = $manager->get_callbacks_for_hook(\core_course\hook\after_course_created::class);
 | 
        
           |  |  | 513 |         $componentswithcallbacks = array_column($listeners, 'component');
 | 
        
           |  |  | 514 |   | 
        
           |  |  | 515 |         // Verify the available mock plugin is returned as a listener.
 | 
        
           |  |  | 516 |         $this->assertContains('fake_fullfeatured', $componentswithcallbacks);
 | 
        
           |  |  | 517 |   | 
        
           |  |  | 518 |         // Deprecate the 'fake' plugin type.
 | 
        
           |  |  | 519 |         $this->deprecate_full_mocked_plugintype('fake');
 | 
        
           |  |  | 520 |   | 
        
           |  |  | 521 |         // Force a fresh plugin manager instance, again to ensure the up-to-date component lists are used.
 | 
        
           |  |  | 522 |         $hookmanrc->setStaticPropertyValue('instance', null);
 | 
        
           |  |  | 523 |         $manager = \core\hook\manager::get_instance();
 | 
        
           |  |  | 524 |   | 
        
           |  |  | 525 |         // And verify the plugin is now not returned as a listener, since it's deprecated.
 | 
        
           |  |  | 526 |         $listeners = $manager->get_callbacks_for_hook(\core_course\hook\after_course_created::class);
 | 
        
           |  |  | 527 |         $componentswithcallbacks = array_column($listeners, 'component');
 | 
        
           |  |  | 528 |         $this->assertNotContains('fake_fullfeatured', $componentswithcallbacks);
 | 
        
           |  |  | 529 |     }
 | 
        
           |  |  | 530 |   | 
        
           |  |  | 531 |     /**
 | 
        
           | 1 | efrain | 532 |      * Normalise the sort order of callbacks to help with asserts.
 | 
        
           |  |  | 533 |      *
 | 
        
           |  |  | 534 |      * @param array $callbacks
 | 
        
           |  |  | 535 |      */
 | 
        
           |  |  | 536 |     private function normalise_callbacks(array &$callbacks): void {
 | 
        
           |  |  | 537 |         foreach ($callbacks as &$callback) {
 | 
        
           |  |  | 538 |             ksort($callback);
 | 
        
           |  |  | 539 |         }
 | 
        
           |  |  | 540 |     }
 | 
        
           |  |  | 541 | }
 |