Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 9 May 2022 (12 months).
  • Bug fixes for security issues in 3.11.x will end 14 November 2022 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 35 and 311] [Versions 36 and 311] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       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   * Provides the unit tests class and some helper classes
      19   *
      20   * @package     core_plugin
      21   * @category    test
      22   * @copyright   2013, 2015 David Mudrak <david@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  require_once (__DIR__.'/fixtures/testable_update_validator.php');
      29  
      30  /**
      31   * Unit tests for the {@link \core\update\validator} class
      32   *
      33   * @copyright 2013, 2015 David Mudrak <david@moodle.com>
      34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      35   */
      36  class core_update_validator_testcase extends advanced_testcase {
      37  
      38      public function test_validate_files_layout() {
      39          $fixtures = __DIR__.'/fixtures/update_validator';
      40  
      41          // Non-existing directory.
      42          $validator = testable_core_update_validator::instance($fixtures.'/nulldir', array(
      43              'null/' => true,
      44              'null/lang/' => true,
      45              'null/lang/en/' => true,
      46              'null/lang/en/null.php' => true));
      47          $this->assertEquals('testable_core_update_validator', get_class($validator));
      48          $this->assertFalse($validator->execute());
      49          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR,
      50              'filenotexists', array('file' => 'null/')));
      51  
      52          // Missing expected file.
      53          $validator = testable_core_update_validator::instance($fixtures.'/plugindir', array(
      54              'foobar/' => true,
      55              'foobar/version.php' => true,
      56              'foobar/index.php' => true,
      57              'foobar/lang/' => true,
      58              'foobar/lang/en/' => true,
      59              'foobar/lang/en/local_foobar.php' => true,
      60              'foobar/NOTEXISTS.txt' => true));
      61          $this->assertFalse($validator->execute());
      62          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR,
      63              'filenotexists', array('file' => 'foobar/NOTEXISTS.txt')));
      64  
      65          // Errors during ZIP extraction.
      66          $validator = testable_core_update_validator::instance($fixtures.'/multidir', array(
      67              'one/' => true,
      68              'one/version.php' => 'Can not write target file',
      69              'two/' => true,
      70              'two/README.txt' => true));
      71          $this->assertFalse($validator->execute());
      72          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'filestatus',
      73              array('file' => 'one/version.php', 'status' => 'Can not write target file')));
      74  
      75          // Insufficient number of extracted files.
      76          $validator = testable_core_update_validator::instance($fixtures.'/emptydir', array(
      77              'emptydir/' => true,
      78              'emptydir/README.txt' => true));
      79          $this->assertFalse($validator->execute());
      80          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'filesnumber'));
      81  
      82          // No wrapping directory.
      83          $validator = testable_core_update_validator::instance($fixtures.'/nowrapdir', array(
      84              'version.php' => true,
      85              'index.php' => true,
      86              'lang/' => true,
      87              'lang/en/' => true,
      88              'lang/en/foo.php' => true));
      89          $this->assertFalse($validator->execute());
      90          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'onedir'));
      91  
      92          // Multiple directories.
      93          $validator = testable_core_update_validator::instance($fixtures.'/multidir', array(
      94              'one/' => true,
      95              'one/version.php' => true,
      96              'two/' => true,
      97              'two/README.txt' => true));
      98          $this->assertFalse($validator->execute());
      99          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'onedir'));
     100  
     101          // Invalid root directory name.
     102          $validator = testable_core_update_validator::instance($fixtures.'/github', array(
     103              'moodle-repository_mahara-master/' => true,
     104              'moodle-repository_mahara-master/lang/' => true,
     105              'moodle-repository_mahara-master/lang/en/' => true,
     106              'moodle-repository_mahara-master/lang/en/repository_mahara.php' => true,
     107              'moodle-repository_mahara-master/version.php' => true));
     108          $this->assertFalse($validator->execute());
     109          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'rootdirinvalid',
     110              'moodle-repository_mahara-master'));
     111      }
     112  
     113      public function test_validate_version_php() {
     114          $fixtures = __DIR__.'/fixtures/update_validator';
     115  
     116          $validator = testable_core_update_validator::instance($fixtures.'/noversiontheme', array(
     117              'noversion/' => true,
     118              'noversion/lang/' => true,
     119              'noversion/lang/en/' => true,
     120              'noversion/lang/en/theme_noversion.php' => true));
     121          $validator->assert_plugin_type('theme');
     122          $validator->assert_moodle_version(0);
     123          $this->assertTrue($validator->execute());
     124          $this->assertTrue($this->has_message($validator->get_messages(), $validator::DEBUG, 'missingversionphp'));
     125          $this->assertTrue(is_null($validator->get_versionphp_info()));
     126  
     127          $validator = testable_core_update_validator::instance($fixtures.'/noversionmod', array(
     128              'noversion/' => true,
     129              'noversion/lang/' => true,
     130              'noversion/lang/en/' => true,
     131              'noversion/lang/en/noversion.php' => true));
     132          $validator->assert_plugin_type('mod');
     133          $validator->assert_moodle_version(0);
     134          $this->assertFalse($validator->execute());
     135          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'missingversionphp'));
     136  
     137          $validator = testable_core_update_validator::instance($fixtures.'/plugindir', array(
     138              'legacymod/' => true,
     139              'legacymod/version.php' => true,
     140              'legacymod/lang/' => true,
     141              'legacymod/lang/en/' => true,
     142              'legacymod/lang/en/legacymod.php' => true));
     143          $validator->assert_plugin_type('mod');
     144          $validator->assert_moodle_version(0);
     145          $this->assertFalse($validator->execute());
     146          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'versionphpsyntax', '$module'));
     147  
     148          $validator = testable_core_update_validator::instance($fixtures.'/nocomponent', array(
     149              'baz/' => true,
     150              'baz/version.php' => true,
     151              'baz/lang/' => true,
     152              'baz/lang/en/' => true,
     153              'baz/lang/en/auth_baz.php' => true));
     154          $validator->assert_plugin_type('auth');
     155          $validator->assert_moodle_version(0);
     156          $this->assertFalse($validator->execute());
     157          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'missingcomponent'));
     158  
     159          $validator = testable_core_update_validator::instance($fixtures.'/plugindir', array(
     160              'foobar/' => true,
     161              'foobar/version.php' => true,
     162              'foobar/index.php' => true,
     163              'foobar/lang/' => true));
     164          $validator->assert_plugin_type('block');
     165          $validator->assert_moodle_version('2013031400.00');
     166          $this->assertFalse($validator->execute());
     167          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'componentmismatchtype',
     168              array('expected' => 'block', 'found' => 'local')));
     169  
     170          $validator = testable_core_update_validator::instance($fixtures.'/plugindir', array(
     171              'foobar/' => true,
     172              'foobar/version.php' => true,
     173              'foobar/index.php' => true,
     174              'foobar/lang/' => true,
     175              'foobar/lang/en/' => true,
     176              'foobar/lang/en/local_foobar.php' => true));
     177          $validator->assert_plugin_type('local');
     178          $validator->assert_moodle_version('2013031400.00');
     179          $this->assertTrue($validator->execute());
     180          $this->assertTrue($validator->get_result());
     181          $this->assertEquals('foobar', $validator->get_rootdir());
     182          $this->assertTrue($this->has_message($validator->get_messages(), $validator::INFO, 'rootdir', 'foobar'));
     183          $versionphpinfo = $validator->get_versionphp_info();
     184          $this->assertIsArray($versionphpinfo);
     185          $this->assertCount(4, $versionphpinfo);
     186          $this->assertEquals(2013031900, $versionphpinfo['version']);
     187          $this->assertEquals(2013031200, $versionphpinfo['requires']);
     188          $this->assertEquals('local_foobar', $versionphpinfo['component']);
     189          $this->assertEquals('MATURITY_ALPHA', $versionphpinfo['maturity']); // Note we get the constant name here.
     190          $this->assertEquals(MATURITY_ALPHA, constant($versionphpinfo['maturity'])); // This is how to get the real value.
     191          $this->assertTrue($this->has_message($validator->get_messages(), $validator::WARNING, 'maturity', 'MATURITY_ALPHA'));
     192      }
     193  
     194      public function test_validate_language_pack() {
     195          $fixtures = __DIR__.'/fixtures/update_validator';
     196  
     197          $validator = testable_core_update_validator::instance($fixtures.'/nolang', array(
     198              'bah/' => true,
     199              'bah/index.php' => true,
     200              'bah/view.php' => true,
     201              'bah/version.php' => true));
     202          $validator->assert_plugin_type('mod');
     203          $validator->assert_moodle_version(0);
     204          $this->assertFalse($validator->execute());
     205          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'missinglangenfolder'));
     206  
     207          $validator = testable_core_update_validator::instance($fixtures.'/nolang', array(
     208              'bah/' => true,
     209              'bah/version.php' => true,
     210              'bah/lang/' => true,
     211              'bah/lang/en/' => true));
     212          $validator->assert_plugin_type('mod');
     213          $validator->assert_moodle_version(0);
     214          $this->assertFalse($validator->execute());
     215          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'missinglangenfile'));
     216  
     217          $validator = testable_core_update_validator::instance($fixtures.'/nolang', array(
     218              'bah/' => true,
     219              'bah/version.php' => true,
     220              'bah/lang/' => true,
     221              'bah/lang/en/' => true,
     222              'bah/lang/en/bleh.php' => true,
     223              'bah/lang/en/bah.php' => true));
     224          $validator->assert_plugin_type('mod');
     225          $validator->assert_moodle_version(0);
     226          $this->assertTrue($validator->execute());
     227          $this->assertTrue($this->has_message($validator->get_messages(), $validator::WARNING, 'multiplelangenfiles'));
     228          $this->assertTrue(is_null($validator->get_language_file_name()));
     229  
     230          $validator = testable_core_update_validator::instance($fixtures.'/wronglang', array(
     231              'bah/' => true,
     232              'bah/version.php' => true,
     233              'bah/lang/' => true,
     234              'bah/lang/en/' => true,
     235              'bah/lang/en/bah.php' => true));
     236          $validator->assert_plugin_type('block');
     237          $validator->assert_moodle_version(0);
     238          $this->assertFalse($validator->execute());
     239          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'missingexpectedlangenfile',
     240              'block_bah.php'));
     241          $this->assertEquals('bah', $validator->get_language_file_name());
     242  
     243          $validator = testable_core_update_validator::instance($fixtures.'/noversiontheme', array(
     244              'noversion/' => true,
     245              'noversion/lang/' => true,
     246              'noversion/lang/en/' => true,
     247              'noversion/lang/en/theme_noversion.php' => true));
     248          $validator->assert_plugin_type('theme');
     249          $validator->assert_moodle_version(0);
     250          $this->assertTrue($validator->execute());
     251          $this->assertTrue($this->has_message($validator->get_messages(), $validator::DEBUG, 'foundlangfile', 'theme_noversion'));
     252          $this->assertEquals('theme_noversion', $validator->get_language_file_name());
     253  
     254          $validator = testable_core_update_validator::instance($fixtures.'/plugindir', array(
     255              'foobar/' => true,
     256              'foobar/version.php' => true,
     257              'foobar/index.php' => true,
     258              'foobar/lang/' => true,
     259              'foobar/lang/en/' => true,
     260              'foobar/lang/en/local_foobar.php' => true));
     261          $validator->assert_plugin_type('local');
     262          $validator->assert_moodle_version('2013031400.00');
     263          $this->assertTrue($validator->execute());
     264          $this->assertTrue($this->has_message($validator->get_messages(), $validator::DEBUG, 'foundlangfile', 'local_foobar'));
     265          $this->assertEquals('local_foobar', $validator->get_language_file_name());
     266      }
     267  
     268      public function test_validate_target_location() {
     269          $fixtures = __DIR__.'/fixtures/update_validator';
     270  
     271          $validator = testable_core_update_validator::instance($fixtures.'/installed', array(
     272              'greenbar/' => true,
     273              'greenbar/version.php' => true,
     274              'greenbar/index.php' => true,
     275              'greenbar/lang/' => true,
     276              'greenbar/lang/en/' => true,
     277              'greenbar/lang/en/local_greenbar.php' => true));
     278          $validator->assert_plugin_type('local');
     279          $validator->assert_moodle_version('2013031400.00');
     280  
     281          $this->assertTrue($validator->execute());
     282          $this->assertFalse($this->has_message($validator->get_messages(), $validator::WARNING, 'targetexists',
     283              $validator->get_plugintype_location('local').'/greenbar'));
     284  
     285          $typeroot = $validator->get_plugintype_location('local');
     286          make_writable_directory($typeroot.'/greenbar');
     287          $this->assertTrue($validator->execute());
     288          $this->assertTrue($this->has_message($validator->get_messages(), $validator::WARNING, 'targetexists',
     289              $validator->get_plugintype_location('local').'/greenbar'));
     290  
     291          remove_dir($typeroot.'/greenbar');
     292          file_put_contents($typeroot.'/greenbar', 'This file occupies a place where a plugin should land.');
     293          $this->assertFalse($validator->execute());
     294          $this->assertTrue($this->has_message($validator->get_messages(), $validator::ERROR, 'targetnotdir',
     295              $validator->get_plugintype_location('local').'/greenbar'));
     296  
     297          unlink($typeroot.'/greenbar');
     298          $this->assertTrue($validator->execute());
     299  
     300          $validator = testable_core_update_validator::instance($fixtures.'/plugindir', array(
     301              'foobar/' => true,
     302              'foobar/version.php' => true,
     303              'foobar/index.php' => true,
     304              'foobar/lang/' => true,
     305              'foobar/lang/en/' => true,
     306              'foobar/lang/en/local_foobar.php' => true));
     307          $validator->assert_plugin_type('local');
     308          $validator->assert_moodle_version('2013031400.00');
     309          $this->assertTrue($validator->execute());
     310          $this->assertTrue($this->has_message($validator->get_messages(), $validator::INFO, 'pathwritable',
     311              $validator->get_plugintype_location('local')));
     312      }
     313  
     314      public function test_parse_version_php() {
     315          $fixtures = __DIR__.'/fixtures/update_validator/versionphp';
     316  
     317          $validator = testable_core_update_validator::instance($fixtures, array());
     318          $this->assertEquals('testable_core_update_validator', get_class($validator));
     319  
     320          $info = $validator->testable_parse_version_php($fixtures.'/version1.php');
     321          $this->assertIsArray($info);
     322          $this->assertCount(7, $info);
     323          $this->assertEquals('block_foobar', $info['plugin->component']);  // Later in the file.
     324          $this->assertEquals('2013010100', $info['plugin->version']);      // Numeric wins over strings.
     325          $this->assertEquals('2012122401', $info['plugin->requires']);     // Commented.
     326          $this->assertEquals('MATURITY_STABLE', $info['module->maturity']);// Constant wins regardless the order (non-PHP behaviour).
     327          $this->assertEquals('MATURITY_ALPHA', $info['plugin->maturity']); // Constant wins regardless the order (non-PHP behaviour).
     328          $this->assertEquals('v2.3', $info['module->release']);            // String wins over numeric (non-PHP behaviour).
     329          $this->assertEquals('v2.4', $info['plugin->release']);            // String wins over numeric (non-PHP behaviour).
     330      }
     331  
     332      public function test_messages_output() {
     333          $fixtures = __DIR__.'/fixtures/update_validator';
     334          $validator = testable_core_update_validator::instance($fixtures, array());
     335  
     336          $this->assertDebuggingNotCalled();
     337          $this->assertNotEmpty($validator->message_level_name($validator::ERROR));
     338          $this->assertNotEmpty($validator->message_level_name($validator::WARNING));
     339          $this->assertNotEmpty($validator->message_level_name($validator::INFO));
     340          $this->assertNotEmpty($validator->message_level_name($validator::DEBUG));
     341  
     342          $this->assertNotEmpty($validator->message_code_name('missingversion'));
     343          $this->assertSame(
     344              'some_really_crazy_message_code_that_is_not_localised',
     345              $validator->message_code_name('some_really_crazy_message_code_that_is_not_localised')
     346          );
     347  
     348          $this->assertInstanceOf('help_icon', $validator->message_help_icon('onedir'));
     349          $this->assertFalse($validator->message_help_icon('some_really_crazy_message_code_that_is_not_localised'));
     350  
     351          $this->assertNotEmpty($validator->message_code_info('missingexpectedlangenfile', 'something'));
     352          $this->assertSame('', $validator->message_code_info('missingexpectedlangenfile', null));
     353          $this->assertSame('', $validator->message_code_info('some_really_crazy_message_code_that_is_not_localised', 'something'));
     354      }
     355  
     356      /**
     357       * Helper method for checking if the given message has been raised by the validator.
     358       *
     359       * @param array $messages list of returned messages
     360       * @param string $level expected message severity level
     361       * @param string $msgcode expected message code
     362       * @param string|array $addinfo expected additional info
     363       * @return bool
     364       */
     365      protected function has_message(array $messages, $level, $msgcode, $addinfo = null) {
     366          foreach ($messages as $message) {
     367              if ($message->level === $level and $message->msgcode === $msgcode and $message->addinfo === $addinfo) {
     368                  return true;
     369              }
     370          }
     371          return false;
     372      }
     373  }