Пример #1
0
 /**
  * {@inheritdoc}
  */
 public function getVisibleBlocksPerRegion(array &$cacheable_metadata = [])
 {
     $active_theme = $this->themeManager->getActiveTheme();
     // Build an array of the region names in the right order.
     $empty = array_fill_keys($active_theme->getRegions(), array());
     $full = array();
     foreach ($this->blockStorage->loadByProperties(array('theme' => $active_theme->getName())) as $block_id => $block) {
         /** @var \Drupal\block\BlockInterface $block */
         $access = $block->access('view', NULL, TRUE);
         $region = $block->getRegion();
         if (!isset($cacheable_metadata[$region])) {
             $cacheable_metadata[$region] = CacheableMetadata::createFromObject($access);
         } else {
             $cacheable_metadata[$region] = $cacheable_metadata[$region]->merge(CacheableMetadata::createFromObject($access));
         }
         // Set the contexts on the block before checking access.
         if ($access->isAllowed()) {
             $full[$region][$block_id] = $block;
         }
     }
     // Merge it with the actual values to maintain the region ordering.
     $assignments = array_intersect_key(array_merge($empty, $full), $empty);
     foreach ($assignments as &$assignment) {
         // Suppress errors because PHPUnit will indirectly modify the contents,
         // triggering https://bugs.php.net/bug.php?id=50688.
         @uasort($assignment, 'Drupal\\block\\Entity\\Block::sort');
     }
     return $assignments;
 }
 /**
  * {@inheritdoc}
  */
 protected function getCid()
 {
     if (!isset($this->cid)) {
         $this->cid = 'library_info:' . $this->themeManager->getActiveTheme()->getName();
     }
     return $this->cid;
 }
Пример #3
0
 /**
  * Ensures that Stable overrides all relevant core library assets.
  */
 public function testStableLibraryOverrides()
 {
     // First get the clean library definitions with no active theme.
     $libraries_before = $this->getAllLibraries();
     $libraries_before = $this->removeVendorAssets($libraries_before);
     $this->themeManager->setActiveTheme($this->themeInitialization->getActiveThemeByName('stable'));
     $this->libraryDiscovery->clearCachedDefinitions();
     // Now get the library definitions with Stable as the active theme.
     $libraries_after = $this->getAllLibraries();
     $libraries_after = $this->removeVendorAssets($libraries_after);
     $root = \Drupal::root();
     foreach ($libraries_before as $extension => $libraries) {
         foreach ($libraries as $library_name => $library) {
             // Allow skipping libraries.
             if (in_array("{$extension}/{$library_name}", $this->librariesToSkip)) {
                 continue;
             }
             $library_after = $libraries_after[$extension][$library_name];
             // Check that all the CSS assets are overridden.
             foreach ($library['css'] as $index => $asset) {
                 $clean_path = $asset['data'];
                 $stable_path = $library_after['css'][$index]['data'];
                 // Make core/misc assets look like they are coming from a "core"
                 // module.
                 $replacements = ['core/misc/' => "core/modules/core/css/"];
                 $expected_path = strtr($clean_path, $replacements);
                 // Adjust the module asset paths to correspond with the Stable folder
                 // structure.
                 $expected_path = str_replace("core/modules/{$extension}/css/", "core/themes/stable/css/{$extension}/", $expected_path);
                 $assert_path = str_replace("core/modules/{$extension}/", '', $clean_path);
                 $this->assertEqual($expected_path, $stable_path, "{$assert_path} from the {$extension}/{$library_name} library is overridden in Stable.");
             }
         }
     }
 }
Пример #4
0
 /**
  * {@inheritdoc}
  */
 public function collect(Request $request, Response $response, \Exception $exception = NULL)
 {
     $activeTheme = $this->themeManager->getActiveTheme();
     $this->data['activeTheme'] = ['name' => $activeTheme->getName(), 'path' => $activeTheme->getPath(), 'engine' => $activeTheme->getEngine(), 'owner' => $activeTheme->getOwner(), 'baseThemes' => $activeTheme->getBaseThemes(), 'extension' => $activeTheme->getExtension(), 'styleSheetsRemove' => $activeTheme->getStyleSheetsRemove(), 'libraries' => $activeTheme->getLibraries(), 'regions' => $activeTheme->getRegions()];
     if ($this->themeNegotiator instanceof ThemeNegotiatorWrapper) {
         $this->data['negotiator'] = ['class' => $this->getMethodData($this->themeNegotiator->getNegotiator(), 'determineActiveTheme'), 'id' => $this->themeNegotiator->getNegotiator()->_serviceId];
     }
 }
Пример #5
0
 /**
  * Tests opting out of Stable by setting the base theme to false.
  */
 public function testWildWest()
 {
     $this->themeHandler->install(['test_wild_west']);
     $this->config('system.theme')->set('default', 'test_wild_west')->save();
     $theme = $this->themeManager->getActiveTheme();
     /** @var \Drupal\Core\Theme\ActiveTheme $base_theme */
     $base_themes = $theme->getBaseThemes();
     $this->assertTrue(empty($base_themes), 'No base theme is set when a theme has opted out of using Stable.');
 }
Пример #6
0
 /**
  * {@inheritdoc}
  */
 protected function setUp()
 {
     parent::setUp();
     $this->moduleHandler = $this->getMock('Drupal\\Core\\Extension\\ModuleHandlerInterface');
     $this->themeManager = $this->getMock('Drupal\\Core\\Theme\\ThemeManagerInterface');
     $mock_active_theme = $this->getMockBuilder('Drupal\\Core\\Theme\\ActiveTheme')->disableOriginalConstructor()->getMock();
     $mock_active_theme->expects($this->any())->method('getLibrariesOverride')->willReturn([]);
     $this->themeManager->expects($this->any())->method('getActiveTheme')->willReturn($mock_active_theme);
     $this->libraryDiscoveryParser = new TestLibraryDiscoveryParser($this->root, $this->moduleHandler, $this->themeManager);
 }
Пример #7
0
 /**
  * Tests that changes to the info file are picked up.
  */
 public function testChanges()
 {
     $this->themeHandler->install(array('test_theme'));
     $this->themeHandler->setDefault('test_theme');
     $this->themeManager->resetActiveTheme();
     $active_theme = $this->themeManager->getActiveTheme();
     // Make sure we are not testing the wrong theme.
     $this->assertEqual('test_theme', $active_theme->getName());
     $this->assertEqual(['classy/base', 'core/normalize', 'test_theme/global-styling'], $active_theme->getLibraries());
     // @see theme_test_system_info_alter()
     $this->state->set('theme_test.modify_info_files', TRUE);
     drupal_flush_all_caches();
     $active_theme = $this->themeManager->getActiveTheme();
     $this->assertEqual(['classy/base', 'core/normalize', 'test_theme/global-styling', 'core/backbone'], $active_theme->getLibraries());
 }
Пример #8
0
 /**
  * Builds up all element information.
  *
  * @param string $theme_name
  *   The theme name.
  *
  * @return array
  */
 protected function buildInfo($theme_name)
 {
     // Get cached definitions.
     $cid = $this->getCid($theme_name);
     if ($cache = $this->cacheBackend->get($cid)) {
         return $cache->data;
     }
     // Otherwise, rebuild and cache.
     $info = [];
     foreach ($this->getDefinitions() as $element_type => $definition) {
         $element = $this->createInstance($element_type);
         $element_info = $element->getInfo();
         // If this is element is to be used exclusively in a form, denote that it
         // will receive input, and assign the value callback.
         if ($element instanceof FormElementInterface) {
             $element_info['#input'] = TRUE;
             $element_info['#value_callback'] = array($definition['class'], 'valueCallback');
         }
         $info[$element_type] = $element_info;
     }
     foreach ($info as $element_type => $element) {
         $info[$element_type]['#type'] = $element_type;
     }
     // Allow modules to alter the element type defaults.
     $this->moduleHandler->alter('element_info', $info);
     $this->themeManager->alter('element_info', $info);
     $this->cacheBackend->set($cid, $info, Cache::PERMANENT, ['element_info_build']);
     return $info;
 }
