Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.6.x will end 11 November 2019 (12 months).
  • Bug fixes for security issues in 3.6.x will end 11 May 2020 (18 months) - Support has ended.
  • minimum PHP 7.0.0 Note: minimum PHP version has increased since Moodle 3.3. PHP 7.1.x and 7.2.x are supported too. PHP 7.3.x support is being implemented (@ MDL-63420) and not ready for production with this release.
  • Differences Between: [Versions 35 and 36] [Versions 36 and 310] [Versions 36 and 311] [Versions 36 and 38] [Versions 36 and 39]

       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  /**
      18   * Test tablelib.
      19   *
      20   * @package    core
      21   * @category   phpunit
      22   * @copyright  2013 Damyon Wiese <damyon@moodle.com>
      23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      24   */
      25  
      26  defined('MOODLE_INTERNAL') || die();
      27  
      28  global $CFG;
      29  require_once($CFG->libdir . '/tablelib.php');
      30  require_once($CFG->libdir . '/tests/fixtures/testable_flexible_table.php');
      31  
      32  /**
      33   * Test some of tablelib.
      34   *
      35   * @package    core
      36   * @category   phpunit
      37   * @copyright  2013 Damyon Wiese <damyon@moodle.com>
      38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      39   */
      40  class core_tablelib_testcase extends advanced_testcase {
      41      protected function generate_columns($cols) {
      42          $columns = array();
      43          foreach (range(0, $cols - 1) as $j) {
      44              array_push($columns, 'column' . $j);
      45          }
      46          return $columns;
      47      }
      48  
      49      protected function generate_headers($cols) {
      50          $columns = array();
      51          foreach (range(0, $cols - 1) as $j) {
      52              array_push($columns, 'Column ' . $j);
      53          }
      54          return $columns;
      55      }
      56  
      57      protected function generate_data($rows, $cols) {
      58          $data = array();
      59  
      60          foreach (range(0, $rows - 1) as $i) {
      61              $row = array();
      62              foreach (range(0, $cols - 1) as $j) {
      63                  $val =  'row ' . $i . ' col ' . $j;
      64                  $row['column' . $j] = $val;
      65              }
      66              array_push($data, $row);
      67          }
      68          return $data;
      69      }
      70  
      71      /**
      72       * Create a table with properties as passed in params, add data and output html.
      73       *
      74       * @param string[] $columns
      75       * @param string[] $headers
      76       * @param bool     $sortable
      77       * @param bool     $collapsible
      78       * @param string[] $suppress
      79       * @param string[] $nosorting
      80       * @param (array|object)[] $data
      81       * @param int      $pagesize
      82       */
      83      protected function run_table_test($columns, $headers, $sortable, $collapsible, $suppress, $nosorting, $data, $pagesize) {
      84          $table = $this->create_and_setup_table($columns, $headers, $sortable, $collapsible, $suppress, $nosorting);
      85          $table->pagesize($pagesize, count($data));
      86          foreach ($data as $row) {
      87              $table->add_data_keyed($row);
      88          }
      89          $table->finish_output();
      90      }
      91  
      92      /**
      93       * Create a table with properties as passed in params.
      94       *
      95       * @param string[] $columns
      96       * @param string[] $headers
      97       * @param bool $sortable
      98       * @param bool $collapsible
      99       * @param string[] $suppress
     100       * @param string[] $nosorting
     101       * @return flexible_table
     102       */
     103      protected function create_and_setup_table($columns, $headers, $sortable, $collapsible, $suppress, $nosorting) {
     104          $table = new flexible_table('tablelib_test');
     105  
     106          $table->define_columns($columns);
     107          $table->define_headers($headers);
     108          $table->define_baseurl('/invalid.php');
     109  
     110          $table->sortable($sortable);
     111          $table->collapsible($collapsible);
     112          foreach ($suppress as $column) {
     113              $table->column_suppress($column);
     114          }
     115  
     116          foreach ($nosorting as $column) {
     117              $table->no_sorting($column);
     118          }
     119  
     120          $table->setup();
     121          return $table;
     122      }
     123  
     124      public function test_empty_table() {
     125          $this->expectOutputRegex('/' . get_string('nothingtodisplay') . '/');
     126          $this->run_table_test(
     127              array('column1', 'column2'),       // Columns.
     128              array('Column 1', 'Column 2'),     // Headers.
     129              true,                              // Sortable.
     130              false,                             // Collapsible.
     131              array(),                           // Suppress columns.
     132              array(),                           // No sorting.
     133              array(),                           // Data.
     134              10                                 // Page size.
     135          );
     136      }
     137  
     138      public function test_has_next_pagination() {
     139  
     140          $data = $this->generate_data(11, 2);
     141          $columns = $this->generate_columns(2);
     142          $headers = $this->generate_headers(2);
     143  
     144          // Search for pagination controls containing 'page-link"\saria-label="Next"'.
     145          $this->expectOutputRegex('/page-link"\saria-label="Next"/');
     146  
     147          $this->run_table_test(
     148              $columns,
     149              $headers,
     150              true,
     151              false,
     152              array(),
     153              array(),
     154              $data,
     155              10
     156          );
     157      }
     158  
     159      public function test_has_hide() {
     160  
     161          $data = $this->generate_data(11, 2);
     162          $columns = $this->generate_columns(2);
     163          $headers = $this->generate_headers(2);
     164  
     165          // Search for 'hide' links in the column headers.
     166          $this->expectOutputRegex('/' . get_string('hide') . '/');
     167  
     168          $this->run_table_test(
     169              $columns,
     170              $headers,
     171              true,
     172              true,
     173              array(),
     174              array(),
     175              $data,
     176              10
     177          );
     178      }
     179  
     180      public function test_has_not_hide() {
     181  
     182          $data = $this->generate_data(11, 2);
     183          $columns = $this->generate_columns(2);
     184          $headers = $this->generate_headers(2);
     185  
     186          // Make sure there are no 'hide' links in the headers.
     187  
     188          ob_start();
     189          $this->run_table_test(
     190              $columns,
     191              $headers,
     192              true,
     193              false,
     194              array(),
     195              array(),
     196              $data,
     197              10
     198          );
     199          $output = ob_get_contents();
     200          ob_end_clean();
     201          $this->assertNotContains(get_string('hide'), $output);
     202      }
     203  
     204      public function test_has_sort() {
     205  
     206          $data = $this->generate_data(11, 2);
     207          $columns = $this->generate_columns(2);
     208          $headers = $this->generate_headers(2);
     209  
     210          // Search for pagination controls containing '1.*2</a>.*Next</a>'.
     211          $this->expectOutputRegex('/' . get_string('sortby') . '/');
     212  
     213          $this->run_table_test(
     214              $columns,
     215              $headers,
     216              true,
     217              false,
     218              array(),
     219              array(),
     220              $data,
     221              10
     222          );
     223      }
     224  
     225      public function test_has_not_sort() {
     226  
     227          $data = $this->generate_data(11, 2);
     228          $columns = $this->generate_columns(2);
     229          $headers = $this->generate_headers(2);
     230  
     231          // Make sure there are no 'Sort by' links in the headers.
     232  
     233          ob_start();
     234          $this->run_table_test(
     235              $columns,
     236              $headers,
     237              false,
     238              false,
     239              array(),
     240              array(),
     241              $data,
     242              10
     243          );
     244          $output = ob_get_contents();
     245          ob_end_clean();
     246          $this->assertNotContains(get_string('sortby'), $output);
     247      }
     248  
     249      public function test_has_not_next_pagination() {
     250  
     251          $data = $this->generate_data(10, 2);
     252          $columns = $this->generate_columns(2);
     253          $headers = $this->generate_headers(2);
     254  
     255          // Make sure there are no 'Next' links in the pagination.
     256  
     257          ob_start();
     258          $this->run_table_test(
     259              $columns,
     260              $headers,
     261              true,
     262              false,
     263              array(),
     264              array(),
     265              $data,
     266              10
     267          );
     268  
     269          $output = ob_get_contents();
     270          ob_end_clean();
     271          $this->assertNotContains(get_string('next'), $output);
     272      }
     273  
     274      public function test_1_col() {
     275  
     276          $data = $this->generate_data(100, 1);
     277          $columns = $this->generate_columns(1);
     278          $headers = $this->generate_headers(1);
     279  
     280          $this->expectOutputRegex('/row 0 col 0/');
     281  
     282          $this->run_table_test(
     283              $columns,
     284              $headers,
     285              true,
     286              false,
     287              array(),
     288              array(),
     289              $data,
     290              10
     291          );
     292      }
     293  
     294      public function test_empty_rows() {
     295  
     296          $data = $this->generate_data(1, 5);
     297          $columns = $this->generate_columns(5);
     298          $headers = $this->generate_headers(5);
     299  
     300          // Test that we have at least 5 columns generated for each empty row.
     301          $this->expectOutputRegex('/emptyrow.*r9_c4/');
     302  
     303          $this->run_table_test(
     304              $columns,
     305              $headers,
     306              true,
     307              false,
     308              array(),
     309              array(),
     310              $data,
     311              10
     312          );
     313      }
     314  
     315      public function test_5_cols() {
     316  
     317          $data = $this->generate_data(100, 5);
     318          $columns = $this->generate_columns(5);
     319          $headers = $this->generate_headers(5);
     320  
     321          $this->expectOutputRegex('/row 0 col 0/');
     322  
     323          $this->run_table_test(
     324              $columns,
     325              $headers,
     326              true,
     327              false,
     328              array(),
     329              array(),
     330              $data,
     331              10
     332          );
     333      }
     334  
     335      public function test_50_cols() {
     336  
     337          $data = $this->generate_data(100, 50);
     338          $columns = $this->generate_columns(50);
     339          $headers = $this->generate_headers(50);
     340  
     341          $this->expectOutputRegex('/row 0 col 0/');
     342  
     343          $this->run_table_test(
     344              $columns,
     345              $headers,
     346              true,
     347              false,
     348              array(),
     349              array(),
     350              $data,
     351              10
     352          );
     353      }
     354  
     355      /**
     356       * Data provider for test_fullname_column
     357       *
     358       * @return array
     359       */
     360      public function fullname_column_provider() {
     361          return [
     362              ['language'],
     363              ['alternatename lastname'],
     364              ['firstname lastnamephonetic'],
     365          ];
     366      }
     367  
     368      /**
     369       * Test fullname column observes configured alternate fullname format configuration
     370       *
     371       * @param string $format
     372       * @return void
     373       *
     374       * @dataProvider fullname_column_provider
     375       */
     376      public function test_fullname_column(string $format) {
     377          $this->resetAfterTest();
     378          $this->setAdminUser();
     379  
     380          set_config('alternativefullnameformat', $format);
     381  
     382          $user = $this->getDataGenerator()->create_user();
     383  
     384          $table = $this->create_and_setup_table(['fullname'], [], true, false, [], []);
     385          $this->assertContains(fullname($user, true), $table->format_row($user)['fullname']);
     386      }
     387  
     388      /**
     389       * Test fullname column ignores fullname format configuration for a user with viewfullnames capability prohibited
     390       *
     391       * @param string $format
     392       * @return void
     393       *
     394       * @dataProvider fullname_column_provider
     395       */
     396      public function test_fullname_column_prohibit_viewfullnames(string $format) {
     397          global $DB, $CFG;
     398  
     399          $this->resetAfterTest();
     400  
     401          set_config('alternativefullnameformat', $format);
     402  
     403          $currentuser = $this->getDataGenerator()->create_user();
     404          $this->setUser($currentuser);
     405  
     406          // Prohibit the viewfullnames from the default user role.
     407          $userrole = $DB->get_record('role', ['id' => $CFG->defaultuserroleid]);
     408          role_change_permission($userrole->id, context_system::instance(), 'moodle/site:viewfullnames', CAP_PROHIBIT);
     409  
     410          $user = $this->getDataGenerator()->create_user();
     411  
     412          $table = $this->create_and_setup_table(['fullname'], [], true, false, [], []);
     413          $this->assertContains(fullname($user, false), $table->format_row($user)['fullname']);
     414      }
     415  
     416      public function test_get_row_html() {
     417          $data = $this->generate_data(1, 5);
     418          $columns = $this->generate_columns(5);
     419          $headers = $this->generate_headers(5);
     420          $data = array_keys(array_flip($data[0]));
     421  
     422          $table = new flexible_table('tablelib_test');
     423          $table->define_columns($columns);
     424          $table->define_headers($headers);
     425          $table->define_baseurl('/invalid.php');
     426  
     427          $row = $table->get_row_html($data);
     428          $this->assertRegExp('/row 0 col 0/', $row);
     429          $this->assertRegExp('/<tr class=""/', $row);
     430          $this->assertRegExp('/<td class="cell c0"/', $row);
     431      }
     432  
     433      public function test_persistent_table() {
     434          global $SESSION;
     435  
     436          $data = $this->generate_data(5, 5);
     437          $columns = $this->generate_columns(5);
     438          $headers = $this->generate_headers(5);
     439  
     440          // Testing without persistence first to verify that the results are different.
     441          $table1 = new flexible_table('tablelib_test');
     442          $table1->define_columns($columns);
     443          $table1->define_headers($headers);
     444          $table1->define_baseurl('/invalid.php');
     445  
     446          $table1->sortable(true);
     447          $table1->collapsible(true);
     448  
     449          $table1->is_persistent(false);
     450          $_GET['thide'] = 'column0';
     451          $_GET['tsort'] = 'column1';
     452          $_GET['tifirst'] = 'A';
     453          $_GET['tilast'] = 'Z';
     454  
     455          foreach ($data as $row) {
     456              $table1->add_data_keyed($row);
     457          }
     458          $table1->setup();
     459  
     460          // Clear session data between each new table.
     461          unset($SESSION->flextable);
     462  
     463          $table2 = new flexible_table('tablelib_test');
     464          $table2->define_columns($columns);
     465          $table2->define_headers($headers);
     466          $table2->define_baseurl('/invalid.php');
     467  
     468          $table2->sortable(true);
     469          $table2->collapsible(true);
     470  
     471          $table2->is_persistent(false);
     472          unset($_GET);
     473  
     474          foreach ($data as $row) {
     475              $table2->add_data_keyed($row);
     476          }
     477          $table2->setup();
     478  
     479          $this->assertNotEquals($table1, $table2);
     480  
     481          unset($SESSION->flextable);
     482  
     483          // Now testing with persistence to check that the tables are the same.
     484          $table3 = new flexible_table('tablelib_test');
     485          $table3->define_columns($columns);
     486          $table3->define_headers($headers);
     487          $table3->define_baseurl('/invalid.php');
     488  
     489          $table3->sortable(true);
     490          $table3->collapsible(true);
     491  
     492          $table3->is_persistent(true);
     493          $_GET['thide'] = 'column0';
     494          $_GET['tsort'] = 'column1';
     495          $_GET['tifirst'] = 'A';
     496          $_GET['tilast'] = 'Z';
     497  
     498          foreach ($data as $row) {
     499              $table3->add_data_keyed($row);
     500          }
     501          $table3->setup();
     502  
     503          unset($SESSION->flextable);
     504  
     505          $table4 = new flexible_table('tablelib_test');
     506          $table4->define_columns($columns);
     507          $table4->define_headers($headers);
     508          $table4->define_baseurl('/invalid.php');
     509  
     510          $table4->sortable(true);
     511          $table4->collapsible(true);
     512  
     513          $table4->is_persistent(true);
     514          unset($_GET);
     515  
     516          foreach ($data as $row) {
     517              $table4->add_data_keyed($row);
     518          }
     519          $table4->setup();
     520  
     521          $this->assertEquals($table3, $table4);
     522  
     523          unset($SESSION->flextable);
     524  
     525          // Finally, another test with no persistence, but without clearing the session data.
     526          $table5 = new flexible_table('tablelib_test');
     527          $table5->define_columns($columns);
     528          $table5->define_headers($headers);
     529          $table5->define_baseurl('/invalid.php');
     530  
     531          $table5->sortable(true);
     532          $table5->collapsible(true);
     533  
     534          $table5->is_persistent(true);
     535          $_GET['thide'] = 'column0';
     536          $_GET['tsort'] = 'column1';
     537          $_GET['tifirst'] = 'A';
     538          $_GET['tilast'] = 'Z';
     539  
     540          foreach ($data as $row) {
     541              $table5->add_data_keyed($row);
     542          }
     543          $table5->setup();
     544  
     545          $table6 = new flexible_table('tablelib_test');
     546          $table6->define_columns($columns);
     547          $table6->define_headers($headers);
     548          $table6->define_baseurl('/invalid.php');
     549  
     550          $table6->sortable(true);
     551          $table6->collapsible(true);
     552  
     553          $table6->is_persistent(true);
     554          unset($_GET);
     555  
     556          foreach ($data as $row) {
     557              $table6->add_data_keyed($row);
     558          }
     559          $table6->setup();
     560  
     561          $this->assertEquals($table5, $table6);
     562      }
     563  
     564      /**
     565       * Helper method for preparing tables instances in {@link self::test_can_be_reset()}.
     566       *
     567       * @param string $tableid
     568       * @return testable_flexible_table
     569       */
     570      protected function prepare_table_for_reset_test($tableid) {
     571          global $SESSION;
     572  
     573          unset($SESSION->flextable[$tableid]);
     574  
     575          $data = $this->generate_data(25, 3);
     576          $columns = array('column0', 'column1', 'column2');
     577          $headers = $this->generate_headers(3);
     578  
     579          $table = new testable_flexible_table($tableid);
     580          $table->define_baseurl('/invalid.php');
     581          $table->define_columns($columns);
     582          $table->define_headers($headers);
     583          $table->collapsible(true);
     584          $table->is_persistent(false);
     585  
     586          return $table;
     587      }
     588  
     589      public function test_can_be_reset() {
     590          // Table in its default state (as if seen for the first time), nothing to reset.
     591          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     592          $table->setup();
     593          $this->assertFalse($table->can_be_reset());
     594  
     595          // Table in its default state with default sorting defined, nothing to reset.
     596          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     597          $table->sortable(true, 'column1', SORT_DESC);
     598          $table->setup();
     599          $this->assertFalse($table->can_be_reset());
     600  
     601          // Table explicitly sorted by the default column & direction, nothing to reset.
     602          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     603          $table->sortable(true, 'column1', SORT_DESC);
     604          $_GET['tsort'] = 'column1';
     605          $_GET['tdir'] = SORT_DESC;
     606          $table->setup();
     607          unset($_GET['tsort']);
     608          unset($_GET['tdir']);
     609          $this->assertFalse($table->can_be_reset());
     610  
     611          // Table explicitly sorted twice by the default column & direction, nothing to reset.
     612          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     613          $table->sortable(true, 'column1', SORT_DESC);
     614          $_GET['tsort'] = 'column1';
     615          $_GET['tdir'] = SORT_DESC;
     616          $table->setup();
     617          $table->setup(); // Set up again to simulate the second page request.
     618          unset($_GET['tsort']);
     619          unset($_GET['tdir']);
     620          $this->assertFalse($table->can_be_reset());
     621  
     622          // Table sorted by other than default column, can be reset.
     623          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     624          $table->sortable(true, 'column1', SORT_DESC);
     625          $_GET['tsort'] = 'column2';
     626          $table->setup();
     627          unset($_GET['tsort']);
     628          $this->assertTrue($table->can_be_reset());
     629  
     630          // Table sorted by other than default direction, can be reset.
     631          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     632          $table->sortable(true, 'column1', SORT_DESC);
     633          $_GET['tsort'] = 'column1';
     634          $_GET['tdir'] = SORT_ASC;
     635          $table->setup();
     636          unset($_GET['tsort']);
     637          unset($_GET['tdir']);
     638          $this->assertTrue($table->can_be_reset());
     639  
     640          // Table sorted by the default column after another sorting previously selected.
     641          // This leads to different ORDER BY than just having a single sort defined, can be reset.
     642          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     643          $table->sortable(true, 'column1', SORT_DESC);
     644          $_GET['tsort'] = 'column0';
     645          $table->setup();
     646          $_GET['tsort'] = 'column1';
     647          $table->setup();
     648          unset($_GET['tsort']);
     649          $this->assertTrue($table->can_be_reset());
     650  
     651          // Table having some column collapsed, can be reset.
     652          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     653          $_GET['thide'] = 'column2';
     654          $table->setup();
     655          unset($_GET['thide']);
     656          $this->assertTrue($table->can_be_reset());
     657  
     658          // Table having some column explicitly expanded, nothing to reset.
     659          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     660          $_GET['tshow'] = 'column2';
     661          $table->setup();
     662          unset($_GET['tshow']);
     663          $this->assertFalse($table->can_be_reset());
     664  
     665          // Table after expanding a collapsed column, nothing to reset.
     666          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     667          $_GET['thide'] = 'column0';
     668          $table->setup();
     669          $_GET['tshow'] = 'column0';
     670          $table->setup();
     671          unset($_GET['thide']);
     672          unset($_GET['tshow']);
     673          $this->assertFalse($table->can_be_reset());
     674  
     675          // Table with some name filtering enabled, can be reset.
     676          $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
     677          $_GET['tifirst'] = 'A';
     678          $table->setup();
     679          unset($_GET['tifirst']);
     680          $this->assertTrue($table->can_be_reset());
     681      }
     682  }