diff --git a/admin/tool/installaddon/classes/installer.php b/admin/tool/installaddon/classes/installer.php index be0da1c16f373..d9807f1111c87 100644 --- a/admin/tool/installaddon/classes/installer.php +++ b/admin/tool/installaddon/classes/installer.php @@ -459,6 +459,27 @@ public function move_directory($source, $target, $dirpermissions, $filepermissio clearstatcache(); } + /** + * Detect the given plugin's component name + * + * Only plugins that declare valid $plugin->component value in the version.php + * are supported. + * + * @param string $zipfilepath full path to the saved ZIP file + * @param string $workdir full path to the directory we can use for extracting required bits from the archive + * @return string|bool declared component name or false if unable to detect + */ + public function detect_plugin_component($zipfilepath, $workdir) { + + $versionphp = $this->extract_versionphp_file($zipfilepath, $workdir); + + if (empty($versionphp)) { + return false; + } + + return $this->detect_plugin_component_from_versionphp(file_get_contents($workdir.'/'.$versionphp)); + } + //// End of external API /////////////////////////////////////////////////// /** @@ -626,6 +647,86 @@ protected function decode_remote_request($request) { return $data; } + + /** + * Extracts the version.php from the given plugin ZIP file into the target directory + * + * @param string $zipfilepath full path to the saved ZIP file + * @param string $targetdir full path to extract the file to + * @return string|bool path to the version.php within the $targetpath; false on error (e.g. not found) + */ + protected function extract_versionphp_file($zipfilepath, $targetdir) { + global $CFG; + require_once($CFG->libdir.'/filelib.php'); + + $fp = get_file_packer('application/zip'); + $files = $fp->list_files($zipfilepath); + + if (empty($files)) { + return false; + } + + $rootdirname = null; + $found = null; + + foreach ($files as $file) { + // Valid plugin ZIP package has just one root directory with all + // files in it. + $pathnameitems = explode('/', $file->pathname); + + if (empty($pathnameitems)) { + return false; + } + + // Set the expected name of the root directory in the first + // iteration of the loop. + if ($rootdirname === null) { + $rootdirname = $pathnameitems[0]; + } + + // Require the same root directory for all files in the ZIP + // package. + if ($rootdirname !== $pathnameitems[0]) { + return false; + } + + // If we reached the valid version.php file, remember it. + if ($pathnameitems[1] === 'version.php' and !$file->is_directory and $file->size > 0) { + $found = $file->pathname; + } + } + + if (empty($found)) { + return false; + } + + $extracted = $fp->extract_to_pathname($zipfilepath, $targetdir, array($found)); + + if (empty($extracted)) { + return false; + } + + // The following syntax uses function array dereferencing, added in PHP 5.4.0. + return array_keys($extracted)[0]; + } + + /** + * Return the plugin component declared in its version.php file + * + * @param string $code the contents of the version.php file + * @return string|bool declared plugin component or false if unable to detect + */ + protected function detect_plugin_component_from_versionphp($code) { + + $result = preg_match_all('#^\s*\$plugin\->component\s*=\s*([\'"])(.+?_.+?)\1\s*;#m', $code, $matches); + + // Return if and only if the single match was detected. + if ($result === 1 and !empty($matches[2][0])) { + return $matches[2][0]; + } + + return false; + } } diff --git a/admin/tool/installaddon/tests/fixtures/zips/bar.zip b/admin/tool/installaddon/tests/fixtures/zips/bar.zip new file mode 100644 index 0000000000000..b190d2e6f7c8e Binary files /dev/null and b/admin/tool/installaddon/tests/fixtures/zips/bar.zip differ diff --git a/admin/tool/installaddon/tests/installer_test.php b/admin/tool/installaddon/tests/installer_test.php index 3ebf2f7056445..7280eb9f82f25 100644 --- a/admin/tool/installaddon/tests/installer_test.php +++ b/admin/tool/installaddon/tests/installer_test.php @@ -143,6 +143,21 @@ public function test_move_directory() { $this->assertTrue(is_file($jobroot.'/moved/sub/folder/readme.txt')); $this->assertSame('Hello world!', file_get_contents($jobroot.'/moved/sub/folder/readme.txt')); } + + public function test_detect_plugin_component() { + $jobid = md5(rand().uniqid('test_', true)); + $workdir = make_temp_directory('tool_installaddon/'.$jobid.'/version'); + $zipfile = __DIR__.'/fixtures/zips/bar.zip'; + $installer = tool_installaddon_installer::instance(); + $this->assertEquals('foo_bar', $installer->detect_plugin_component($zipfile, $workdir)); + } + + public function test_detect_plugin_component_from_versionphp() { + $installer = testable_tool_installaddon_installer::instance(); + $this->assertEquals('bar_bar_conan', $installer->detect_plugin_component_from_versionphp(' +$plugin->version = 2014121300; + $plugin->component= "bar_bar_conan" ; // Go Arnie go!')); + } } @@ -173,4 +188,8 @@ public function testable_decode_remote_request($request) { protected function should_send_site_info() { return true; } + + public function detect_plugin_component_from_versionphp($code) { + return parent::detect_plugin_component_from_versionphp($code); + } }