Commit 08c1599c authored by webchick's avatar webchick

Issue #2057259 by alexpott, cweagans, swentel: Fixed Malformed...

Issue #2057259 by alexpott, cweagans, swentel: Fixed Malformed modulename.info.yml file causes fatal error.
parent a42b392c
...@@ -650,3 +650,5 @@ services: ...@@ -650,3 +650,5 @@ services:
class: Drupal\Core\Asset\JsCollectionGrouper class: Drupal\Core\Asset\JsCollectionGrouper
asset.js.dumper: asset.js.dumper:
class: Drupal\Core\Asset\AssetDumper class: Drupal\Core\Asset\AssetDumper
info_parser:
class: Drupal\Core\Extension\InfoParser
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
use Drupal\Component\Utility\Xss; use Drupal\Component\Utility\Xss;
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\Language; use Drupal\Core\Language\Language;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\PhpStorage\PhpStorageFactory; use Drupal\Component\PhpStorage\PhpStorageFactory;
...@@ -4600,54 +4599,19 @@ function element_set_attributes(array &$element, array $map) { ...@@ -4600,54 +4599,19 @@ function element_set_attributes(array &$element, array $map) {
/** /**
* Parses Drupal module and theme .info.yml files. * Parses Drupal module and theme .info.yml files.
* *
* Info files are NOT for placing arbitrary theme and module-specific settings.
* Use Config::get() and Config::set()->save() for that. Info files are
* formatted as YAML. If the 'version' key is set to 'VERSION' in any info file,
* then the value will be substituted with the current version of Drupal core.
*
* Information stored in a module .info.yml file:
* - name: The real name of the module for display purposes.
* - description: A brief description of the module.
* - dependencies: An array of shortnames of other modules this module requires.
* - package: The name of the package of modules this module belongs to.
*
* See forum.info.yml for an example of a module .info.yml file.
*
* Information stored in a theme .info.yml file:
* - name: The real name of the theme for display purposes.
* - description: Brief description.
* - screenshot: Path to screenshot relative to the theme's .info.yml file.
* - engine: Theme engine; typically twig.
* - base theme: Name of a base theme, if applicable.
* - regions: Listed regions.
* - features: Features available.
* - stylesheets: Theme stylesheets.
* - scripts: Theme scripts.
*
* See bartik.info.yml for an example of a theme .info.yml file.
*
* @param string $filename * @param string $filename
* The file we are parsing. Accepts file with relative or absolute path. * The file we are parsing. Accepts file with relative or absolute path.
* *
* @return array * @return array
* The info array. * The info array.
*
* @see \Drupal\Core\Extension\InfoParser::parse().
*
* @deprecated as of Drupal 8.0. Use \Drupal::service('info_parser')->parse()
* instead.
*/ */
function drupal_parse_info_file($filename) { function drupal_parse_info_file($filename) {
static $info = array(); return \Drupal::service('info_parser')->parse($filename);
if (!isset($info[$filename])) {
if (!file_exists($filename)) {
$info[$filename] = array();
}
else {
$parser = new Parser();
$info[$filename] = $parser->parse(file_get_contents($filename));
if (isset($info[$filename]['version']) && $info[$filename]['version'] === 'VERSION') {
$info[$filename]['version'] = \Drupal::VERSION;
}
}
}
return $info[$filename];
} }
/** /**
......
...@@ -464,6 +464,8 @@ function install_begin_request(&$install_state) { ...@@ -464,6 +464,8 @@ function install_begin_request(&$install_state) {
$container->register('asset.js.collection_renderer', 'Drupal\Core\Asset\JsCollectionRenderer') $container->register('asset.js.collection_renderer', 'Drupal\Core\Asset\JsCollectionRenderer')
->addArgument(new Reference('state')); ->addArgument(new Reference('state'));
// Register the info parser.
$container->register('info_parser', 'Drupal\Core\Extension\InfoParser');
} }
// Set the request in the kernel to the new created Request above // Set the request in the kernel to the new created Request above
// so it is available to the rest of the installation process. // so it is available to the rest of the installation process.
......
...@@ -1076,7 +1076,7 @@ function install_profile_info($profile, $langcode = 'en') { ...@@ -1076,7 +1076,7 @@ function install_profile_info($profile, $langcode = 'en') {
'php' => DRUPAL_MINIMUM_PHP, 'php' => DRUPAL_MINIMUM_PHP,
); );
$profile_file = drupal_get_path('profile', $profile) . "/$profile.info.yml"; $profile_file = drupal_get_path('profile', $profile) . "/$profile.info.yml";
$info = drupal_parse_info_file($profile_file); $info = \Drupal::service('info_parser')->parse($profile_file);
$info += $defaults; $info += $defaults;
$info['dependencies'] = array_unique(array_merge( $info['dependencies'] = array_unique(array_merge(
drupal_required_modules(), drupal_required_modules(),
......
...@@ -292,7 +292,7 @@ function drupal_required_modules() { ...@@ -292,7 +292,7 @@ function drupal_required_modules() {
$required[] = drupal_get_profile(); $required[] = drupal_get_profile();
foreach ($files as $name => $file) { foreach ($files as $name => $file) {
$info = drupal_parse_info_file($file->uri); $info = \Drupal::service('info_parser')->parse($file->uri);
if (!empty($info) && !empty($info['required']) && $info['required']) { if (!empty($info) && !empty($info['required']) && $info['required']) {
$required[] = $name; $required[] = $name;
} }
......
<?php
/**
* @file
* Contains Drupal\Core\Extension\InfoParser.
*/
namespace Drupal\Core\Extension;
use Drupal\Component\Utility\String;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
/**
* Class that parses Drupal module's, theme's and profile's .info.yml files.
*/
class InfoParser implements InfoParserInterface {
/**
* Array of all info keyed by filename.
*
* @var array
*/
protected $parsedInfos = array();
/**
* Symfony YAML parser object.
*
* @var \Symfony\Component\Yaml\Parser
*/
protected $parser;
/**
* {@inheritdoc}
*/
public function parse($filename) {
if (!isset($this->parsedInfos[$filename])) {
if (!file_exists($filename)) {
$this->parsedInfos[$filename] = array();
}
else {
try {
$this->parsedInfos[$filename] = $this->getParser()->parse(file_get_contents($filename));
}
catch (ParseException $e) {
$message = String::format("Unable to parse !file. Parser error !error.", array('!file' => $filename, '!error' => $e->getMessage()));
throw new InfoParserException($message, $filename);
}
$missing_keys = array_diff($this->getRequiredKeys(), array_keys($this->parsedInfos[$filename]));
if (!empty($missing_keys)) {
$message = format_plural(count($missing_keys), 'Missing required key (!missing_keys) in !file.', 'Missing required keys (!missing_keys) in !file.', array('!missing_keys' => implode(', ', $missing_keys), '!file' => $filename));
throw new InfoParserException($message, $filename);
}
if (isset($this->parsedInfos[$filename]['version']) && $this->parsedInfos[$filename]['version'] === 'VERSION') {
$this->parsedInfos[$filename]['version'] = \Drupal::VERSION;
}
}
}
return $this->parsedInfos[$filename];
}
/**
* Returns a parser for parsing .info.yml files.
*
* @return \Symfony\Component\Yaml\Parser
* Symfony YAML parser object.
*/
protected function getParser() {
if (!$this->parser) {
$this->parser = new Parser();
}
return $this->parser;
}
/**
* Returns an array of keys required to exist in .info.yml file.
*
* @return array
* An array of required keys.
*/
protected function getRequiredKeys() {
return array('name', 'type');
}
}
<?php
/**
* @file
* Contains Drupal\Core\Extension\InfoParserException.
*/
namespace Drupal\Core\Extension;
/**
* An exception thrown by the InfoParser class whilst parsing info.yml files.
*/
class InfoParserException extends \RuntimeException {
/**
* The info.yml filename.
*
* @var string
*/
protected $infoFilename;
/**
* Constructs the InfoParserException object.
*
* @param string $message
* The Exception message to throw.
* @param string $filename
* The info.yml filename.
*/
public function __construct($message, $info_filename) {
$this->infoFilename = $info_filename;
parent::__construct($message);
}
/**
* Gets the info.yml filename.
*
* @return string
* The info.yml filename.
*/
public function getInfoFilename () {
return $this->infoFilename;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Extension\InfoParserInterface.
*/
namespace Drupal\Core\Extension;
/**
* Interface for classes that parses Drupal's info.yml files.
*/
interface InfoParserInterface {
/**
* Parses Drupal module, theme and profile .info.yml files.
*
* Info files are NOT for placing arbitrary theme and module-specific
* settings. Use Config::get() and Config::set()->save() for that. Info files
* are formatted as YAML. If the 'version' key is set to 'VERSION' in any info
* file, then the value will be substituted with the current version of Drupal
* core.
*
* Information stored in all .info.yml files:
* - name: The real name of the module for display purposes. (Required)
* - description: A brief description of the module.
* - type: whether it is for a module or theme. (Required)
*
* Information stored in a module .info.yml file:
* - dependencies: An array of shortnames of other modules this module requires.
* - package: The name of the package of modules this module belongs to.
*
* See forum.info.yml for an example of a module .info.yml file.
*
* Information stored in a theme .info.yml file:
* - screenshot: Path to screenshot relative to the theme's .info.yml file.
* - engine: Theme engine; typically twig.
* - base theme: Name of a base theme, if applicable.
* - regions: Listed regions.
* - features: Features available.
* - stylesheets: Theme stylesheets.
* - scripts: Theme scripts.
*
* See bartik.info.yml for an example of a theme .info.yml file.
*
* @param string $filename
* The file we are parsing. Accepts file with relative or absolute path.
*
* @return array
* The info array.
*
* @throw \Drupal\Core\Extension\InfoParserException
* Exception thrown if there is a parsing error or the .info.yml file does
* not contain a required key.
*/
public function parse($filename);
}
...@@ -56,7 +56,7 @@ protected function process(array $files, array $files_to_add) { ...@@ -56,7 +56,7 @@ protected function process(array $files, array $files_to_add) {
// new resource on the list for merging. // new resource on the list for merging.
if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info.yml')) { if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info.yml')) {
// Get the .info.yml file for the module or theme this file belongs to. // Get the .info.yml file for the module or theme this file belongs to.
$info = drupal_parse_info_file($info_file); $info = \Drupal::service('info_parser')->parse($info_file);
// If the module or theme is incompatible with Drupal core, remove it // If the module or theme is incompatible with Drupal core, remove it
// from the array for the current search directory, so it is not // from the array for the current search directory, so it is not
......
...@@ -137,13 +137,10 @@ public static function getProjectName($directory) { ...@@ -137,13 +137,10 @@ public static function getProjectName($directory) {
*/ */
public static function getProjectTitle($directory) { public static function getProjectTitle($directory) {
$info_file = self::findInfoFile($directory); $info_file = self::findInfoFile($directory);
$info = drupal_parse_info_file($info_file); $info = \Drupal::service('info_parser')->parse($info_file);
if (empty($info)) { if (empty($info)) {
throw new UpdaterException(t('Unable to parse info file: %info_file.', array('%info_file' => $info_file))); throw new UpdaterException(t('Unable to parse info file: %info_file.', array('%info_file' => $info_file)));
} }
if (empty($info['name'])) {
throw new UpdaterException(t("The info file (%info_file) does not define a 'name' attribute.", array('%info_file' => $info_file)));
}
return $info['name']; return $info['name'];
} }
......
...@@ -201,7 +201,8 @@ function getProjectName($file) { ...@@ -201,7 +201,8 @@ function getProjectName($file) {
* Filters the project .info.yml data to only save attributes we need. * Filters the project .info.yml data to only save attributes we need.
* *
* @param array $info * @param array $info
* Array of .info.yml file data as returned by drupal_parse_info_file(). * Array of .info.yml file data as returned by
* \Drupal\Core\Extension\InfoParser.
* @param $additional_whitelist * @param $additional_whitelist
* (optional) Array of additional elements to be collected from the .info.yml * (optional) Array of additional elements to be collected from the .info.yml
* file. Defaults to array(). * file. Defaults to array().
......
name: 'Aggregator test views' name: 'Aggregator test views'
type: module
description: 'Provides default views for views aggregator tests.' description: 'Provides default views for views aggregator tests.'
package: Testing package: Testing
version: VERSION version: VERSION
......
name: 'Dblog test views' name: 'Dblog test views'
type: module
description: 'Provides default views for views dblog tests.' description: 'Provides default views for views dblog tests.'
package: Testing package: Testing
version: VERSION version: VERSION
......
...@@ -970,6 +970,9 @@ protected function prepareEnvironment() { ...@@ -970,6 +970,9 @@ protected function prepareEnvironment() {
// @todo Remove this once this class has no calls to t() and format_plural() // @todo Remove this once this class has no calls to t() and format_plural()
$this->container->register('string_translation', 'Drupal\Core\StringTranslation\TranslationManager'); $this->container->register('string_translation', 'Drupal\Core\StringTranslation\TranslationManager');
// Register info parser.
$this->container->register('info_parser', 'Drupal\Core\Extension\InfoParser');
\Drupal::setContainer($this->container); \Drupal::setContainer($this->container);
// Unset globals. // Unset globals.
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
* *
* The list of modules gets populated by module.info.yml files, which contain * The list of modules gets populated by module.info.yml files, which contain
* each module's name, description, and information about which modules it * each module's name, description, and information about which modules it
* requires. See drupal_parse_info_file() for info on module.info.yml * requires. See \Drupal\Core\Extension\InfoParser for info on module.info.yml
* descriptors. * descriptors.
*/ */
class ModulesListForm extends FormBase { class ModulesListForm extends FormBase {
......
<?php
/**
* @file
* Definition of Drupal\system\Tests\Common\ParseInfoFileUnitTest.
*/
namespace Drupal\system\Tests\Common;
use Drupal\simpletest\UnitTestBase;
/**
* Tests the drupal_parse_info_file() API function.
*/
class ParseInfoFileUnitTest extends UnitTestBase {
public static function getInfo() {
return array(
'name' => 'Parsing .info.yml files',
'description' => 'Tests the drupal_parse_info_file() API function.',
'group' => 'Common',
);
}
/**
* Parses an example .info.yml file and verifies the results.
*/
function testParseInfoFile() {
$info_values = drupal_parse_info_file(drupal_get_path('module', 'system') . '/tests/common_test_info.txt');
$this->assertEqual($info_values['simple_string'], 'A simple string', 'Simple string value was parsed correctly.', 'System');
$this->assertEqual($info_values['version'], \Drupal::VERSION, 'Constant value was parsed correctly.', 'System');
$this->assertEqual($info_values['double_colon'], 'dummyClassName::', 'Value containing double-colon was parsed correctly.', 'System');
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\Extension\InfoParserUnitTest.
*/
namespace Drupal\system\Tests\Extension;
use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\Core\Extension\InfoParser;
use Drupal\Core\Extension\InfoParserException;
/**
* Tests InfoParser class.
*
* Files for this test are stored in core/modules/system/tests/fixtures and end
* with .info.txt instead of info.yml in order not not be considered as real
* extensions.
*/
class InfoParserUnitTest extends DrupalUnitTestBase {
/**
* The InfoParser object.
*
* @var \Drupal\Core\Extension\InfoParser
*/
protected $infoParser;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'InfoParser',
'description' => 'Tests InfoParser class and exception.',
'group' => 'Extension',
);
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->infoParser = new InfoParser();
}
/**
* Tests the functionality of the infoParser object.
*/
public function testInfoParser() {
$info = $this->infoParser->parse('core/modules/system/tests/fixtures/does_not_exist.info.txt');
$this->assertTrue(empty($info), 'Non existing info.yml returns empty array.');
// Test that invalid YAML throws an exception and that message contains the
// filename that caused it.
$filename = 'core/modules/system/tests/fixtures/broken.info.txt';
try {
$this->infoParser->parse($filename);
$this->fail('Expected InfoParserException not thrown when reading broken.info.txt');
}
catch (InfoParserException $e) {
$this->assertTrue(strpos($e->getMessage(), $filename) !== FALSE, 'Exception message contains info.yml filename.');
}
// Tests that missing required keys are detected.
$filename = 'core/modules/system/tests/fixtures/missing_keys.info.txt';
try {
$this->infoParser->parse($filename);
$this->fail('Expected InfoParserException not thrown when reading missing_keys.info.txt');
}
catch (InfoParserException $e) {
$expected_message = 'Missing required keys (name, type) in core/modules/system/tests/fixtures/missing_keys.info.txt.';
$this->assertEqual($e->getMessage(), $expected_message);
}
// Tests that a single missing required key is detected.
$filename = 'core/modules/system/tests/fixtures/missing_key.info.txt';
try {
$this->infoParser->parse($filename);
$this->fail('Expected InfoParserException not thrown when reading missing_key.info.txt');
}
catch (InfoParserException $e) {
$expected_message = 'Missing required key (type) in core/modules/system/tests/fixtures/missing_key.info.txt.';
$this->assertEqual($e->getMessage(), $expected_message);
}
$info_values = $this->infoParser->parse('core/modules/system/tests/fixtures/common_test.info.txt');
$this->assertEqual($info_values['simple_string'], 'A simple string', 'Simple string value was parsed correctly.', 'System');
$this->assertEqual($info_values['version'], \Drupal::VERSION, 'Constant value was parsed correctly.', 'System');
$this->assertEqual($info_values['double_colon'], 'dummyClassName::', 'Value containing double-colon was parsed correctly.', 'System');
}
}
...@@ -1133,7 +1133,7 @@ function hook_system_theme_info() { ...@@ -1133,7 +1133,7 @@ function hook_system_theme_info() {
* This hook is invoked in _system_rebuild_module_data() and in * This hook is invoked in _system_rebuild_module_data() and in
* _system_rebuild_theme_data(). A module may implement this hook in order to * _system_rebuild_theme_data(). A module may implement this hook in order to
* add to or alter the data generated by reading the .info.yml file with * add to or alter the data generated by reading the .info.yml file with
* drupal_parse_info_file(). * \Drupal\Core\Extension\InfoParser.
* *
* @param $info * @param $info
* The .info.yml file contents, passed by reference so that it can be altered. * The .info.yml file contents, passed by reference so that it can be altered.
......
...@@ -2402,7 +2402,7 @@ function _system_rebuild_module_data() { ...@@ -2402,7 +2402,7 @@ function _system_rebuild_module_data() {
$modules[$key]->filename = $module->uri; $modules[$key]->filename = $module->uri;
// Look for the info file. // Look for the info file.
$module->info = drupal_parse_info_file(dirname($module->uri) . '/' . $module->name . '.info.yml'); $module->info = \Drupal::service('info_parser')->parse(dirname($module->uri) . '/' . $module->name . '.info.yml');
// Skip modules/profiles that don't provide info or have the wrong type. // Skip modules/profiles that don't provide info or have the wrong type.
if (empty($module->info) || !isset($module->info['type']) || !in_array($module->info['type'], array('module', 'profile'))) { if (empty($module->info) || !isset($module->info['type']) || !in_array($module->info['type'], array('module', 'profile'))) {
...@@ -2526,7 +2526,7 @@ function _system_rebuild_theme_data() { ...@@ -2526,7 +2526,7 @@ function _system_rebuild_theme_data() {
// Read info files for each theme // Read info files for each theme
foreach ($themes as $key => $theme) { foreach ($themes as $key => $theme) {
$themes[$key]->filename = $theme->uri; $themes[$key]->filename = $theme->uri;
$themes[$key]->info = drupal_parse_info_file($theme->uri) + $defaults; $themes[$key]->info = \Drupal::service('info_parser')->parse($theme->uri) + $defaults;
// Skip this extension if its type is not theme. // Skip this extension if its type is not theme.
if (!isset($themes[$key]->info['type']) || $themes[$key]->info['type'] != 'theme') { if (!isset($themes[$key]->info['type']) || $themes[$key]->info['type'] != 'theme') {
......
# info.yml for testing broken YAML parsing exception handling.
name: File
type: module
description: 'Defines a file field type.'
package: Core
version: VERSION
core: 8.x
dependencies::;;
- field
simple_string: A simple string name: common_test
type: module
description: 'testing info file parsing'
simple_string: 'A simple string'
version: "VERSION" version: "VERSION"
double_colon: dummyClassName:: double_colon: dummyClassName::
# info.yml for testing missing type key.
name: File
description: 'Defines a file field type.'
package: Core
version: VERSION
core: 8.x
dependencies:
- field
# info.yml for testing missing name, description, and type keys.
package: Core
version: VERSION
core: 8.x
dependencies:
- field
...@@ -624,7 +624,7 @@ function update_verify_update_archive($project, $archive_file, $directory) { ...@@ -624,7 +624,7 @@ function update_verify_update_archive($project, $archive_file, $directory) {
$files = file_scan_directory("$directory/$project", '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', array('key' => 'name', 'min_depth' => 0)); $files = file_scan_directory("$directory/$project", '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', array('key' => 'name', 'min_depth' => 0));
foreach ($files as $file) { foreach ($files as $file) {
// Get the .info.yml file for the module or theme this file belongs to. // Get the .info.yml file for the module or theme this file belongs to.
$info = drupal_parse_info_file($file->uri); $info = \Drupal::service('info_parser')->parse($file->uri);
// If the module or theme is incompatible with Drupal core, set an error. // If the module or theme is incompatible with Drupal core, set an error.
if (empty($info['core']) || $info['core'] != \Drupal::CORE_COMPATIBILITY) { if (empty($info['core']) || $info['core'] != \Drupal::CORE_COMPATIBILITY) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment