Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
  16  
  17  namespace core_courseformat;
  18  
  19  use moodle_exception;
  20  use stdClass;
  21  
  22  /**
  23   * Tests for the stateactions class.
  24   *
  25   * @package    core_courseformat
  26   * @category   test
  27   * @copyright  2021 Sara Arjona (sara@moodle.com)
  28   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29   * @coversDefaultClass \core_courseformat\stateactions
  30   */
  31  class stateactions_test extends \advanced_testcase {
  32  
  33      /**
  34       * Setup to ensure that fixtures are loaded.
  35       */
  36      public static function setupBeforeClass(): void {
  37          global $CFG;
  38          require_once($CFG->dirroot . '/lib/externallib.php');
  39      }
  40  
  41      /**
  42       * Helper method to create an activity into a section and add it to the $sections and $activities arrays.
  43       *
  44       * @param int $courseid Course identifier where the activity will be added.
  45       * @param string $type Activity type ('forum', 'assign', ...).
  46       * @param int $section Section number where the activity will be added.
  47       * @param bool $visible Whether the activity will be visible or not.
  48       * @return int the activity cm id
  49       */
  50      private function create_activity(
  51          int $courseid,
  52          string $type,
  53          int $section,
  54          bool $visible = true
  55      ): int {
  56  
  57          $activity = $this->getDataGenerator()->create_module(
  58              $type,
  59              ['course' => $courseid],
  60              [
  61                  'section' => $section,
  62                  'visible' => $visible
  63              ]
  64          );
  65          return $activity->cmid;
  66      }
  67  
  68      /**
  69       * Helper to create a course and generate a section list.
  70       *
  71       * @param string $format the course format
  72       * @param int $sections the number of sections
  73       * @param int[] $hiddensections the section numbers to hide
  74       * @return stdClass the course object
  75       */
  76      private function create_course(string $format, int $sections, array $hiddensections): stdClass {
  77          global $DB;
  78  
  79          $course = $this->getDataGenerator()->create_course(['numsections' => $sections, 'format' => $format]);
  80          foreach ($hiddensections as $section) {
  81              set_section_visible($course->id, $section, 0);
  82          }
  83  
  84          return $course;
  85      }
  86  
  87      /**
  88       * Return an array if the course references.
  89       *
  90       * This method is used to create alias to sections and other stuff in the dataProviders.
  91       *
  92       * @param stdClass $course the course object
  93       * @return int[] a relation betwee all references and its element id
  94       */
  95      private function course_references(stdClass $course): array {
  96          global $DB;
  97  
  98          $references = [];
  99  
 100          $sectionrecords = $DB->get_records('course_sections', ['course' => $course->id]);
 101          foreach ($sectionrecords as $id => $section) {
 102              $references["section{$section->section}"] = $section->id;
 103          }
 104          $references['course'] = $course->id;
 105          $references['invalidsection'] = -1;
 106          $references['invalidcm'] = -1;
 107  
 108          return $references;
 109      }
 110  
 111      /**
 112       * Translate a references array into current ids.
 113       *
 114       * @param string[] $references the references list
 115       * @param string[] $values the values to translate
 116       * @return int[] the list of ids
 117       */
 118      private function translate_references(array $references, array $values): array {
 119          $result = [];
 120          foreach ($values as $value) {
 121              $result[] = $references[$value];
 122          }
 123          return $result;
 124      }
 125  
 126      /**
 127       * Generate a sorted and summarized list of an state updates message.
 128       *
 129       * It is important to note that the order in the update messages are not important in a real scenario
 130       * because each message affects a specific part of the course state. However, for the PHPUnit test
 131       * have them sorted and classified simplifies the asserts.
 132       *
 133       * @param stateupdates $updateobj the state updates object
 134       * @return array of all data updates.
 135       */
 136      private function summarize_updates(stateupdates $updateobj): array {
 137          // Check state returned after executing given action.
 138          $updatelist = $updateobj->jsonSerialize();
 139  
 140          // Initial summary structure.
 141          $result = [
 142              'create' => [
 143                  'course' => [],
 144                  'section' => [],
 145                  'cm' => [],
 146                  'count' => 0,
 147              ],
 148              'put' => [
 149                  'course' => [],
 150                  'section' => [],
 151                  'cm' => [],
 152                  'count' => 0,
 153              ],
 154              'remove' => [
 155                  'course' => [],
 156                  'section' => [],
 157                  'cm' => [],
 158                  'count' => 0,
 159              ],
 160          ];
 161          foreach ($updatelist as $update) {
 162              if (!isset($result[$update->action])) {
 163                  $result[$update->action] = [
 164                      'course' => [],
 165                      'section' => [],
 166                      'cm' => [],
 167                      'count' => 0,
 168                  ];
 169              }
 170              $elementid = $update->fields->id ?? 0;
 171              $result[$update->action][$update->name][$elementid] = $update->fields;
 172              $result[$update->action]['count']++;
 173          }
 174          return $result;
 175      }
 176  
 177      /**
 178       * Test the behaviour course_state.
 179       *
 180       * @dataProvider get_state_provider
 181       * @covers ::course_state
 182       * @covers ::section_state
 183       * @covers ::cm_state
 184       *
 185       * @param string $format The course will be created with this course format.
 186       * @param string $role The role of the user that will execute the method.
 187       * @param string $method the method to call
 188       * @param array $params the ids, targetsection and targetcm to use as params
 189       * @param array $expectedresults List of the course module names expected after calling the method.
 190       * @param bool $expectedexception If this call will raise an exception.
 191  
 192       */
 193      public function test_get_state(
 194          string $format,
 195          string $role,
 196          string $method,
 197          array $params,
 198          array $expectedresults,
 199          bool $expectedexception = false
 200      ): void {
 201  
 202          $this->resetAfterTest();
 203  
 204          // Create a course with 3 sections, 1 of them hidden.
 205          $course = $this->create_course($format, 3, [2]);
 206  
 207          $references = $this->course_references($course);
 208  
 209          // Create and enrol user using given role.
 210          if ($role == 'admin') {
 211              $this->setAdminUser();
 212          } else {
 213              $user = $this->getDataGenerator()->create_user();
 214              if ($role != 'unenroled') {
 215                  $this->getDataGenerator()->enrol_user($user->id, $course->id, $role);
 216              }
 217              $this->setUser($user);
 218          }
 219  
 220          // Add some activities to the course. One visible and one hidden in both sections 1 and 2.
 221          $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
 222          $references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
 223          $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
 224          $references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
 225  
 226          if ($expectedexception) {
 227              $this->expectException(moodle_exception::class);
 228          }
 229  
 230          // Initialise stateupdates.
 231          $courseformat = course_get_format($course->id);
 232          $updates = new stateupdates($courseformat);
 233  
 234          // Execute given method.
 235          $actions = new stateactions();
 236          $actions->$method(
 237              $updates,
 238              $course,
 239              $this->translate_references($references, $params['ids']),
 240              $references[$params['targetsectionid']] ?? null,
 241              $references[$params['targetcmid']] ?? null
 242          );
 243  
 244          // Format results in a way we can compare easily.
 245          $results = $this->summarize_updates($updates);
 246  
 247          // The state actions does not use create or remove actions because they are designed
 248          // to refresh parts of the state.
 249          $this->assertEquals(0, $results['create']['count']);
 250          $this->assertEquals(0, $results['remove']['count']);
 251  
 252          // Validate we have all the expected entries.
 253          $expectedtotal = count($expectedresults['course']) + count($expectedresults['section']) + count($expectedresults['cm']);
 254          $this->assertEquals($expectedtotal, $results['put']['count']);
 255  
 256          // Validate course, section and cm.
 257          foreach ($expectedresults as $name => $referencekeys) {
 258              foreach ($referencekeys as $referencekey) {
 259                  $this->assertArrayHasKey($references[$referencekey], $results['put'][$name]);
 260              }
 261          }
 262      }
 263  
 264      /**
 265       * Data provider for data request creation tests.
 266       *
 267       * @return array the testing scenarios
 268       */
 269      public function get_state_provider(): array {
 270          return array_merge(
 271              $this->course_state_provider('weeks'),
 272              $this->course_state_provider('topics'),
 273              $this->course_state_provider('social'),
 274              $this->section_state_provider('weeks', 'admin'),
 275              $this->section_state_provider('weeks', 'editingteacher'),
 276              $this->section_state_provider('weeks', 'student'),
 277              $this->section_state_provider('topics', 'admin'),
 278              $this->section_state_provider('topics', 'editingteacher'),
 279              $this->section_state_provider('topics', 'student'),
 280              $this->section_state_provider('social', 'admin'),
 281              $this->section_state_provider('social', 'editingteacher'),
 282              $this->section_state_provider('social', 'student'),
 283              $this->cm_state_provider('weeks', 'admin'),
 284              $this->cm_state_provider('weeks', 'editingteacher'),
 285              $this->cm_state_provider('weeks', 'student'),
 286              $this->cm_state_provider('topics', 'admin'),
 287              $this->cm_state_provider('topics', 'editingteacher'),
 288              $this->cm_state_provider('topics', 'student'),
 289              $this->cm_state_provider('social', 'admin'),
 290              $this->cm_state_provider('social', 'editingteacher'),
 291              $this->cm_state_provider('social', 'student'),
 292          );
 293      }
 294  
 295      /**
 296       * Course state data provider.
 297       *
 298       * @param string $format the course format
 299       * @return array the testing scenarios
 300       */
 301      public function course_state_provider(string $format): array {
 302          $expectedexception = ($format === 'social');
 303          return [
 304              // Tests for course_state.
 305              "admin $format course_state" => [
 306                  'format' => $format,
 307                  'role' => 'admin',
 308                  'method' => 'course_state',
 309                  'params' => [
 310                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 311                  ],
 312                  'expectedresults' => [
 313                      'course' => ['course'],
 314                      'section' => ['section0', 'section1', 'section2', 'section3'],
 315                      'cm' => ['cm0', 'cm1', 'cm2', 'cm3'],
 316                  ],
 317                  'expectedexception' => $expectedexception,
 318              ],
 319              "editingteacher $format course_state" => [
 320                  'format' => $format,
 321                  'role' => 'editingteacher',
 322                  'method' => 'course_state',
 323                  'params' => [
 324                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 325                  ],
 326                  'expectedresults' => [
 327                      'course' => ['course'],
 328                      'section' => ['section0', 'section1', 'section2', 'section3'],
 329                      'cm' => ['cm0', 'cm1', 'cm2', 'cm3'],
 330                  ],
 331                  'expectedexception' => $expectedexception,
 332              ],
 333              "student $format course_state" => [
 334                  'format' => $format,
 335                  'role' => 'student',
 336                  'method' => 'course_state',
 337                  'params' => [
 338                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 339                  ],
 340                  'expectedresults' => [
 341                      'course' => ['course'],
 342                      'section' => ['section0', 'section1', 'section3'],
 343                      'cm' => ['cm0'],
 344                  ],
 345                  'expectedexception' => $expectedexception,
 346              ],
 347          ];
 348      }
 349  
 350      /**
 351       * Section state data provider.
 352       *
 353       * @param string $format the course format
 354       * @param string $role the user role
 355       * @return array the testing scenarios
 356       */
 357      public function section_state_provider(string $format, string $role): array {
 358  
 359          // Social format will raise an exception and debug messages because it does not
 360          // use sections and it does not provide a renderer.
 361          $expectedexception = ($format === 'social');
 362  
 363          // All sections and cms that the user can access to.
 364          $usersections = ['section0', 'section1', 'section2', 'section3'];
 365          $usercms = ['cm0', 'cm1', 'cm2', 'cm3'];
 366          if ($role == 'student') {
 367              $usersections = ['section0', 'section1', 'section3'];
 368              $usercms = ['cm0'];
 369          }
 370  
 371          return [
 372              "$role $format section_state no section" => [
 373                  'format' => $format,
 374                  'role' => $role,
 375                  'method' => 'section_state',
 376                  'params' => [
 377                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 378                  ],
 379                  'expectedresults' => [],
 380                  'expectedexception' => true,
 381              ],
 382              "$role $format section_state section 0" => [
 383                  'format' => $format,
 384                  'role' => $role,
 385                  'method' => 'section_state',
 386                  'params' => [
 387                      'ids' => ['section0'], 'targetsectionid' => null, 'targetcmid' => null
 388                  ],
 389                  'expectedresults' => [
 390                      'course' => [],
 391                      'section' => array_intersect(['section0'], $usersections),
 392                      'cm' => [],
 393                  ],
 394                  'expectedexception' => $expectedexception,
 395              ],
 396              "$role $format section_state visible section" => [
 397                  'format' => $format,
 398                  'role' => $role,
 399                  'method' => 'section_state',
 400                  'params' => [
 401                      'ids' => ['section1'], 'targetsectionid' => null, 'targetcmid' => null
 402                  ],
 403                  'expectedresults' => [
 404                      'course' => [],
 405                      'section' => array_intersect(['section1'], $usersections),
 406                      'cm' => array_intersect(['cm0', 'cm1'], $usercms),
 407                  ],
 408                  'expectedexception' => $expectedexception,
 409              ],
 410              "$role $format section_state hidden section" => [
 411                  'format' => $format,
 412                  'role' => $role,
 413                  'method' => 'section_state',
 414                  'params' => [
 415                      'ids' => ['section2'], 'targetsectionid' => null, 'targetcmid' => null
 416                  ],
 417                  'expectedresults' => [
 418                      'course' => [],
 419                      'section' => array_intersect(['section2'], $usersections),
 420                      'cm' => array_intersect(['cm2', 'cm3'], $usercms),
 421                  ],
 422                  'expectedexception' => $expectedexception,
 423              ],
 424              "$role $format section_state several sections" => [
 425                  'format' => $format,
 426                  'role' => $role,
 427                  'method' => 'section_state',
 428                  'params' => [
 429                      'ids' => ['section1', 'section3'], 'targetsectionid' => null, 'targetcmid' => null
 430                  ],
 431                  'expectedresults' => [
 432                      'course' => [],
 433                      'section' => array_intersect(['section1', 'section3'], $usersections),
 434                      'cm' => array_intersect(['cm0', 'cm1'], $usercms),
 435                  ],
 436                  'expectedexception' => $expectedexception,
 437              ],
 438              "$role $format section_state invalid section" => [
 439                  'format' => $format,
 440                  'role' => $role,
 441                  'method' => 'section_state',
 442                  'params' => [
 443                      'ids' => ['invalidsection'], 'targetsectionid' => null, 'targetcmid' => null
 444                  ],
 445                  'expectedresults' => [],
 446                  'expectedexception' => true,
 447              ],
 448              "$role $format section_state using target section" => [
 449                  'format' => $format,
 450                  'role' => $role,
 451                  'method' => 'section_state',
 452                  'params' => [
 453                      'ids' => ['section1'], 'targetsectionid' => 'section3', 'targetcmid' => null
 454                  ],
 455                  'expectedresults' => [
 456                      'course' => [],
 457                      'section' => array_intersect(['section1', 'section3'], $usersections),
 458                      'cm' => array_intersect(['cm0', 'cm1'], $usercms),
 459                  ],
 460                  'expectedexception' => $expectedexception,
 461              ],
 462              "$role $format section_state using target targetcmid" => [
 463                  'format' => $format,
 464                  'role' => $role,
 465                  'method' => 'section_state',
 466                  'params' => [
 467                      'ids' => ['section3'], 'targetsectionid' => null, 'targetcmid' => 'cm1'
 468                  ],
 469                  'expectedresults' => [
 470                      'course' => [],
 471                      'section' => array_intersect(['section3'], $usersections),
 472                      'cm' => array_intersect(['cm1'], $usercms),
 473                  ],
 474                  'expectedexception' => $expectedexception,
 475              ],
 476          ];
 477      }
 478  
 479      /**
 480       * Course module state data provider.
 481       *
 482       * @param string $format the course format
 483       * @param string $role the user role
 484       * @return array the testing scenarios
 485       */
 486      public function cm_state_provider(string $format, string $role): array {
 487  
 488          // All sections and cms that the user can access to.
 489          $usersections = ['section0', 'section1', 'section2', 'section3'];
 490          $usercms = ['cm0', 'cm1', 'cm2', 'cm3'];
 491          if ($role == 'student') {
 492              $usersections = ['section0', 'section1', 'section3'];
 493              $usercms = ['cm0'];
 494          }
 495  
 496          return [
 497              "$role $format cm_state no cms" => [
 498                  'format' => $format,
 499                  'role' => $role,
 500                  'method' => 'cm_state',
 501                  'params' => [
 502                      'ids' => [], 'targetsectionid' => null, 'targetcmid' => null
 503                  ],
 504                  'expectedresults' => [],
 505                  'expectedexception' => true,
 506              ],
 507              "$role $format cm_state visible cm" => [
 508                  'format' => $format,
 509                  'role' => $role,
 510                  'method' => 'cm_state',
 511                  'params' => [
 512                      'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => null
 513                  ],
 514                  'expectedresults' => [
 515                      'course' => [],
 516                      'section' => array_intersect(['section1'], $usersections),
 517                      'cm' => array_intersect(['cm0'], $usercms),
 518                  ],
 519                  'expectedexception' => false,
 520              ],
 521              "$role $format cm_state hidden cm" => [
 522                  'format' => $format,
 523                  'role' => $role,
 524                  'method' => 'cm_state',
 525                  'params' => [
 526                      'ids' => ['cm1'], 'targetsectionid' => null, 'targetcmid' => null
 527                  ],
 528                  'expectedresults' => [
 529                      'course' => [],
 530                      'section' => array_intersect(['section1'], $usersections),
 531                      'cm' => array_intersect(['cm1'], $usercms),
 532                  ],
 533                  'expectedexception' => false,
 534              ],
 535              "$role $format cm_state several cm" => [
 536                  'format' => $format,
 537                  'role' => $role,
 538                  'method' => 'cm_state',
 539                  'params' => [
 540                      'ids' => ['cm0', 'cm2'], 'targetsectionid' => null, 'targetcmid' => null
 541                  ],
 542                  'expectedresults' => [
 543                      'course' => [],
 544                      'section' => array_intersect(['section1', 'section2'], $usersections),
 545                      'cm' => array_intersect(['cm0', 'cm2'], $usercms),
 546                  ],
 547                  'expectedexception' => false,
 548              ],
 549              "$role $format cm_state using targetsection" => [
 550                  'format' => $format,
 551                  'role' => $role,
 552                  'method' => 'cm_state',
 553                  'params' => [
 554                      'ids' => ['cm0'], 'targetsectionid' => 'section2', 'targetcmid' => null
 555                  ],
 556                  'expectedresults' => [
 557                      'course' => [],
 558                      'section' => array_intersect(['section1', 'section2'], $usersections),
 559                      'cm' => array_intersect(['cm0'], $usercms),
 560                  ],
 561                  'expectedexception' => ($format === 'social'),
 562              ],
 563              "$role $format cm_state using targetcm" => [
 564                  'format' => $format,
 565                  'role' => $role,
 566                  'method' => 'cm_state',
 567                  'params' => [
 568                      'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => 'cm3'
 569                  ],
 570                  'expectedresults' => [
 571                      'course' => [],
 572                      'section' => array_intersect(['section1', 'section2'], $usersections),
 573                      'cm' => array_intersect(['cm0', 'cm3'], $usercms),
 574                  ],
 575                  'expectedexception' => false,
 576              ],
 577              "$role $format cm_state using an invalid cm" => [
 578                  'format' => $format,
 579                  'role' => $role,
 580                  'method' => 'cm_state',
 581                  'params' => [
 582                      'ids' => ['invalidcm'], 'targetsectionid' => null, 'targetcmid' => null
 583                  ],
 584                  'expectedresults' => [],
 585                  'expectedexception' => true,
 586              ],
 587          ];
 588      }
 589  
 590      /**
 591       * Internal method for testing a specific state action.
 592       *
 593       * @param string $method the method to test
 594       * @param string $role the user role
 595       * @param string[] $idrefs the sections or cms id references to be used as method params
 596       * @param bool $expectedexception whether the call should throw an exception
 597       * @param int $expectedtotal the expected total number of state puts
 598       * @param string|null $coursefield the course field to check
 599       * @param int|string|null $coursevalue the section field value
 600       * @param string|null $sectionfield the section field to check
 601       * @param int|string|null $sectionvalue the section field value
 602       * @param string|null $cmfield the cm field to check
 603       * @param int|string|null $cmvalue the cm field value
 604       * @return array the state update summary
 605       */
 606      protected function basic_state_text(
 607          string $method = 'section_hide',
 608          string $role = 'editingteacher',
 609          array $idrefs = [],
 610          bool $expectedexception = false,
 611          int $expectedtotal = 0,
 612          ?string $coursefield = null,
 613          $coursevalue = 0,
 614          ?string $sectionfield = null,
 615          $sectionvalue = 0,
 616          ?string $cmfield = null,
 617          $cmvalue = 0
 618      ): array {
 619          $this->resetAfterTest();
 620  
 621          // Create a course with 3 sections, 1 of them hidden.
 622          $course = $this->create_course('topics', 3, [2]);
 623  
 624          $references = $this->course_references($course);
 625  
 626          $user = $this->getDataGenerator()->create_user();
 627          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role);
 628          $this->setUser($user);
 629  
 630          // Add some activities to the course. One visible and one hidden in both sections 1 and 2.
 631          $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
 632          $references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
 633          $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
 634          $references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
 635  
 636          if ($expectedexception) {
 637              $this->expectException(moodle_exception::class);
 638          }
 639  
 640          // Initialise stateupdates.
 641          $courseformat = course_get_format($course->id);
 642          $updates = new stateupdates($courseformat);
 643  
 644          // Execute the method.
 645          $actions = new stateactions();
 646          $actions->$method(
 647              $updates,
 648              $course,
 649              $this->translate_references($references, $idrefs),
 650          );
 651  
 652          // Format results in a way we can compare easily.
 653          $results = $this->summarize_updates($updates);
 654  
 655          // Most state actions does not use create or remove actions because they are designed
 656          // to refresh parts of the state.
 657          $this->assertEquals(0, $results['create']['count']);
 658          $this->assertEquals(0, $results['remove']['count']);
 659  
 660          // Validate we have all the expected entries.
 661          $this->assertEquals($expectedtotal, $results['put']['count']);
 662  
 663          // Validate course, section and cm.
 664          if (!empty($coursefield)) {
 665              foreach ($results['put']['course'] as $courseid) {
 666                  $this->assertEquals($coursevalue, $results['put']['course'][$courseid][$coursefield]);
 667              }
 668          }
 669          if (!empty($sectionfield)) {
 670              foreach ($results['put']['section'] as $section) {
 671                  $this->assertEquals($sectionvalue, $section->$sectionfield);
 672              }
 673          }
 674          if (!empty($cmfield)) {
 675              foreach ($results['put']['cm'] as $cm) {
 676                  $this->assertEquals($cmvalue, $cm->$cmfield);
 677              }
 678          }
 679          return $results;
 680      }
 681  
 682      /**
 683       * Test for section_hide
 684       *
 685       * @covers ::section_hide
 686       * @dataProvider basic_role_provider
 687       * @param string $role the user role
 688       * @param bool $expectedexception if it will expect an exception.
 689       */
 690      public function test_section_hide(
 691          string $role = 'editingteacher',
 692          bool $expectedexception = false
 693      ): void {
 694          $this->basic_state_text(
 695              'section_hide',
 696              $role,
 697              ['section1', 'section2', 'section3'],
 698              $expectedexception,
 699              7,
 700              null,
 701              null,
 702              'visible',
 703              0,
 704              null,
 705              null
 706          );
 707      }
 708  
 709      /**
 710       * Test for section_hide
 711       *
 712       * @covers ::section_show
 713       * @dataProvider basic_role_provider
 714       * @param string $role the user role
 715       * @param bool $expectedexception if it will expect an exception.
 716       */
 717      public function test_section_show(
 718          string $role = 'editingteacher',
 719          bool $expectedexception = false
 720      ): void {
 721          $this->basic_state_text(
 722              'section_show',
 723              $role,
 724              ['section1', 'section2', 'section3'],
 725              $expectedexception,
 726              7,
 727              null,
 728              null,
 729              'visible',
 730              1,
 731              null,
 732              null
 733          );
 734      }
 735  
 736      /**
 737       * Test for cm_show
 738       *
 739       * @covers ::cm_show
 740       * @dataProvider basic_role_provider
 741       * @param string $role the user role
 742       * @param bool $expectedexception if it will expect an exception.
 743       */
 744      public function test_cm_show(
 745          string $role = 'editingteacher',
 746          bool $expectedexception = false
 747      ): void {
 748          $this->basic_state_text(
 749              'cm_show',
 750              $role,
 751              ['cm0', 'cm1', 'cm2', 'cm3'],
 752              $expectedexception,
 753              4,
 754              null,
 755              null,
 756              null,
 757              null,
 758              'visible',
 759              1
 760          );
 761      }
 762  
 763      /**
 764       * Test for cm_hide
 765       *
 766       * @covers ::cm_hide
 767       * @dataProvider basic_role_provider
 768       * @param string $role the user role
 769       * @param bool $expectedexception if it will expect an exception.
 770       */
 771      public function test_cm_hide(
 772          string $role = 'editingteacher',
 773          bool $expectedexception = false
 774      ): void {
 775          $this->basic_state_text(
 776              'cm_hide',
 777              $role,
 778              ['cm0', 'cm1', 'cm2', 'cm3'],
 779              $expectedexception,
 780              4,
 781              null,
 782              null,
 783              null,
 784              null,
 785              'visible',
 786              0
 787          );
 788      }
 789  
 790      /**
 791       * Test for cm_stealth
 792       *
 793       * @covers ::cm_stealth
 794       * @dataProvider basic_role_provider
 795       * @param string $role the user role
 796       * @param bool $expectedexception if it will expect an exception.
 797       */
 798      public function test_cm_stealth(
 799          string $role = 'editingteacher',
 800          bool $expectedexception = false
 801      ): void {
 802          set_config('allowstealth', 1);
 803          $this->basic_state_text(
 804              'cm_stealth',
 805              $role,
 806              ['cm0', 'cm1', 'cm2', 'cm3'],
 807              $expectedexception,
 808              4,
 809              null,
 810              null,
 811              null,
 812              null,
 813              'stealth',
 814              1
 815          );
 816          // Disable stealth.
 817          set_config('allowstealth', 0);
 818          // When stealth are disabled the validation is a but more complex because they depends
 819          // also on the section visibility (legacy stealth).
 820          $this->basic_state_text(
 821              'cm_stealth',
 822              $role,
 823              ['cm0', 'cm1'],
 824              $expectedexception,
 825              2,
 826              null,
 827              null,
 828              null,
 829              null,
 830              'stealth',
 831              0
 832          );
 833          $this->basic_state_text(
 834              'cm_stealth',
 835              $role,
 836              ['cm2', 'cm3'],
 837              $expectedexception,
 838              2,
 839              null,
 840              null,
 841              null,
 842              null,
 843              'stealth',
 844              1
 845          );
 846      }
 847  
 848      /**
 849       * Data provider for basic role tests.
 850       *
 851       * @return array the testing scenarios
 852       */
 853      public function basic_role_provider() {
 854          return [
 855              'editingteacher' => [
 856                  'role' => 'editingteacher',
 857                  'expectedexception' => false,
 858              ],
 859              'teacher' => [
 860                  'role' => 'teacher',
 861                  'expectedexception' => true,
 862              ],
 863              'student' => [
 864                  'role' => 'student',
 865                  'expectedexception' => true,
 866              ],
 867              'guest' => [
 868                  'role' => 'guest',
 869                  'expectedexception' => true,
 870              ],
 871          ];
 872      }
 873  
 874      /**
 875       * Test for cm_moveright
 876       *
 877       * @covers ::cm_moveright
 878       * @dataProvider basic_role_provider
 879       * @param string $role the user role
 880       * @param bool $expectedexception if it will expect an exception.
 881       */
 882      public function test_cm_moveright(
 883          string $role = 'editingteacher',
 884          bool $expectedexception = false
 885      ): void {
 886          $this->basic_state_text(
 887              'cm_moveright',
 888              $role,
 889              ['cm0', 'cm1', 'cm2', 'cm3'],
 890              $expectedexception,
 891              4,
 892              null,
 893              null,
 894              null,
 895              null,
 896              'indent',
 897              1
 898          );
 899      }
 900  
 901      /**
 902       * Test for cm_moveleft
 903       *
 904       * @covers ::cm_moveleft
 905       * @dataProvider basic_role_provider
 906       * @param string $role the user role
 907       * @param bool $expectedexception if it will expect an exception.
 908       */
 909      public function test_cm_moveleft(
 910          string $role = 'editingteacher',
 911          bool $expectedexception = false
 912      ): void {
 913          $this->basic_state_text(
 914              'cm_moveleft',
 915              $role,
 916              ['cm0', 'cm1', 'cm2', 'cm3'],
 917              $expectedexception,
 918              4,
 919              null,
 920              null,
 921              null,
 922              null,
 923              'indent',
 924              0
 925          );
 926      }
 927  }