Пример #9
0
 /**
  * {@inheritdoc}
  */
 public function evaluate()
 {
     if (!$this->configuration['theme']) {
         return TRUE;
     }
     return $this->themeManager->getActiveTheme()->getName() == $this->configuration['theme'];
 }
 /**
  * Tests the destruct method.
  *
  * @covers ::destruct
  */
 public function testDestruct()
 {
     $this->activeTheme = $this->getMockBuilder('Drupal\\Core\\Theme\\ActiveTheme')->disableOriginalConstructor()->getMock();
     $this->themeManager->expects($this->once())->method('getActiveTheme')->willReturn($this->activeTheme);
     $this->activeTheme->expects($this->once())->method('getName')->willReturn('kitten_theme');
     $this->libraryDiscoveryCollector = new LibraryDiscoveryCollector($this->cache, $this->lock, $this->libraryDiscoveryParser, $this->themeManager);
     $this->libraryDiscoveryParser->expects($this->once())->method('buildByExtension')->with('test')->willReturn($this->libraryData);
     $lock_key = 'library_info:kitten_theme:Drupal\\Core\\Cache\\CacheCollector';
     $this->lock->expects($this->once())->method('acquire')->with($lock_key)->will($this->returnValue(TRUE));
     $this->cache->expects($this->exactly(2))->method('get')->with('library_info:kitten_theme')->willReturn(FALSE);
     $this->cache->expects($this->once())->method('set')->with('library_info:kitten_theme', array('test' => $this->libraryData), Cache::PERMANENT, ['library_info']);
     $this->lock->expects($this->once())->method('release')->with($lock_key);
     // This should get data and persist the key.
     $this->libraryDiscoveryCollector->get('test');
     $this->libraryDiscoveryCollector->destruct();
 }
Пример #11
0
 /**
  * @covers ::getInfoProperty
  */
 public function testGetInfoProperty()
 {
     $this->themeManager->method('getActiveTheme')->willReturn(new ActiveTheme(['name' => 'test']));
     $element_info = new TestElementInfoManager(new \ArrayObject(), $this->cache, $this->cacheTagsInvalidator, $this->moduleHandler, $this->themeManager);
     $this->assertSame('baz', $element_info->getInfoProperty('foo', '#bar'));
     $this->assertNull($element_info->getInfoProperty('foo', '#non_existing_property'));
     $this->assertSame('qux', $element_info->getInfoProperty('foo', '#non_existing_property', 'qux'));
 }
 /**
  * Gets the name of the theme used for this block listing.
  *
  * @return string
  *   The name of the theme.
  */
 protected function getThemeName()
 {
     // If no theme was specified, use the current theme.
     if (!$this->theme) {
         $this->theme = $this->themeManager->getActiveTheme()->getName();
     }
     return $this->theme;
 }
Пример #13
0
 /**
  * Page callback: Tests the theme negotiation functionality.
  *
  * @param bool $inherited
  *   TRUE when the requested page is intended to inherit
  *   the theme of its parent.
  *
  * @return string
  *   A string describing the requested custom theme and actual
  *   theme being used
  *   for the current page request.
  */
 public function themePage($inherited)
 {
     $theme_key = $this->themeManager->getActiveTheme()->getName();
     // Now we check what the theme negotiator service returns.
     $active_theme = $this->themeNegotiator->determineActiveTheme($this->routeMatch);
     $output = "Active theme: {$active_theme}. Actual theme: {$theme_key}.";
     if ($inherited) {
         $output .= ' Theme negotiation inheritance is being tested.';
     }
     return ['#markup' => $output];
 }
 /**
  * Tests the getInfo() method when render element plugins are used.
  *
  * @covers ::getInfo
  * @covers ::buildInfo
  *
  * @dataProvider providerTestGetInfoElementPlugin
  */
 public function testGetInfoElementPlugin($plugin_class, $expected_info)
 {
     $this->moduleHandler->expects($this->once())->method('invokeAll')->with('element_info')->willReturn(array());
     $this->moduleHandler->expects($this->once())->method('alter')->with('element_info', $this->anything())->will($this->returnArgument(0));
     $plugin = $this->getMock($plugin_class);
     $plugin->expects($this->once())->method('getInfo')->willReturn(array('#theme' => 'page'));
     $element_info = $this->getMockBuilder('Drupal\\Core\\Render\\ElementInfoManager')->setConstructorArgs(array(new \ArrayObject(), $this->cache, $this->cacheTagsInvalidator, $this->moduleHandler, $this->themeManager))->setMethods(array('getDefinitions', 'createInstance'))->getMock();
     $this->themeManager->expects($this->any())->method('getActiveTheme')->willReturn(new ActiveTheme(['name' => 'test']));
     $element_info->expects($this->once())->method('createInstance')->with('page')->willReturn($plugin);
     $element_info->expects($this->once())->method('getDefinitions')->willReturn(array('page' => array('class' => 'TestElementPlugin')));
     $this->assertEquals($expected_info, $element_info->getInfo('page'));
 }
Пример #15
0
 /**
  * {@inheritdoc}
  */
 public function build()
 {
     $build = parent::build();
     $active_theme = $this->themeManager->getActiveTheme();
     $theme_name = $active_theme->getName();
     $destination = $this->redirectDestination->get();
     $visible_regions = $this->getVisibleRegionNames($theme_name);
     // Build an array of the region names in the right order.
     $build += array_fill_keys(array_keys($visible_regions), []);
     foreach ($visible_regions as $region => $region_name) {
         $query = ['region' => $region];
         if ($destination) {
             $query['destination'] = $destination;
         }
         $title = $this->t('<span class="visually-hidden">Place block in the %region region</span>', ['%region' => $region_name]);
         $operations['block_description'] = ['#type' => 'inline_template', '#template' => '<div class="block-place-region">{{ link }}</div>', '#context' => ['link' => Link::createFromRoute($title, 'block.admin_library', ['theme' => $theme_name], ['query' => $query, 'attributes' => ['title' => $title, 'class' => ['use-ajax', 'button', 'button--small'], 'data-dialog-type' => 'modal', 'data-dialog-options' => Json::encode(['width' => 700])]])]];
         $build[$region] = ['block_place_operations' => $operations] + $build[$region];
     }
     $build['#attached']['library'][] = 'block_place/drupal.block_place';
     return $build;
 }
Пример #16
0
 /**
  * {@inheritdoc}
  */
 protected function setUp()
 {
     parent::setUp();
     $this->libraryDiscovery = $this->getMockBuilder('Drupal\\Core\\Asset\\LibraryDiscovery')->disableOriginalConstructor()->getMock();
     $this->libraryDependencyResolver = $this->getMock('\\Drupal\\Core\\Asset\\LibraryDependencyResolverInterface');
     $this->libraryDependencyResolver->expects($this->any())->method('getLibrariesWithDependencies')->willReturnArgument(0);
     $this->moduleHandler = $this->getMock('\\Drupal\\Core\\Extension\\ModuleHandlerInterface');
     $this->themeManager = $this->getMock('\\Drupal\\Core\\Theme\\ThemeManagerInterface');
     $active_theme = $this->getMockBuilder('\\Drupal\\Core\\Theme\\ActiveTheme')->disableOriginalConstructor()->getMock();
     $active_theme->expects($this->any())->method('getName')->willReturn('bartik');
     $this->themeManager->expects($this->any())->method('getActiveTheme')->willReturn($active_theme);
     $this->languageManager = $this->getMock('\\Drupal\\Core\\Language\\LanguageManagerInterface');
     $english = $this->getMock('\\Drupal\\Core\\Language\\LanguageInterface');
     $english->expects($this->any())->method('getId')->willReturn('en');
     $japanese = $this->getMock('\\Drupal\\Core\\Language\\LanguageInterface');
     $japanese->expects($this->any())->method('getId')->willReturn('jp');
     $this->languageManager = $this->getMock('\\Drupal\\Core\\Language\\LanguageManagerInterface');
     $this->languageManager->expects($this->any())->method('getCurrentLanguage')->will($this->onConsecutiveCalls($english, $english, $japanese, $japanese));
     $this->cache = new TestMemoryBackend('llama');
     $this->assetResolver = new AssetResolver($this->libraryDiscovery, $this->libraryDependencyResolver, $this->moduleHandler, $this->themeManager, $this->languageManager, $this->cache);
 }
 /**
  * Ensures that all core module and theme library files exist.
  */
 public function testCoreLibraryCompleteness()
 {
     // First verify all libraries with no active theme.
     $this->verifyLibraryFilesExist($this->getAllLibraries());
     // Then verify all libraries for each core theme. This may seem like
     // overkill but themes can override and extend other extensions' libraries
     // and these changes are only applied for the active theme.
     foreach ($this->allThemes as $theme) {
         $this->themeManager->setActiveTheme($this->themeInitialization->getActiveThemeByName($theme));
         $this->libraryDiscovery->clearCachedDefinitions();
         $this->verifyLibraryFilesExist($this->getAllLibraries());
     }
 }
