Example #1
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()];
 }
Example #2
0
 /**
  * Completes the theme registry adding discovered functions and hooks.
  *
  * @param array $cache
  *   The theme registry as documented in
  *   \Drupal\Core\Theme\Registry::processExtension().
  * @param \Drupal\Core\Theme\ActiveTheme $theme
  *   Current active theme.
  *
  * @see ::processExtension()
  */
 protected function postProcessExtension(array &$cache, ActiveTheme $theme)
 {
     $grouped_functions = $this->getPrefixGroupedUserFunctions();
     // Gather prefixes. This will be used to limit the found functions to the
     // expected naming conventions.
     $prefixes = array_keys((array) $this->moduleHandler->getModuleList());
     foreach (array_reverse($theme->getBaseThemes()) as $base) {
         $prefixes[] = $base->getName();
     }
     if ($theme->getEngine()) {
         $prefixes[] = $theme->getEngine() . '_engine';
     }
     $prefixes[] = $theme->getName();
     // Collect all variable preprocess functions in the correct order.
     $suggestion_level = [];
     $matches = [];
     // Look for functions named according to the pattern and add them if they
     // have matching hooks in the registry.
     foreach ($prefixes as $prefix) {
         // Grep only the functions which are within the prefix group.
         list($first_prefix, ) = explode('_', $prefix, 2);
         if (!isset($grouped_functions[$first_prefix])) {
             continue;
         }
         // Add the function and the name of the associated theme hook to the list
         // of preprocess functions grouped by suggestion specificity if a matching
         // base hook is found.
         foreach ($grouped_functions[$first_prefix] as $candidate) {
             if (preg_match("/^{$prefix}_preprocess_(((?:[^_]++|_(?!_))+)__.*)/", $candidate, $matches)) {
                 if (isset($cache[$matches[2]])) {
                     $level = substr_count($matches[1], '__');
                     $suggestion_level[$level][$candidate] = $matches[1];
                 }
             }
         }
     }
     // Add missing variable preprocessors. This is needed for modules that do
     // not explicitly register the hook. For example, when a theme contains a
     // variable preprocess function but it does not implement a template, it
     // will go missing. This will add the expected function. It also allows
     // modules or themes to have a variable process function based on a pattern
     // even if the hook does not exist.
     ksort($suggestion_level);
     foreach ($suggestion_level as $level => $item) {
         foreach ($item as $preprocessor => $hook) {
             if (isset($cache[$hook]['preprocess functions']) && !in_array($hook, $cache[$hook]['preprocess functions'])) {
                 // Add missing preprocessor to existing hook.
                 $cache[$hook]['preprocess functions'][] = $preprocessor;
             } elseif (!isset($cache[$hook]) && strpos($hook, '__')) {
                 // Process non-existing hook and register it.
                 // Look for a previously defined hook that is either a less specific
                 // suggestion hook or the base hook.
                 $this->completeSuggestion($hook, $cache);
                 $cache[$hook]['preprocess functions'][] = $preprocessor;
             }
         }
     }
     // Inherit all base hook variable preprocess functions into suggestion
     // hooks. This ensures that derivative hooks have a complete set of variable
     // preprocess functions.
     foreach ($cache as $hook => $info) {
         // The 'base hook' is only applied to derivative hooks already registered
         // from a pattern. This is typically set from
         // drupal_find_theme_functions() and drupal_find_theme_templates().
         if (isset($info['incomplete preprocess functions'])) {
             $this->completeSuggestion($hook, $cache);
             unset($cache[$hook]['incomplete preprocess functions']);
         }
         // Optimize the registry.
         if (isset($cache[$hook]['preprocess functions']) && empty($cache[$hook]['preprocess functions'])) {
             unset($cache[$hook]['preprocess functions']);
         }
         // Ensure uniqueness.
         if (isset($cache[$hook]['preprocess functions'])) {
             $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
         }
     }
 }