Пример #18
0
 /**
  * Tests getting the theme registry defined by a module.
  */
 public function testGetRegistryForModule()
 {
     $test_theme = new ActiveTheme(['name' => 'test_theme', 'path' => 'core/modules/system/tests/themes/test_theme/test_theme.info.yml', 'engine' => 'twig', 'owner' => 'twig', 'stylesheets_remove' => [], 'libraries_override' => [], 'libraries_extend' => [], 'libraries' => [], 'extension' => '.twig', 'base_themes' => []]);
     $test_stable = new ActiveTheme(['name' => 'test_stable', 'path' => 'core/modules/system/tests/themes/test_stable/test_stable.info.yml', 'engine' => 'twig', 'owner' => 'twig', 'stylesheets_remove' => [], 'libraries_override' => [], 'libraries_extend' => [], 'libraries' => [], 'extension' => '.twig', 'base_themes' => []]);
     $this->themeManager->expects($this->exactly(2))->method('getActiveTheme')->willReturnOnConsecutiveCalls($test_theme, $test_stable);
     // Include the module and theme files so that hook_theme can be called.
     include_once $this->root . '/core/modules/system/tests/modules/theme_test/theme_test.module';
     include_once $this->root . '/core/modules/system/tests/themes/test_stable/test_stable.theme';
     $this->moduleHandler->expects($this->exactly(2))->method('getImplementations')->with('theme')->will($this->returnValue(array('theme_test')));
     $this->moduleHandler->expects($this->atLeastOnce())->method('getModuleList')->willReturn([]);
     $registry = $this->registry->get();
     // Ensure that the registry entries from the module are found.
     $this->assertArrayHasKey('theme_test', $registry);
     $this->assertArrayHasKey('theme_test_template_test', $registry);
     $this->assertArrayHasKey('theme_test_template_test_2', $registry);
     $this->assertArrayHasKey('theme_test_suggestion_provided', $registry);
     $this->assertArrayHasKey('theme_test_specific_suggestions', $registry);
     $this->assertArrayHasKey('theme_test_suggestions', $registry);
     $this->assertArrayHasKey('theme_test_function_suggestions', $registry);
     $this->assertArrayHasKey('theme_test_foo', $registry);
     $this->assertArrayHasKey('theme_test_render_element', $registry);
     $this->assertArrayHasKey('theme_test_render_element_children', $registry);
     $this->assertArrayHasKey('theme_test_function_template_override', $registry);
     $this->assertArrayNotHasKey('test_theme_not_existing_function', $registry);
     $this->assertFalse(in_array('test_stable_preprocess_theme_test_render_element', $registry['theme_test_render_element']['preprocess functions']));
     $info = $registry['theme_test_function_suggestions'];
     $this->assertEquals('module', $info['type']);
     $this->assertEquals('core/modules/system/tests/modules/theme_test', $info['theme path']);
     $this->assertEquals('theme_theme_test_function_suggestions', $info['function']);
     $this->assertEquals(array(), $info['variables']);
     // The second call will initialize with the second theme. Ensure that this
     // returns a different object and the discovery for the second theme's
     // preprocess function worked.
     $other_registry = $this->registry->get();
     $this->assertNotSame($registry, $other_registry);
     $this->assertTrue(in_array('test_stable_preprocess_theme_test_render_element', $other_registry['theme_test_render_element']['preprocess functions']));
 }
Пример #19
0
 /**
  * Apply libraries overrides specified for the current active theme.
  *
  * @param array $libraries
  *   The libraries definitions.
  * @param string $extension
  *   The extension in which these libraries are defined.
  *
  * @return array
  *   The modified libraries definitions.
  */
 protected function applyLibrariesOverride($libraries, $extension)
 {
     $active_theme = $this->themeManager->getActiveTheme();
     // ActiveTheme::getLibrariesOverride() returns libraries-overrides for the
     // current theme as well as all its base themes.
     $all_libraries_overrides = $active_theme->getLibrariesOverride();
     foreach ($all_libraries_overrides as $theme_path => $libraries_overrides) {
         foreach ($libraries as $library_name => $library) {
             // Process libraries overrides.
             if (isset($libraries_overrides["{$extension}/{$library_name}"])) {
                 // Active theme defines an override for this library.
                 $override_definition = $libraries_overrides["{$extension}/{$library_name}"];
                 if (is_string($override_definition) || $override_definition === FALSE) {
                     // A string or boolean definition implies an override (or removal)
                     // for the whole library. Use the override key to specify that this
                     // library will be overridden when it is called.
                     // @see \Drupal\Core\Asset\LibraryDiscovery::getLibraryByName()
                     if ($override_definition) {
                         $libraries[$library_name]['override'] = $override_definition;
                     } else {
                         $libraries[$library_name]['override'] = FALSE;
                     }
                 } elseif (is_array($override_definition)) {
                     // An array definition implies an override for an asset within this
                     // library.
                     foreach ($override_definition as $sub_key => $value) {
                         // Throw an exception if the asset is not properly specified.
                         if (!is_array($value)) {
                             throw new InvalidLibrariesOverrideSpecificationException(sprintf('Library asset %s is not correctly specified. It should be in the form "extension/library_name/sub_key/path/to/asset.js".', "{$extension}/{$library_name}/{$sub_key}"));
                         }
                         if ($sub_key === 'drupalSettings') {
                             // drupalSettings may not be overridden.
                             throw new InvalidLibrariesOverrideSpecificationException(sprintf('drupalSettings may not be overridden in libraries-override. Trying to override %s. Use hook_library_info_alter() instead.', "{$extension}/{$library_name}/{$sub_key}"));
                         } elseif ($sub_key === 'css') {
                             // SMACSS category should be incorporated into the asset name.
                             foreach ($value as $category => $overrides) {
                                 $this->setOverrideValue($libraries[$library_name], [$sub_key, $category], $overrides, $theme_path);
                             }
                         } else {
                             $this->setOverrideValue($libraries[$library_name], [$sub_key], $value, $theme_path);
                         }
                     }
                 }
             }
         }
     }
     return $libraries;
 }
 /**
  * Applies the libraries-extend specified by the active theme.
  *
  * This extends the library definitions with the those specified by the
  * libraries-extend specifications for the active theme.
  *
  * @param string $extension
  *   The name of the extension for which library definitions will be extended.
  * @param string $library_name
  *   The name of the library whose definitions is to be extended.
  * @param $library_definition
  *   The library definition to be extended.
  *
  * @return array
  *   The library definition extended as specified by libraries-extend.
  *
  * @throws \Drupal\Core\Asset\Exception\InvalidLibrariesExtendSpecificationException
  */
 protected function applyLibrariesExtend($extension, $library_name, $library_definition)
 {
     $libraries_extend = $this->themeManager->getActiveTheme()->getLibrariesExtend();
     if (!empty($libraries_extend["{$extension}/{$library_name}"])) {
         foreach ($libraries_extend["{$extension}/{$library_name}"] as $library_extend_name) {
             if (!is_string($library_extend_name)) {
                 // Only string library names are allowed.
                 throw new InvalidLibrariesExtendSpecificationException('The libraries-extend specification for each library must be a list of strings.');
             }
             list($new_extension, $new_library_name) = explode('/', $library_extend_name, 2);
             $new_libraries = $this->get($new_extension);
             if (isset($new_libraries[$new_library_name])) {
                 $library_definition = NestedArray::mergeDeep($library_definition, $new_libraries[$new_library_name]);
             } else {
                 throw new InvalidLibrariesExtendSpecificationException(sprintf('The specified library "%s" does not exist.', $library_extend_name));
             }
         }
     }
     return $library_definition;
 }
Пример #21
0
 /**
  * Parses a given library file and allows module to alter it.
  *
  * This method sets the parsed information onto the library property.
  *
  * Library information is parsed from *.libraries.yml files; see
  * editor.library.yml for an example. Every library must have at least one js
  * or css entry. Each entry starts with a machine name and defines the
  * following elements:
  * - js: A list of JavaScript files to include. Each file is keyed by the file
  *   path. An item can have several attributes (like HTML
  *   attributes). For example:
  *   @code
  *   js:
  *     path/js/file.js: { attributes: { defer: true } }
  *   @endcode
  *   If the file has no special attributes, just use an empty object:
  *   @code
  *   js:
  *     path/js/file.js: {}
  *   @endcode
  *   The path of the file is relative to the module or theme directory, unless
  *   it starts with a /, in which case it is relative to the Drupal root. If
  *   the file path starts with //, it will be treated as a protocol-free,
  *   external resource (e.g., //cdn.com/library.js). Full URLs
  *   (e.g., http://cdn.com/library.js) as well as URLs that use a valid
  *   stream wrapper (e.g., public://path/to/file.js) are also supported.
  * - css: A list of categories for which the library provides CSS files. The
  *   available categories are:
  *   - base
  *   - layout
  *   - component
  *   - state
  *   - theme
  *   Each category is itself a key for a sub-list of CSS files to include:
  *   @code
  *   css:
  *     component:
  *       css/file.css: {}
  *   @endcode
  *   Just like with JavaScript files, each CSS file is the key of an object
  *   that can define specific attributes. The format of the file path is the
  *   same as for the JavaScript files.
  * - dependencies: A list of libraries this library depends on.
  * - version: The library version. The string "VERSION" can be used to mean
  *   the current Drupal core version.
  * - header: By default, JavaScript files are included in the footer. If the
  *   script must be included in the header (along with all its dependencies),
  *   set this to true. Defaults to false.
  * - minified: If the file is already minified, set this to true to avoid
  *   minifying it again. Defaults to false.
  * - remote: If the library is a third-party script, this provides the
  *   repository URL for reference.
  * - license: If the remote property is set, the license information is
  *   required. It has 3 properties:
  *   - name: The human-readable name of the license.
  *   - url: The URL of the license file/information for the version of the
  *     library used.
  *   - gpl-compatible: A Boolean for whether this library is GPL compatible.
  *
  * See https://www.drupal.org/node/2274843#define-library for more
  * information.
  *
  * @param string $extension
  *   The name of the extension that registered a library.
  * @param string $path
  *   The relative path to the extension.
  *
  * @return array
  *   An array of parsed library data.
  *
  * @throws \Drupal\Core\Asset\Exception\InvalidLibraryFileException
  *   Thrown when a parser exception got thrown.
  */
 protected function parseLibraryInfo($extension, $path)
 {
     $libraries = [];
     $library_file = $path . '/' . $extension . '.libraries.yml';
     if (file_exists($this->root . '/' . $library_file)) {
         try {
             $libraries = Yaml::decode(file_get_contents($this->root . '/' . $library_file));
         } catch (InvalidDataTypeException $e) {
             // Rethrow a more helpful exception to provide context.
             throw new InvalidLibraryFileException(sprintf('Invalid library definition in %s: %s', $library_file, $e->getMessage()), 0, $e);
         }
     }
     // Allow modules to add dynamic library definitions.
     $hook = 'library_info_build';
     if ($this->moduleHandler->implementsHook($extension, $hook)) {
         $libraries = NestedArray::mergeDeep($libraries, $this->moduleHandler->invoke($extension, $hook));
     }
     // Allow modules to alter the module's registered libraries.
     $this->moduleHandler->alter('library_info', $libraries, $extension);
     $this->themeManager->alter('library_info', $libraries, $extension);
     return $libraries;
 }
 /**
  * Retrieves the key of the theme used to render the emails.
  */
 public function getMailTheme()
 {
     $theme = $this->mailsystemConfig->get('theme');
     switch ($theme) {
         case 'default':
             $theme = $this->configFactory->get('system.theme')->get('default');
             break;
         case 'current':
             $theme = $this->themeManager->getActiveTheme()->getName();
             break;
         case 'domain':
             // Fetch the theme for the current domain.
             // @todo: Reimplement this as soon as module port or similar module is around.
             if (FALSE && \Drupal::moduleHandler()->moduleExists('domain_theme')) {
                 // Assign the selected theme, based on the active domain.
                 global $_domain;
                 $domain_theme = domain_theme_lookup($_domain['domain_id']);
                 // The above returns -1 on failure.
                 $theme = $domain_theme != -1 ? $domain_theme['theme'] : $this->themeManager->getActiveTheme()->getName();
             }
             break;
     }
     return $theme;
 }
Пример #23
0
 /**
  * {@inheritdoc}
  */
 public function prepareForm($form_id, &$form, FormStateInterface &$form_state)
 {
     $user = $this->currentUser();
     $form['#type'] = 'form';
     // Only update the action if it is not already set.
     if (!isset($form['#action'])) {
         // Instead of setting an actual action URL, we set the placeholder, which
         // will be replaced at the very last moment. This ensures forms with
         // dynamically generated action URLs don't have poor cacheability.
         // Use the proper API to generate the placeholder, when we have one. See
         // https://www.drupal.org/node/2562341.
         $placeholder = 'form_action_' . hash('crc32b', __METHOD__);
         $form['#attached']['placeholders'][$placeholder] = ['#lazy_builder' => ['form_builder:renderPlaceholderFormAction', []]];
         $form['#action'] = $placeholder;
     }
     // Fix the form method, if it is 'get' in $form_state, but not in $form.
     if ($form_state->isMethodType('get') && !isset($form['#method'])) {
         $form['#method'] = 'get';
     }
     // GET forms should not use a CSRF token.
     if (isset($form['#method']) && $form['#method'] === 'get') {
         // Merges in a default, this means if you've explicitly set #token to the
         // the $form_id on a GET form, which we don't recommend, it will work.
         $form += ['#token' => FALSE];
     }
     // Generate a new #build_id for this form, if none has been set already.
     // The form_build_id is used as key to cache a particular build of the form.
     // For multi-step forms, this allows the user to go back to an earlier
     // build, make changes, and re-submit.
     // @see self::buildForm()
     // @see self::rebuildForm()
     if (!isset($form['#build_id'])) {
         $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
     }
     $form['form_build_id'] = array('#type' => 'hidden', '#value' => $form['#build_id'], '#id' => $form['#build_id'], '#name' => 'form_build_id', '#parents' => array('form_build_id'));
     // Add a token, based on either #token or form_id, to any form displayed to
     // authenticated users. This ensures that any submitted form was actually
     // requested previously by the user and protects against cross site request
     // forgeries.
     // This does not apply to programmatically submitted forms. Furthermore,
     // since tokens are session-bound and forms displayed to anonymous users are
     // very likely cached, we cannot assign a token for them.
     // During installation, there is no $user yet.
     // Form constructors may explicitly set #token to FALSE when cross site
     // request forgery is irrelevant to the form, such as search forms.
     if ($form_state->isProgrammed() || isset($form['#token']) && $form['#token'] === FALSE) {
         unset($form['#token']);
     } else {
         $form['#cache']['contexts'][] = 'user.roles:authenticated';
         if ($user && $user->isAuthenticated()) {
             // Generate a public token based on the form id.
             $form['#token'] = $form_id;
             $form['form_token'] = array('#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'), '#type' => 'token', '#default_value' => $this->csrfToken->get($form['#token']), '#parents' => array('form_token'), '#cache' => ['max-age' => 0]);
         }
     }
     if (isset($form_id)) {
         $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => Html::getUniqueId("edit-{$form_id}"), '#parents' => array('form_id'));
     }
     if (!isset($form['#id'])) {
         $form['#id'] = Html::getUniqueId($form_id);
         // Provide a selector usable by JavaScript. As the ID is unique, its not
         // possible to rely on it in JavaScript.
         $form['#attributes']['data-drupal-selector'] = Html::getId($form_id);
     }
     $form += $this->elementInfo->getInfo('form');
     $form += array('#tree' => FALSE, '#parents' => array());
     $form['#validate'][] = '::validateForm';
     $form['#submit'][] = '::submitForm';
     $build_info = $form_state->getBuildInfo();
     // If no #theme has been set, automatically apply theme suggestions.
     // The form theme hook itself, which is rendered by form.html.twig,
     // is in #theme_wrappers. Therefore, the #theme function only has to care
     // for rendering the inner form elements, not the form itself.
     if (!isset($form['#theme'])) {
         $form['#theme'] = array($form_id);
         if (isset($build_info['base_form_id'])) {
             $form['#theme'][] = $build_info['base_form_id'];
         }
     }
     // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and
     // hook_form_FORM_ID_alter() implementations.
     $hooks = array('form');
     if (isset($build_info['base_form_id'])) {
         $hooks[] = 'form_' . $build_info['base_form_id'];
     }
     $hooks[] = 'form_' . $form_id;
     $this->moduleHandler->alter($hooks, $form, $form_state, $form_id);
     $this->themeManager->alter($hooks, $form, $form_state, $form_id);
 }