Example #3
0
 /**
  * {@inheritdoc}
  *
  * @todo Should we cache some of these information?
  */
 public function alterForTheme(ActiveTheme $theme, $type, &$data, &$context1 = NULL, &$context2 = NULL)
 {
     // Most of the time, $type is passed as a string, so for performance,
     // normalize it to that. When passed as an array, usually the first item in
     // the array is a generic type, and additional items in the array are more
     // specific variants of it, as in the case of array('form', 'form_FORM_ID').
     if (is_array($type)) {
         $extra_types = $type;
         $type = array_shift($extra_types);
         // Allow if statements in this function to use the faster isset() rather
         // than !empty() both when $type is passed as a string, or as an array with
         // one item.
         if (empty($extra_types)) {
             unset($extra_types);
         }
     }
     $theme_keys = array();
     foreach ($theme->getBaseThemes() as $base) {
         $theme_keys[] = $base->getName();
     }
     $theme_keys[] = $theme->getName();
     $functions = array();
     foreach ($theme_keys as $theme_key) {
         $function = $theme_key . '_' . $type . '_alter';
         if (function_exists($function)) {
             $functions[] = $function;
         }
         if (isset($extra_types)) {
             foreach ($extra_types as $extra_type) {
                 $function = $theme_key . '_' . $extra_type . '_alter';
                 if (function_exists($function)) {
                     $functions[] = $function;
                 }
             }
         }
     }
     foreach ($functions as $function) {
         $function($data, $context1, $context2);
     }
 }
 /**
  * {@inheritdoc}
  */
 public function loadActiveTheme(ActiveTheme $active_theme)
 {
     // Initialize the theme.
     if ($theme_engine = $active_theme->getEngine()) {
         // Include the engine.
         include_once DRUPAL_ROOT . '/' . $active_theme->getOwner();
         if (function_exists($theme_engine . '_init')) {
             foreach ($active_theme->getBaseThemes() as $base) {
                 call_user_func($theme_engine . '_init', $base->getExtension());
             }
             call_user_func($theme_engine . '_init', $active_theme->getExtension());
         }
     } else {
         // include non-engine theme files
         foreach ($active_theme->getBaseThemes() as $base) {
             // Include the theme file or the engine.
             if ($base->getOwner()) {
                 include_once DRUPAL_ROOT . '/' . $base->getOwner();
             }
         }
         // and our theme gets one too.
         if ($active_theme->getOwner()) {
             include_once DRUPAL_ROOT . '/' . $active_theme->getOwner();
         }
     }
     // Always include Twig as the default theme engine.
     include_once DRUPAL_ROOT . '/core/themes/engines/twig/twig.engine';
 }
Example #5
0
 /**
  * Ensures the phase functions are invoked in the correct order.
  *
  * @param array $functions
  *   The phase functions to iterate over.
  * @param string $hook
  *   The current hook being processed.
  * @param \Drupal\Core\Theme\ActiveTheme $theme
  *   Current active theme.
  *
  * @see https://www.drupal.org/node/2098551
  */
 protected function sortFunctions(array &$functions, $hook, ActiveTheme $theme)
 {
     // Immediately return if there is nothing to sort.
     if (count($functions) < 2) {
         return;
     }
     $themes = array_keys($theme->getBaseThemes());
     $themes[] = $theme->getName();
     // Create an associative array of theme functions to ensure sort order.
     $theme_functions = array_fill_keys($themes, array());
     // Iterate over all the themes.
     foreach ($themes as $theme) {
         // Only add the function to the array of theme functions if it currently
         // exists in the $functions array.
         $function = $theme . '_preprocess_' . $hook;
         $key = array_search($function, $functions);
         if ($key !== FALSE) {
             // Save the theme function to be added later, but sorted.
             $theme_functions[$theme][] = $function;
             // Remove it from the current $functions array.
             unset($functions[$key]);
         }
     }
     // Iterate over all the captured theme functions and place them back into
     // the phase functions array.
     foreach ($theme_functions as $array) {
         $functions = array_merge($functions, $array);
     }
 }