Пример #24
0
 /**
  * Gets the current theme for this page.
  *
  * @return string
  *   The current theme.
  */
 protected function getTheme()
 {
     return $this->themeManager->getActiveTheme()->getName();
 }
Пример #25
0
 /**
  * {@inheritdoc}
  */
 public function getContext()
 {
     return $this->themeManager->getActiveTheme()->getName() ?: 'stark';
 }
Пример #26
0
 /**
  * See the docs for ::render().
  */
 protected function doRender(&$elements, $is_root_call = FALSE)
 {
     if (!isset($elements['#access']) && isset($elements['#access_callback'])) {
         if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) {
             $elements['#access_callback'] = $this->controllerResolver->getControllerFromDefinition($elements['#access_callback']);
         }
         $elements['#access'] = call_user_func($elements['#access_callback'], $elements);
     }
     // Early-return nothing if user does not have access.
     if (empty($elements) || isset($elements['#access']) && !$elements['#access']) {
         return '';
     }
     // Do not print elements twice.
     if (!empty($elements['#printed'])) {
         return '';
     }
     if (!isset(static::$stack)) {
         static::$stack = new \SplStack();
     }
     static::$stack->push(new BubbleableMetadata());
     // Set the bubbleable rendering metadata that has configurable defaults, if:
     // - this is the root call, to ensure that the final render array definitely
     //   has these configurable defaults, even when no subtree is render cached.
     // - this is a render cacheable subtree, to ensure that the cached data has
     //   the configurable defaults (which may affect the ID and invalidation).
     if ($is_root_call || isset($elements['#cache']['keys'])) {
         $required_cache_contexts = $this->rendererConfig['required_cache_contexts'];
         if (isset($elements['#cache']['contexts'])) {
             $elements['#cache']['contexts'] = Cache::mergeContexts($elements['#cache']['contexts'], $required_cache_contexts);
         } else {
             $elements['#cache']['contexts'] = $required_cache_contexts;
         }
     }
     // Try to fetch the prerendered element from cache, run any
     // #post_render_cache callbacks and return the final markup.
     if (isset($elements['#cache']['keys'])) {
         $cached_element = $this->renderCache->get($elements);
         if ($cached_element !== FALSE) {
             $elements = $cached_element;
             // Only when we're not in a root (non-recursive) drupal_render() call,
             // #post_render_cache callbacks must be executed, to prevent breaking
             // the render cache in case of nested elements with #cache set.
             if ($is_root_call) {
                 $this->processPostRenderCache($elements);
             }
             // Mark the element markup as safe. If we have cached children, we need
             // to mark them as safe too. The parent markup contains the child
             // markup, so if the parent markup is safe, then the markup of the
             // individual children must be safe as well.
             $elements['#markup'] = SafeMarkup::set($elements['#markup']);
             if (!empty($elements['#cache_properties'])) {
                 foreach (Element::children($cached_element) as $key) {
                     SafeMarkup::set($cached_element[$key]['#markup']);
                 }
             }
             // The render cache item contains all the bubbleable rendering metadata
             // for the subtree.
             $this->updateStack($elements);
             // Render cache hit, so rendering is finished, all necessary info
             // collected!
             $this->bubbleStack();
             return $elements['#markup'];
         }
     }
     // Two-tier caching: track pre-bubbling elements' #cache for later
     // comparison.
     // @see \Drupal\Core\Render\RenderCacheInterface::get()
     // @see \Drupal\Core\Render\RenderCacheInterface::set()
     $pre_bubbling_elements = [];
     $pre_bubbling_elements['#cache'] = isset($elements['#cache']) ? $elements['#cache'] : [];
     // If the default values for this element have not been loaded yet, populate
     // them.
     if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
         $elements += $this->elementInfo->getInfo($elements['#type']);
     }
     // Make any final changes to the element before it is rendered. This means
     // that the $element or the children can be altered or corrected before the
     // element is rendered into the final text.
     if (isset($elements['#pre_render'])) {
         foreach ($elements['#pre_render'] as $callable) {
             if (is_string($callable) && strpos($callable, '::') === FALSE) {
                 $callable = $this->controllerResolver->getControllerFromDefinition($callable);
             }
             $elements = call_user_func($callable, $elements);
         }
     }
     // Defaults for bubbleable rendering metadata.
     $elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
     $elements['#cache']['max-age'] = isset($elements['#cache']['max-age']) ? $elements['#cache']['max-age'] : Cache::PERMANENT;
     $elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array();
     $elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array();
     // Allow #pre_render to abort rendering.
     if (!empty($elements['#printed'])) {
         // The #printed element contains all the bubbleable rendering metadata for
         // the subtree.
         $this->updateStack($elements);
         // #printed, so rendering is finished, all necessary info collected!
         $this->bubbleStack();
         return '';
     }
     // Add any JavaScript state information associated with the element.
     if (!empty($elements['#states'])) {
         drupal_process_states($elements);
     }
     // Get the children of the element, sorted by weight.
     $children = Element::children($elements, TRUE);
     // Initialize this element's #children, unless a #pre_render callback
     // already preset #children.
     if (!isset($elements['#children'])) {
         $elements['#children'] = '';
     }
     // @todo Simplify after https://www.drupal.org/node/2273925.
     if (isset($elements['#markup'])) {
         $elements['#markup'] = SafeMarkup::set($elements['#markup']);
     }
     // Assume that if #theme is set it represents an implemented hook.
     $theme_is_implemented = isset($elements['#theme']);
     // Check the elements for insecure HTML and pass through sanitization.
     if (isset($elements)) {
         $markup_keys = array('#description', '#field_prefix', '#field_suffix');
         foreach ($markup_keys as $key) {
             if (!empty($elements[$key]) && is_scalar($elements[$key])) {
                 $elements[$key] = SafeMarkup::checkAdminXss($elements[$key]);
             }
         }
     }
     // Call the element's #theme function if it is set. Then any children of the
     // element have to be rendered there. If the internal #render_children
     // property is set, do not call the #theme function to prevent infinite
     // recursion.
     if ($theme_is_implemented && !isset($elements['#render_children'])) {
         $elements['#children'] = $this->theme->render($elements['#theme'], $elements);
         // If ThemeManagerInterface::render() returns FALSE this means that the
         // hook in #theme was not found in the registry and so we need to update
         // our flag accordingly. This is common for theme suggestions.
         $theme_is_implemented = $elements['#children'] !== FALSE;
     }
     // If #theme is not implemented or #render_children is set and the element
     // has an empty #children attribute, render the children now. This is the
     // same process as Renderer::render() but is inlined for speed.
     if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
         foreach ($children as $key) {
             $elements['#children'] .= $this->doRender($elements[$key]);
         }
         $elements['#children'] = SafeMarkup::set($elements['#children']);
     }
     // If #theme is not implemented and the element has raw #markup as a
     // fallback, prepend the content in #markup to #children. In this case
     // #children will contain whatever is provided by #pre_render prepended to
     // what is rendered recursively above. If #theme is implemented then it is
     // the responsibility of that theme implementation to render #markup if
     // required. Eventually #theme_wrappers will expect both #markup and
     // #children to be a single string as #children.
     if (!$theme_is_implemented && isset($elements['#markup'])) {
         $elements['#children'] = SafeMarkup::set($elements['#markup'] . $elements['#children']);
     }
     // Let the theme functions in #theme_wrappers add markup around the rendered
     // children.
     // #states and #attached have to be processed before #theme_wrappers,
     // because the #type 'page' render array from drupal_prepare_page() would
     // render the $page and wrap it into the html.html.twig template without the
     // attached assets otherwise.
     // If the internal #render_children property is set, do not call the
     // #theme_wrappers function(s) to prevent infinite recursion.
     if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) {
         foreach ($elements['#theme_wrappers'] as $key => $value) {
             // If the value of a #theme_wrappers item is an array then the theme
             // hook is found in the key of the item and the value contains attribute
             // overrides. Attribute overrides replace key/value pairs in $elements
             // for only this ThemeManagerInterface::render() call. This allows
             // #theme hooks and #theme_wrappers hooks to share variable names
             // without conflict or ambiguity.
             $wrapper_elements = $elements;
             if (is_string($key)) {
                 $wrapper_hook = $key;
                 foreach ($value as $attribute => $override) {
                     $wrapper_elements[$attribute] = $override;
                 }
             } else {
                 $wrapper_hook = $value;
             }
             $elements['#children'] = $this->theme->render($wrapper_hook, $wrapper_elements);
         }
     }
     // Filter the outputted content and make any last changes before the content
     // is sent to the browser. The changes are made on $content which allows the
     // outputted text to be filtered.
     if (isset($elements['#post_render'])) {
         foreach ($elements['#post_render'] as $callable) {
             if (is_string($callable) && strpos($callable, '::') === FALSE) {
                 $callable = $this->controllerResolver->getControllerFromDefinition($callable);
             }
             $elements['#children'] = call_user_func($callable, $elements['#children'], $elements);
         }
     }
     // We store the resulting output in $elements['#markup'], to be consistent
     // with how render cached output gets stored. This ensures that
     // #post_render_cache callbacks get the same data to work with, no matter if
     // #cache is disabled, #cache is enabled, there is a cache hit or miss.
     $prefix = isset($elements['#prefix']) ? SafeMarkup::checkAdminXss($elements['#prefix']) : '';
     $suffix = isset($elements['#suffix']) ? SafeMarkup::checkAdminXss($elements['#suffix']) : '';
     $elements['#markup'] = $prefix . $elements['#children'] . $suffix;
     // We've rendered this element (and its subtree!), now update the stack.
     $this->updateStack($elements);
     // Cache the processed element if both $pre_bubbling_elements and $elements
     // have the metadata necessary to generate a cache ID.
     if (isset($pre_bubbling_elements['#cache']['keys']) && isset($elements['#cache']['keys'])) {
         if ($pre_bubbling_elements['#cache']['keys'] !== $elements['#cache']['keys']) {
             throw new \LogicException('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.');
         }
         $this->renderCache->set($elements, $pre_bubbling_elements);
     }
     // Only when we're in a root (non-recursive) drupal_render() call,
     // #post_render_cache callbacks must be executed, to prevent breaking the
     // render cache in case of nested elements with #cache set.
     //
     // By running them here, we ensure that:
     // - they run when #cache is disabled,
     // - they run when #cache is enabled and there is a cache miss.
     // Only the case of a cache hit when #cache is enabled, is not handled here,
     // that is handled earlier in Renderer::render().
     if ($is_root_call) {
         // We've already called ::updateStack() earlier, which updated both the
         // element and current stack frame. However,
         // Renderer::processPostRenderCache() can both change the element
         // further and create and render new child elements, so provide a fresh
         // stack frame to collect those additions, merge them back to the element,
         // and then update the current frame to match the modified element state.
         do {
             static::$stack->push(new BubbleableMetadata());
             $this->processPostRenderCache($elements);
             $post_render_additions = static::$stack->pop();
             $elements['#post_render_cache'] = NULL;
             BubbleableMetadata::createFromRenderArray($elements)->merge($post_render_additions)->applyTo($elements);
         } while (!empty($elements['#post_render_cache']));
         if (static::$stack->count() !== 1) {
             throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
         }
     }
     // Rendering is finished, all necessary info collected!
     $this->bubbleStack();
     $elements['#printed'] = TRUE;
     $elements['#markup'] = SafeMarkup::set($elements['#markup']);
     return $elements['#markup'];
 }
 /**
  * {@inheritdoc}
  */
 public function getJsAssets(AttachedAssetsInterface $assets, $optimize)
 {
     $theme_info = $this->themeManager->getActiveTheme();
     // Add the theme name to the cache key since themes may implement
     // hook_library_info_alter(). Additionally add the current language to
     // support translation of JavaScript files via hook_js_alter().
     $libraries_to_load = $this->getLibrariesToLoad($assets);
     $cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize;
     if ($cached = $this->cache->get($cid)) {
         list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data;
     } else {
         $javascript = [];
         $default_options = ['type' => 'file', 'group' => JS_DEFAULT, 'weight' => 0, 'cache' => TRUE, 'preprocess' => TRUE, 'attributes' => [], 'version' => NULL, 'browsers' => []];
         // Collect all libraries that contain JS assets and are in the header.
         $header_js_libraries = [];
         foreach ($libraries_to_load as $library) {
             list($extension, $name) = explode('/', $library, 2);
             $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
             if (isset($definition['js']) && !empty($definition['header'])) {
                 $header_js_libraries[] = $library;
             }
         }
         // The current list of header JS libraries are only those libraries that
         // are in the header, but their dependencies must also be loaded for them
         // to function correctly, so update the list with those.
         $header_js_libraries = $this->libraryDependencyResolver->getLibrariesWithDependencies($header_js_libraries);
         foreach ($libraries_to_load as $library) {
             list($extension, $name) = explode('/', $library, 2);
             $definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
             if (isset($definition['js'])) {
                 foreach ($definition['js'] as $options) {
                     $options += $default_options;
                     // 'scope' is a calculated option, based on which libraries are
                     // marked to be loaded from the header (see above).
                     $options['scope'] = in_array($library, $header_js_libraries) ? 'header' : 'footer';
                     // Preprocess can only be set if caching is enabled and no
                     // attributes are set.
                     $options['preprocess'] = $options['cache'] && empty($options['attributes']) ? $options['preprocess'] : FALSE;
                     // Always add a tiny value to the weight, to conserve the insertion
                     // order.
                     $options['weight'] += count($javascript) / 1000;
                     // Local and external files must keep their name as the associative
                     // key so the same JavaScript file is not added twice.
                     $javascript[$options['data']] = $options;
                 }
             }
         }
         // Allow modules and themes to alter the JavaScript assets.
         $this->moduleHandler->alter('js', $javascript, $assets);
         $this->themeManager->alter('js', $javascript, $assets);
         // Sort JavaScript assets, so that they appear in the correct order.
         uasort($javascript, 'static::sort');
         // Prepare the return value: filter JavaScript assets per scope.
         $js_assets_header = [];
         $js_assets_footer = [];
         foreach ($javascript as $key => $item) {
             if ($item['scope'] == 'header') {
                 $js_assets_header[$key] = $item;
             } elseif ($item['scope'] == 'footer') {
                 $js_assets_footer[$key] = $item;
             }
         }
         if ($optimize) {
             $collection_optimizer = \Drupal::service('asset.js.collection_optimizer');
             $js_assets_header = $collection_optimizer->optimize($js_assets_header);
             $js_assets_footer = $collection_optimizer->optimize($js_assets_footer);
         }
         // If the core/drupalSettings library is being loaded or is already
         // loaded, get the JavaScript settings assets, and convert them into a
         // single "regular" JavaScript asset.
         $libraries_to_load = $this->getLibrariesToLoad($assets);
         $settings_required = in_array('core/drupalSettings', $libraries_to_load) || in_array('core/drupalSettings', $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()));
         $settings_have_changed = count($libraries_to_load) > 0 || count($assets->getSettings()) > 0;
         // Initialize settings to FALSE since they are not needed by default. This
         // distinguishes between an empty array which must still allow
         // hook_js_settings_alter() to be run.
         $settings = FALSE;
         if ($settings_required && $settings_have_changed) {
             $settings = $this->getJsSettingsAssets($assets);
             // Allow modules to add cached JavaScript settings.
             foreach ($this->moduleHandler->getImplementations('js_settings_build') as $module) {
                 $function = $module . '_' . 'js_settings_build';
                 $function($settings, $assets);
             }
         }
         $settings_in_header = in_array('core/drupalSettings', $header_js_libraries);
         $this->cache->set($cid, [$js_assets_header, $js_assets_footer, $settings, $settings_in_header], CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
     }
     if ($settings !== FALSE) {
         // Attached settings override both library definitions and
         // hook_js_settings_build().
         $settings = NestedArray::mergeDeepArray([$settings, $assets->getSettings()], TRUE);
         // Allow modules and themes to alter the JavaScript settings.
         $this->moduleHandler->alter('js_settings', $settings, $assets);
         $this->themeManager->alter('js_settings', $settings, $assets);
         // Update the $assets object accordingly, so that it reflects the final
         // settings.
         $assets->setSettings($settings);
         $settings_as_inline_javascript = ['type' => 'setting', 'group' => JS_SETTING, 'weight' => 0, 'browsers' => [], 'data' => $settings];
         $settings_js_asset = ['drupalSettings' => $settings_as_inline_javascript];
         // Prepend to the list of JS assets, to render it first. Preferably in
         // the footer, but in the header if necessary.
         if ($settings_in_header) {
             $js_assets_header = $settings_js_asset + $js_assets_header;
         } else {
             $js_assets_footer = $settings_js_asset + $js_assets_footer;
         }
     }
     return [$js_assets_header, $js_assets_footer];
 }
Пример #28
0
 /**
  * Gets the path of the active theme.
  *
  * @return string
  *   The path to the active theme.
  */
 public function getActiveThemePath()
 {
     return $this->themeManager->getActiveTheme()->getPath();
 }
Пример #29
0
 /**
  * Builds the theme registry cache.
  *
  * Theme hook definitions are collected in the following order:
  * - Modules
  * - Base theme engines
  * - Base themes
  * - Theme engine
  * - Theme
  *
  * All theme hook definitions are essentially just collated and merged in the
  * above order. However, various extension-specific default values and
  * customizations are required; e.g., to record the effective file path for
  * theme template. Therefore, this method first collects all extensions per
  * type, and then dispatches the processing for each extension to
  * processExtension().
  *
  * After completing the collection, modules are allowed to alter it. Lastly,
  * any derived and incomplete theme hook definitions that are hook suggestions
  * for base hooks (e.g., 'block__node' for the base hook 'block') need to be
  * determined based on the full registry and classified as 'base hook'.
  *
  * See the @link themeable Default theme implementations topic @endlink for
  * details.
  *
  * @return \Drupal\Core\Utility\ThemeRegistry
  *   The build theme registry.
  *
  * @see hook_theme_registry_alter()
  */
 protected function build()
 {
     $cache = array();
     // First, preprocess the theme hooks advertised by modules. This will
     // serve as the basic registry. Since the list of enabled modules is the
     // same regardless of the theme used, this is cached in its own entry to
     // save building it for every theme.
     if ($cached = $this->cache->get('theme_registry:build:modules')) {
         $cache = $cached->data;
     } else {
         foreach ($this->moduleHandler->getImplementations('theme') as $module) {
             $this->processExtension($cache, $module, 'module', $module, $this->getPath($module));
         }
         // Only cache this registry if all modules are loaded.
         if ($this->moduleHandler->isLoaded()) {
             $this->cache->set("theme_registry:build:modules", $cache, Cache::PERMANENT, array('theme_registry'));
         }
     }
     // Process each base theme.
     // Ensure that we start with the root of the parents, so that both CSS files
     // and preprocess functions comes first.
     foreach (array_reverse($this->theme->getBaseThemes()) as $base) {
         // If the base theme uses a theme engine, process its hooks.
         $base_path = $base->getPath();
         if ($this->theme->getEngine()) {
             $this->processExtension($cache, $this->theme->getEngine(), 'base_theme_engine', $base->getName(), $base_path);
         }
         $this->processExtension($cache, $base->getName(), 'base_theme', $base->getName(), $base_path);
     }
     // And then the same thing, but for the theme.
     if ($this->theme->getEngine()) {
         $this->processExtension($cache, $this->theme->getEngine(), 'theme_engine', $this->theme->getName(), $this->theme->getPath());
     }
     // Hooks provided by the theme itself.
     $this->processExtension($cache, $this->theme->getName(), 'theme', $this->theme->getName(), $this->theme->getPath());
     // Discover and add all preprocess functions for theme hook suggestions.
     $this->postProcessExtension($cache, $this->theme);
     // Let modules and themes alter the registry.
     $this->moduleHandler->alter('theme_registry', $cache);
     $this->themeManager->alterForTheme($this->theme, 'theme_registry', $cache);
     // @todo Implement more reduction of the theme registry entry.
     // Optimize the registry to not have empty arrays for functions.
     foreach ($cache as $hook => $info) {
         if (empty($info['preprocess functions'])) {
             unset($cache[$hook]['preprocess functions']);
         }
     }
     $this->registry[$this->theme->getName()] = $cache;
     return $this->registry[$this->theme->getName()];
 }
Пример #30
0
 /**
  * See the docs for ::render().
  */
 protected function doRender(&$elements, $is_root_call = FALSE)
 {
     if (empty($elements)) {
         return '';
     }
     if (!isset($elements['#access']) && isset($elements['#access_callback'])) {
         if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) {
             $elements['#access_callback'] = $this->controllerResolver->getControllerFromDefinition($elements['#access_callback']);
         }
         $elements['#access'] = call_user_func($elements['#access_callback'], $elements);
     }
     // Early-return nothing if user does not have access.
     if (isset($elements['#access'])) {
         // If #access is an AccessResultInterface object, we must apply it's
         // cacheability metadata to the render array.
         if ($elements['#access'] instanceof AccessResultInterface) {
             $this->addCacheableDependency($elements, $elements['#access']);
             if (!$elements['#access']->isAllowed()) {
                 return '';
             }
         } elseif ($elements['#access'] === FALSE) {
             return '';
         }
     }
     // Do not print elements twice.
     if (!empty($elements['#printed'])) {
         return '';
     }
     $context = $this->getCurrentRenderContext();
     if (!isset($context)) {
         throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.");
     }
     $context->push(new BubbleableMetadata());
     // Set the bubbleable rendering metadata that has configurable defaults, if:
     // - this is the root call, to ensure that the final render array definitely
     //   has these configurable defaults, even when no subtree is render cached.
     // - this is a render cacheable subtree, to ensure that the cached data has
     //   the configurable defaults (which may affect the ID and invalidation).
     if ($is_root_call || isset($elements['#cache']['keys'])) {
         $required_cache_contexts = $this->rendererConfig['required_cache_contexts'];
         if (isset($elements['#cache']['contexts'])) {
             $elements['#cache']['contexts'] = Cache::mergeContexts($elements['#cache']['contexts'], $required_cache_contexts);
         } else {
             $elements['#cache']['contexts'] = $required_cache_contexts;
         }
     }
     // Try to fetch the prerendered element from cache, replace any placeholders
     // and return the final markup.
     if (isset($elements['#cache']['keys'])) {
         $cached_element = $this->renderCache->get($elements);
         if ($cached_element !== FALSE) {
             $elements = $cached_element;
             // Only when we're in a root (non-recursive) Renderer::render() call,
             // placeholders must be processed, to prevent breaking the render cache
             // in case of nested elements with #cache set.
             if ($is_root_call) {
                 $this->replacePlaceholders($elements);
             }
             // Mark the element markup as safe if is it a string.
             if (is_string($elements['#markup'])) {
                 $elements['#markup'] = SafeString::create($elements['#markup']);
             }
             // The render cache item contains all the bubbleable rendering metadata
             // for the subtree.
             $context->update($elements);
             // Render cache hit, so rendering is finished, all necessary info
             // collected!
             $context->bubble();
             return $elements['#markup'];
         }
     }
     // Two-tier caching: track pre-bubbling elements' #cache for later
     // comparison.
     // @see \Drupal\Core\Render\RenderCacheInterface::get()
     // @see \Drupal\Core\Render\RenderCacheInterface::set()
     $pre_bubbling_elements = [];
     $pre_bubbling_elements['#cache'] = isset($elements['#cache']) ? $elements['#cache'] : [];
     // If the default values for this element have not been loaded yet, populate
     // them.
     if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
         $elements += $this->elementInfo->getInfo($elements['#type']);
     }
     // First validate the usage of #lazy_builder; both of the next if-statements
     // use it if available.
     if (isset($elements['#lazy_builder'])) {
         // @todo Convert to assertions once https://www.drupal.org/node/2408013
         //   lands.
         if (!is_array($elements['#lazy_builder'])) {
             throw new \DomainException('The #lazy_builder property must have an array as a value.');
         }
         if (count($elements['#lazy_builder']) !== 2) {
             throw new \DomainException('The #lazy_builder property must have an array as a value, containing two values: the callback, and the arguments for the callback.');
         }
         if (count($elements['#lazy_builder'][1]) !== count(array_filter($elements['#lazy_builder'][1], function ($v) {
             return is_null($v) || is_scalar($v);
         }))) {
             throw new \DomainException("A #lazy_builder callback's context may only contain scalar values or NULL.");
         }
         $children = Element::children($elements);
         if ($children) {
             throw new \DomainException(sprintf('When a #lazy_builder callback is specified, no children can exist; all children must be generated by the #lazy_builder callback. You specified the following children: %s.', implode(', ', $children)));
         }
         $supported_keys = ['#lazy_builder', '#cache', '#create_placeholder', '#weight', '#printed'];
         $unsupported_keys = array_diff(array_keys($elements), $supported_keys);
         if (count($unsupported_keys)) {
             throw new \DomainException(sprintf('When a #lazy_builder callback is specified, no properties can exist; all properties must be generated by the #lazy_builder callback. You specified the following properties: %s.', implode(', ', $unsupported_keys)));
         }
     }
     // If instructed to create a placeholder, and a #lazy_builder callback is
     // present (without such a callback, it would be impossible to replace the
     // placeholder), replace the current element with a placeholder.
     if (isset($elements['#create_placeholder']) && $elements['#create_placeholder'] === TRUE) {
         if (!isset($elements['#lazy_builder'])) {
             throw new \LogicException('When #create_placeholder is set, a #lazy_builder callback must be present as well.');
         }
         $elements = $this->createPlaceholder($elements);
     }
     // Build the element if it is still empty.
     if (isset($elements['#lazy_builder'])) {
         $callable = $elements['#lazy_builder'][0];
         $args = $elements['#lazy_builder'][1];
         if (is_string($callable) && strpos($callable, '::') === FALSE) {
             $callable = $this->controllerResolver->getControllerFromDefinition($callable);
         }
         $new_elements = call_user_func_array($callable, $args);
         // Retain the original cacheability metadata, plus cache keys.
         CacheableMetadata::createFromRenderArray($elements)->merge(CacheableMetadata::createFromRenderArray($new_elements))->applyTo($new_elements);
         if (isset($elements['#cache']['keys'])) {
             $new_elements['#cache']['keys'] = $elements['#cache']['keys'];
         }
         $elements = $new_elements;
         $elements['#lazy_builder_built'] = TRUE;
     }
     // Make any final changes to the element before it is rendered. This means
     // that the $element or the children can be altered or corrected before the
     // element is rendered into the final text.
     if (isset($elements['#pre_render'])) {
         foreach ($elements['#pre_render'] as $callable) {
             if (is_string($callable) && strpos($callable, '::') === FALSE) {
                 $callable = $this->controllerResolver->getControllerFromDefinition($callable);
             }
             $elements = call_user_func($callable, $elements);
         }
     }
     // Defaults for bubbleable rendering metadata.
     $elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
     $elements['#cache']['max-age'] = isset($elements['#cache']['max-age']) ? $elements['#cache']['max-age'] : Cache::PERMANENT;
     $elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array();
     // Allow #pre_render to abort rendering.
     if (!empty($elements['#printed'])) {
         // The #printed element contains all the bubbleable rendering metadata for
         // the subtree.
         $context->update($elements);
         // #printed, so rendering is finished, all necessary info collected!
         $context->bubble();
         return '';
     }
     // Add any JavaScript state information associated with the element.
     if (!empty($elements['#states'])) {
         drupal_process_states($elements);
     }
     // Get the children of the element, sorted by weight.
     $children = Element::children($elements, TRUE);
     // Initialize this element's #children, unless a #pre_render callback
     // already preset #children.
     if (!isset($elements['#children'])) {
         $elements['#children'] = '';
     }
     if (!empty($elements['#markup'])) {
         // @todo Decide how to support non-HTML in the render API in
         //   https://www.drupal.org/node/2501313.
         $elements['#markup'] = $this->xssFilterAdminIfUnsafe($elements['#markup']);
     }
     // Assume that if #theme is set it represents an implemented hook.
     $theme_is_implemented = isset($elements['#theme']);
     // Check the elements for insecure HTML and pass through sanitization.
     if (isset($elements)) {
         $markup_keys = array('#description', '#field_prefix', '#field_suffix');
         foreach ($markup_keys as $key) {
             if (!empty($elements[$key]) && is_scalar($elements[$key])) {
                 $elements[$key] = $this->xssFilterAdminIfUnsafe($elements[$key]);
             }
         }
     }
     // Call the element's #theme function if it is set. Then any children of the
     // element have to be rendered there. If the internal #render_children
     // property is set, do not call the #theme function to prevent infinite
     // recursion.
     if ($theme_is_implemented && !isset($elements['#render_children'])) {
         $elements['#children'] = $this->theme->render($elements['#theme'], $elements);
         // If ThemeManagerInterface::render() returns FALSE this means that the
         // hook in #theme was not found in the registry and so we need to update
         // our flag accordingly. This is common for theme suggestions.
         $theme_is_implemented = $elements['#children'] !== FALSE;
     }
     // If #theme is not implemented or #render_children is set and the element
     // has an empty #children attribute, render the children now. This is the
     // same process as Renderer::render() but is inlined for speed.
     if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
         foreach ($children as $key) {
             $elements['#children'] .= $this->doRender($elements[$key]);
         }
         $elements['#children'] = SafeString::create($elements['#children']);
     }
     // If #theme is not implemented and the element has raw #markup as a
     // fallback, prepend the content in #markup to #children. In this case
     // #children will contain whatever is provided by #pre_render prepended to
     // what is rendered recursively above. If #theme is implemented then it is
     // the responsibility of that theme implementation to render #markup if
     // required. Eventually #theme_wrappers will expect both #markup and
     // #children to be a single string as #children.
     if (!$theme_is_implemented && isset($elements['#markup'])) {
         $elements['#children'] = SafeString::create($elements['#markup'] . $elements['#children']);
     }
     // Let the theme functions in #theme_wrappers add markup around the rendered
     // children.
     // #states and #attached have to be processed before #theme_wrappers,
     // because the #type 'page' render array from drupal_prepare_page() would
     // render the $page and wrap it into the html.html.twig template without the
     // attached assets otherwise.
     // If the internal #render_children property is set, do not call the
     // #theme_wrappers function(s) to prevent infinite recursion.
     if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) {
         foreach ($elements['#theme_wrappers'] as $key => $value) {
             // If the value of a #theme_wrappers item is an array then the theme
             // hook is found in the key of the item and the value contains attribute
             // overrides. Attribute overrides replace key/value pairs in $elements
             // for only this ThemeManagerInterface::render() call. This allows
             // #theme hooks and #theme_wrappers hooks to share variable names
             // without conflict or ambiguity.
             $wrapper_elements = $elements;
             if (is_string($key)) {
                 $wrapper_hook = $key;
                 foreach ($value as $attribute => $override) {
                     $wrapper_elements[$attribute] = $override;
                 }
             } else {
                 $wrapper_hook = $value;
             }
             $elements['#children'] = $this->theme->render($wrapper_hook, $wrapper_elements);
         }
     }
     // Filter the outputted content and make any last changes before the content
     // is sent to the browser. The changes are made on $content which allows the
     // outputted text to be filtered.
     if (isset($elements['#post_render'])) {
         foreach ($elements['#post_render'] as $callable) {
             if (is_string($callable) && strpos($callable, '::') === FALSE) {
                 $callable = $this->controllerResolver->getControllerFromDefinition($callable);
             }
             $elements['#children'] = call_user_func($callable, $elements['#children'], $elements);
         }
     }
     // We store the resulting output in $elements['#markup'], to be consistent
     // with how render cached output gets stored. This ensures that placeholder
     // replacement logic gets the same data to work with, no matter if #cache is
     // disabled, #cache is enabled, there is a cache hit or miss.
     $prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : '';
     $suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : '';
     $elements['#markup'] = $prefix . $elements['#children'] . $suffix;
     // We've rendered this element (and its subtree!), now update the context.
     $context->update($elements);
     // Cache the processed element if both $pre_bubbling_elements and $elements
     // have the metadata necessary to generate a cache ID.
     if (isset($pre_bubbling_elements['#cache']['keys']) && isset($elements['#cache']['keys'])) {
         if ($pre_bubbling_elements['#cache']['keys'] !== $elements['#cache']['keys']) {
             throw new \LogicException('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.');
         }
         $this->renderCache->set($elements, $pre_bubbling_elements);
     }
     // Only when we're in a root (non-recursive) Renderer::render() call,
     // placeholders must be processed, to prevent breaking the render cache in
     // case of nested elements with #cache set.
     //
     // By running them here, we ensure that:
     // - they run when #cache is disabled,
     // - they run when #cache is enabled and there is a cache miss.
     // Only the case of a cache hit when #cache is enabled, is not handled here,
     // that is handled earlier in Renderer::render().
     if ($is_root_call) {
         $this->replacePlaceholders($elements);
         // @todo remove as part of https://www.drupal.org/node/2511330.
         if ($context->count() !== 1) {
             throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
         }
     }
     // Rendering is finished, all necessary info collected!
     $context->bubble();
     $elements['#printed'] = TRUE;
     return SafeString::create($elements['#markup']);
 }