protected function setUp() { parent::setUp(); // Set up three memory backends to be used in the chain. $this->firstBackend = new MemoryBackend('foo'); $this->secondBackend = new MemoryBackend('bar'); $this->thirdBackend = new MemoryBackend('baz'); // Set an initial fixed dataset for all testing. The next three data // collections will test two edge cases (last backend has the data, and // first backend has the data) and will test a normal use case (middle // backend has the data). We should have a complete unit test with those. // Note that in all cases, when the same key is set on more than one // backend, the values are voluntarily different, this ensures in which // backend we actually fetched the key when doing get calls. // Set a key present on all backends (for delete). $this->firstBackend->set('t123', 1231); $this->secondBackend->set('t123', 1232); $this->thirdBackend->set('t123', 1233); // Set a key present on the second and the third (for get), those two will // be different, this will ensure from where we get the key. $this->secondBackend->set('t23', 232); $this->thirdBackend->set('t23', 233); // Set a key on only the third, we will ensure propagation using this one. $this->thirdBackend->set('t3', 33); // Create the chain. $this->chain = new BackendChain('foobarbaz'); $this->chain->appendBackend($this->firstBackend)->appendBackend($this->secondBackend)->appendBackend($this->thirdBackend); }
/** * {@inheritdoc} * * Cached by role, invalidated whenever permissions change. */ public function generate(AccountInterface $account) { // User 1 is the super user, and can always access all permissions. Use a // different, unique identifier for the hash. if ($account->id() == 1) { return $this->hash('is-super-user'); } $sorted_roles = $account->getRoles(); sort($sorted_roles); $role_list = implode(',', $sorted_roles); $cid = "user_permissions_hash:{$role_list}"; if ($static_cache = $this->static->get($cid)) { return $static_cache->data; } else { $tags = Cache::buildTags('config:user.role', $sorted_roles, '.'); if ($cache = $this->cache->get($cid)) { $permissions_hash = $cache->data; } else { $permissions_hash = $this->doGenerate($sorted_roles); $this->cache->set($cid, $permissions_hash, Cache::PERMANENT, $tags); } $this->static->set($cid, $permissions_hash, Cache::PERMANENT, $tags); } return $permissions_hash; }
/** * User object. * * @return \Drupal\moodle\Sql\User */ public function user() { // Static cache of already retrieved user data. $data =& drupal_static(__METHOD__, array()); $user_cid = "moodle-user:{$this->user->id()}"; // If we do not have this user id in the static cache, check {cache_data}. if (!isset($data[$user_cid])) { $cache = $this->cacheBackend->get($user_cid); if ($cache && $cache->data && isset($cache->data[$user_cid])) { $data[$user_cid] = $cache->data[$user_cid]; } } // If nothing in the cache then retrieve it from the database. if (!isset($data[$user_cid])) { $user = new User(); $this->query(); $this->addFields(); $statement = $this->query->execute(); $statement->setFetchMode(\PDO::FETCH_INTO, $user); $data[$user_cid] = $statement->fetch(); // Store the results for a day. $this->cacheBackend->set($user_cid, $data, REQUEST_TIME + 86400); } return $data[$user_cid]; }
/** * {@inheritdoc} * * Cached by role, invalidated whenever permissions change. */ public function generate(AccountInterface $account) { $sorted_roles = $account->getRoles(); sort($sorted_roles); $role_list = implode(',', $sorted_roles); if ($cache = $this->cache->get("user_permissions_hash:{$role_list}")) { $permissions_hash = $cache->data; } else { $permissions_hash = $this->doGenerate($sorted_roles); $this->cache->set("user_permissions_hash:{$role_list}", $permissions_hash, Cache::PERMANENT, array('user_role' => $sorted_roles)); } return $permissions_hash; }
/** * Loads the base country definitions. * * @return array */ protected function loadBaseDefinitions() { if (!empty($this->baseDefinitions)) { return $this->baseDefinitions; } $cache_key = 'address.countries.base'; if ($cached = $this->cache->get($cache_key)) { $this->baseDefinitions = $cached->data; } else { $this->baseDefinitions = json_decode(file_get_contents($this->definitionPath . 'base.json'), TRUE); $this->cache->set($cache_key, $this->baseDefinitions, CacheBackendInterface::CACHE_PERMANENT, ['countries']); } return $this->baseDefinitions; }
/** * {@inheritdoc} * * Cached by role, invalidated whenever permissions change. */ public function generate(AccountInterface $account) { $sorted_roles = $account->getRoles(); sort($sorted_roles); $role_list = implode(',', $sorted_roles); if ($cache = $this->cache->get("user_permissions_hash:{$role_list}")) { $permissions_hash = $cache->data; } else { $permissions_hash = $this->doGenerate($sorted_roles); $tags = Cache::buildTags('config:user.role', $sorted_roles, '.'); $this->cache->set("user_permissions_hash:{$role_list}", $permissions_hash, Cache::PERMANENT, $tags); } return $permissions_hash; }
/** * Sets a cache of plugin definitions for the decorated discovery class. * * @param array $definitions * List of definitions to store in cache. */ protected function setCachedDefinitions($definitions) { if ($this->cacheBackend) { $this->cacheBackend->set($this->cacheKey, $definitions, Cache::PERMANENT, $this->cacheTags); } $this->definitions = $definitions; }
/** * {@inheritdoc} */ public function getLineItemTypeId($product_type_id) { if (!isset($this->map)) { if ($cached_map = $this->cache->get('commerce_product.line_item_type_map')) { $this->map = $cached_map->data; } else { $this->map = $this->buildMap(); $this->cache->set('commerce_product.line_item_type_map', $this->map); } } // A valid product type ID should always have a matching line item type ID. if (empty($this->map[$product_type_id])) { throw new \InvalidArgumentException(sprintf('No line item type found for the "%s" product type.', $product_type_id)); } return $this->map[$product_type_id]; }
/** * Responds after a request has finished, but before it is sent to the client. * * @param \Guzzle\Common\Event $event * The Guzzle event object. */ public function onRequestSent(Event $event) { $request = $event['request']; $response = $event['response']; // Handle permanent redirects by setting the redirected URL so that the // client can grab it quickly. $redirect = FALSE; $url = $old_url = $request->getUrl(); if ($previous_response = $response->getPreviousResponse()) { if ($previous_response->getStatusCode() == 301 && ($location = $previous_response->getLocation())) { $response->getParams()->set('feeds.redirect', $location); $redirect = TRUE; $url = $request->getUrl(); } } $cache_hit = $response->getStatusCode() == 304; if ($redirect) { // Delete the old cache entry. $this->cacheBackend->delete($this->getCacheKey($old_url)); // Not sure if the repeated requests are smart enough to find the // redirect, so cache the old URL with the new response. static::$downloadCache[$old_url] = $response; } if ($redirect || !$cache_hit) { $cache = new \stdClass(); $cache->headers = array_change_key_case($response->getHeaders()->toArray()); // @todo We should only cache for certain status codes. $cache->code = $response->getStatusCode(); $this->cacheBackend->set($this->getCacheKey($url), $cache); } // Set in-page download cache. static::$downloadCache[$url] = $response; }
/** * Tests the getAllBundleInfo() method. * * @covers ::getAllBundleInfo */ public function testGetAllBundleInfo() { $this->moduleHandler->invokeAll('entity_bundle_info')->willReturn([]); $this->moduleHandler->alter('entity_bundle_info', Argument::type('array'))->willReturn(NULL); $apple = $this->prophesize(EntityTypeInterface::class); $apple->getLabel()->willReturn('Apple'); $apple->getBundleOf()->willReturn(NULL); $banana = $this->prophesize(EntityTypeInterface::class); $banana->getLabel()->willReturn('Banana'); $banana->getBundleOf()->willReturn(NULL); $this->setUpEntityTypeDefinitions(['apple' => $apple, 'banana' => $banana]); $this->cacheBackend->get('entity_bundle_info:en')->willReturn(FALSE); $this->cacheBackend->set('entity_bundle_info:en', Argument::any(), Cache::PERMANENT, ['entity_types', 'entity_bundles'])->will(function () { $this->get('entity_bundle_info:en')->willReturn((object) ['data' => 'cached data'])->shouldBeCalled(); })->shouldBeCalled(); $this->cacheTagsInvalidator->invalidateTags(['entity_bundles'])->shouldBeCalled(); $this->typedDataManager->clearCachedDefinitions()->shouldBeCalled(); $expected = ['apple' => ['apple' => ['label' => 'Apple']], 'banana' => ['banana' => ['label' => 'Banana']]]; $bundle_info = $this->entityTypeBundleInfo->getAllBundleInfo(); $this->assertSame($expected, $bundle_info); $bundle_info = $this->entityTypeBundleInfo->getAllBundleInfo(); $this->assertSame($expected, $bundle_info); $this->entityTypeBundleInfo->clearCachedBundles(); $bundle_info = $this->entityTypeBundleInfo->getAllBundleInfo(); $this->assertSame('cached data', $bundle_info); }
/** * {@inheritdoc} */ public function write($key, $content) { $this->storage()->save($key, $content); // Save the last mtime. $cid = 'twig:' . $key; $this->cache->set($cid, REQUEST_TIME); }
/** * Builds the "format_tags" configuration part of the CKEditor JS settings. * * @see getConfig() * * @param \Drupal\editor\Entity\Editor $editor * A configured text editor object. * * @return array * An array containing the "format_tags" configuration. */ protected function generateFormatTagsSetting(Editor $editor) { // When no text format is associated yet, assume no tag is allowed. // @see \Drupal\Editor\EditorInterface::hasAssociatedFilterFormat() if (!$editor->hasAssociatedFilterFormat()) { return array(); } $format = $editor->getFilterFormat(); $cid = 'ckeditor_internal_format_tags:' . $format->id(); if ($cached = $this->cache->get($cid)) { $format_tags = $cached->data; } else { // The <p> tag is always allowed — HTML without <p> tags is nonsensical. $format_tags = ['p']; // Given the list of possible format tags, automatically determine whether // the current text format allows this tag, and thus whether it should show // up in the "Format" dropdown. $possible_format_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre']; foreach ($possible_format_tags as $tag) { $input = '<' . $tag . '>TEST</' . $tag . '>'; $output = trim(check_markup($input, $editor->id())); if ($input == $output) { $format_tags[] = $tag; } } $format_tags = implode(';', $format_tags); // Cache the "format_tags" configuration. This cache item is infinitely // valid; it only changes whenever the text format is changed, hence it's // tagged with the text format's cache tag. $this->cache->set($cid, $format_tags, Cache::PERMANENT, $format->getCacheTags()); } return $format_tags; }
/** * {@inheritdoc} */ public function loadTreeData($menu_name, MenuTreeParameters $parameters) { // Build the cache ID; sort 'expanded' and 'conditions' to prevent duplicate // cache items. sort($parameters->expandedParents); asort($parameters->conditions); $tree_cid = "tree-data:{$menu_name}:" . serialize($parameters); $cache = $this->menuCacheBackend->get($tree_cid); if ($cache && isset($cache->data)) { $data = $cache->data; // Cache the definitions in memory so they don't need to be loaded again. $this->definitions += $data['definitions']; unset($data['definitions']); } else { $links = $this->loadLinks($menu_name, $parameters); $data['tree'] = $this->doBuildTreeData($links, $parameters->activeTrail, $parameters->minDepth); $data['definitions'] = array(); $data['route_names'] = $this->collectRoutesAndDefinitions($data['tree'], $data['definitions']); $this->menuCacheBackend->set($tree_cid, $data, Cache::PERMANENT, ['config:system.menu.' . $menu_name]); // The definitions were already added to $this->definitions in // $this->doBuildTreeData() unset($data['definitions']); } return $data; }
/** * Discovers all available tests in all extensions. * * @param string $extension * (optional) The name of an extension to limit discovery to; e.g., 'node'. * * @return array * An array of tests keyed by the first @group specified in each test's * PHPDoc comment block, and then keyed by class names. For example: * @code * $groups['block'] => array( * 'Drupal\block\Tests\BlockTest' => array( * 'name' => 'Drupal\block\Tests\BlockTest', * 'description' => 'Tests block UI CRUD functionality.', * 'group' => 'block', * ), * ); * @endcode * * @throws \ReflectionException * If a discovered test class does not match the expected class name. * * @todo Remove singular grouping; retain list of groups in 'group' key. * @see https://www.drupal.org/node/2296615 * @todo Add base class groups 'Kernel' + 'Web', complementing 'PHPUnit'. */ public function getTestClasses($extension = NULL) { if (!isset($extension)) { if ($this->cacheBackend && ($cache = $this->cacheBackend->get('simpletest:discovery:classes'))) { return $cache->data; } } $list = array(); $classmap = $this->findAllClassFiles($extension); // Prevent expensive class loader lookups for each reflected test class by // registering the complete classmap of test classes to the class loader. // This also ensures that test classes are loaded from the discovered // pathnames; a namespace/classname mismatch will throw an exception. $this->classLoader->addClassMap($classmap); foreach ($classmap as $classname => $pathname) { try { $class = new \ReflectionClass($classname); } catch (\ReflectionException $e) { // Re-throw with expected pathname. $message = $e->getMessage() . " in expected {$pathname}"; throw new \ReflectionException($message, $e->getCode(), $e); } // Skip interfaces, abstract classes, and traits. if (!$class->isInstantiable()) { continue; } // Skip non-test classes. if (!$class->isSubclassOf('Drupal\\simpletest\\TestBase') && !$class->isSubclassOf('PHPUnit_Framework_TestCase')) { continue; } $info = static::getTestInfo($class); // Skip this test class if it requires unavailable modules. // @todo PHPUnit skips tests with unmet requirements when executing a test // (instead of excluding them upfront). Refactor test runner to follow // that approach. // @see https://www.drupal.org/node/1273478 if (!empty($info['requires']['module'])) { if (array_diff($info['requires']['module'], $this->availableExtensions['module'])) { continue; } } $list[$info['group']][$classname] = $info; } // Sort the groups and tests within the groups by name. uksort($list, 'strnatcasecmp'); foreach ($list as &$tests) { uksort($tests, 'strnatcasecmp'); } // Allow modules extending core tests to disable originals. \Drupal::moduleHandler()->alter('simpletest', $list); if (!isset($extension)) { if ($this->cacheBackend) { $this->cacheBackend->set('simpletest:discovery:classes', $list); } } return $list; }
/** * Compile the source and write the compiled template to disk. */ public function updateCompiledTemplate($cache_filename, $name) { $source = $this->loader->getSource($name); $compiled_source = $this->compileSource($source, $name); $this->storage()->save($cache_filename, $compiled_source); // Save the last modification time $cid = 'twig:' . $cache_filename; $this->cache_object->set($cid, REQUEST_TIME); }
/** * Discovers all available tests in all extensions. * * @param string $extension * (optional) The name of an extension to limit discovery to; e.g., 'node'. * * @return array * An array of tests keyed by the first @group specified in each test's * PHPDoc comment block, and then keyed by class names. For example: * @code * $groups['block'] => array( * 'Drupal\block\Tests\BlockTest' => array( * 'name' => 'Drupal\block\Tests\BlockTest', * 'description' => 'Tests block UI CRUD functionality.', * 'group' => 'block', * ), * ); * @endcode * * @throws \ReflectionException * If a discovered test class does not match the expected class name. * * @todo Remove singular grouping; retain list of groups in 'group' key. * @see https://www.drupal.org/node/2296615 * @todo Add base class groups 'Kernel' + 'Web', complementing 'PHPUnit'. */ public function getTestClasses($extension = NULL) { $reader = new SimpleAnnotationReader(); $reader->addNamespace('Drupal\\simpletest\\Annotation'); if (!isset($extension)) { if ($this->cacheBackend && ($cache = $this->cacheBackend->get('simpletest:discovery:classes'))) { return $cache->data; } } $list = array(); $classmap = $this->findAllClassFiles($extension); // Prevent expensive class loader lookups for each reflected test class by // registering the complete classmap of test classes to the class loader. // This also ensures that test classes are loaded from the discovered // pathnames; a namespace/classname mismatch will throw an exception. $this->classLoader->addClassMap($classmap); foreach ($classmap as $classname => $pathname) { $finder = MockFileFinder::create($pathname); $parser = new StaticReflectionParser($classname, $finder, TRUE); try { $info = static::getTestInfo($classname, $parser->getDocComment()); } catch (MissingGroupException $e) { // If the class name ends in Test and is not a migrate table dump. if (preg_match('/Test$/', $classname) && strpos($classname, 'migrate_drupal\\Tests\\Table') === FALSE) { throw $e; } // If the class is @group annotation just skip it. Most likely it is an // abstract class, trait or test fixture. continue; } // Skip this test class if it requires unavailable modules. // @todo PHPUnit skips tests with unmet requirements when executing a test // (instead of excluding them upfront). Refactor test runner to follow // that approach. // @see https://www.drupal.org/node/1273478 if (!empty($info['requires']['module'])) { if (array_diff($info['requires']['module'], $this->availableExtensions['module'])) { continue; } } $list[$info['group']][$classname] = $info; } // Sort the groups and tests within the groups by name. uksort($list, 'strnatcasecmp'); foreach ($list as &$tests) { uksort($tests, 'strnatcasecmp'); } // Allow modules extending core tests to disable originals. \Drupal::moduleHandler()->alter('simpletest', $list); if (!isset($extension)) { if ($this->cacheBackend) { $this->cacheBackend->set('simpletest:discovery:classes', $list); } } return $list; }
/** * Loads all non-admin routes right before the actual page is rendered. * * @param \Symfony\Component\HttpKernel\Event\KernelEvent $event * The event to process. */ public function onRequest(KernelEvent $event) { // Only preload on normal HTML pages, as they will display menu links. if ($this->routeProvider instanceof PreloadableRouteProviderInterface && $event->getRequest()->getRequestFormat() == 'html') { // Ensure that the state query is cached to skip the database query, if // possible. $key = 'routing.non_admin_routes'; if ($cache = $this->cache->get($key)) { $routes = $cache->data; } else { $routes = $this->state->get($key, []); $this->cache->set($key, $routes, Cache::PERMANENT, ['routes']); } if ($routes) { // Preload all the non-admin routes at once. $this->routeProvider->preLoadRoutes($routes); } } }
/** * Stores entities in the persistent cache backend. * * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities * Entities to store in the cache. */ protected function setPersistentCache($entities) { if (!$this->entityType->isPersistentlyCacheable()) { return; } $cache_tags = array($this->entityTypeId . '_values', 'entity_field_info'); foreach ($entities as $id => $entity) { $this->cacheBackend->set($this->buildCacheId($id), $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags); } }
/** * {@inheritdoc} */ public function write($name, array $data) { if ($this->storage->write($name, $data)) { // While not all written data is read back, setting the cache instead of // just deleting it avoids cache rebuild stampedes. $this->cache->set($this->getCacheKey($name), $data); $this->findByPrefixCache = array(); return TRUE; } return FALSE; }
/** * Marks the fast cache bin as outdated because of a write. */ protected function markAsOutdated() { // Clocks on a single server can drift. Multiple servers may have slightly // differing opinions about the current time. Given that, do not assume // 'now' on this server is always later than our stored timestamp. $now = microtime(TRUE); if ($now > $this->getLastWriteTimestamp()) { $this->lastWriteTimestamp = $now; $this->consistentBackend->set(self::LAST_WRITE_TIMESTAMP_PREFIX . $this->bin, $this->lastWriteTimestamp); } }
/** * {@inheritdoc} */ protected function loadDefinitions($countryCode, $parentId = NULL) { $lookup_id = $parentId ?: $countryCode; if (isset($this->definitions[$lookup_id])) { return $this->definitions[$lookup_id]; } // If there are predefined subdivisions at this level, try to load them. $this->definitions[$lookup_id] = []; if ($this->hasData($countryCode, $parentId)) { $cache_key = 'address.subdivisions.' . $lookup_id; $filename = $this->definitionPath . $lookup_id . '.json'; if ($cached = $this->cache->get($cache_key)) { $this->definitions[$lookup_id] = $cached->data; } elseif ($raw_definition = @file_get_contents($filename)) { $this->definitions[$lookup_id] = json_decode($raw_definition, TRUE); $this->cache->set($cache_key, $this->definitions[$lookup_id], CacheBackendInterface::CACHE_PERMANENT, ['subdivisions']); } } return $this->definitions[$lookup_id]; }
/** * Builds a menu tree. * * This function may be used build the data for a menu tree only, for example * to further massage the data manually before further processing happens. * MenuTree::checkAccess() needs to be invoked afterwards. * * @param string $menu_name * The name of the menu. * @param array $parameters * The parameters passed into static::buildTree() * * @see static::buildTree() */ protected function doBuildTree($menu_name, array $parameters = array()) { $language_interface = $this->languageManager->getCurrentLanguage(); // Build the cache id; sort parents to prevent duplicate storage and remove // default parameter values. if (isset($parameters['expanded'])) { sort($parameters['expanded']); } $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters)); // If we do not have this tree in the static cache, check {cache_menu}. if (!isset($this->menuTree[$tree_cid])) { $cache = $this->cache->get($tree_cid); if ($cache && $cache->data) { $this->menuTree[$tree_cid] = $cache->data; } } if (!isset($this->menuTree[$tree_cid])) { $query = $this->queryFactory->get('menu_link'); for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { $query->sort('p' . $i, 'ASC'); } $query->condition('menu_name', $menu_name); if (!empty($parameters['expanded'])) { $query->condition('plid', $parameters['expanded'], 'IN'); } elseif (!empty($parameters['only_active_trail'])) { $query->condition('mlid', $parameters['active_trail'], 'IN'); } $min_depth = isset($parameters['min_depth']) ? $parameters['min_depth'] : 1; if ($min_depth != 1) { $query->condition('depth', $min_depth, '>='); } if (isset($parameters['max_depth'])) { $query->condition('depth', $parameters['max_depth'], '<='); } // Add custom query conditions, if any were passed. if (isset($parameters['conditions'])) { foreach ($parameters['conditions'] as $column => $value) { $query->condition($column, $value); } } // Build an ordered array of links using the query result object. $links = array(); if ($result = $query->execute()) { $links = $this->menuLinkStorage->loadMultiple($result); } $active_trail = isset($parameters['active_trail']) ? $parameters['active_trail'] : array(); $tree = $this->doBuildTreeData($links, $active_trail, $min_depth); // Cache the data, if it is not already in the cache. $this->cache->set($tree_cid, $tree, Cache::PERMANENT, array('menu' => $menu_name)); $this->menuTree[$tree_cid] = $tree; } return $this->menuTree[$tree_cid]; }
/** * {@inheritdoc} */ public function get($locale, $fallback_locale = null) { $locale = $this->resolveLocale($locale, $fallback_locale); if (isset($this->numberFormats[$locale])) { return $this->numberFormats[$locale]; } // Load the definition. $cache_key = 'commerce_price.number_format.' . $locale; if ($cached = $this->cache->get($cache_key)) { $definition = $cached->data; } else { $filename = $this->definitionPath . $locale . '.json'; $definition = json_decode(file_get_contents($filename), true); $this->cache->set($cache_key, $definition, CacheBackendInterface::CACHE_PERMANENT, ['number_formats']); } // Instantiate and alter the number format. $number_format = $this->createNumberFormatFromDefinition($definition, $locale); $event = new NumberFormatEvent($number_format); $this->eventDispatcher->dispatch(PriceEvents::NUMBER_FORMAT_LOAD, $event); $this->numberFormats[$locale] = $number_format; return $this->numberFormats[$locale]; }
/** * 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 = $cache; return $this->registry; }
/** * Finds configuration object names starting with a given prefix. * * Given the following configuration objects: * - node.type.article * - node.type.page * * Passing the prefix 'node.type.' will return an array containing the above * names. * * @param string $prefix * The prefix to search for * * @return array * An array containing matching configuration object names. */ protected function findByPrefix($prefix) { if (!isset($this->findByPrefixCache[$prefix])) { // The : character is not allowed in config file names, so this can not // conflict. if ($cache = $this->cache->get('find:' . $prefix)) { $this->findByPrefixCache[$prefix] = $cache->data; } else { $this->findByPrefixCache[$prefix] = $this->storage->listAll($prefix); $this->cache->set('find:' . $prefix, $this->findByPrefixCache[$prefix], Cache::PERMANENT, array($this::FIND_BY_PREFIX_CACHE_TAG => TRUE)); } } return $this->findByPrefixCache[$prefix]; }
/** * {@inheritdoc} */ public function getPunctuationCharacters() { if (empty($this->punctuationCharacters)) { $langcode = $this->languageManager->getCurrentLanguage()->getId(); $cid = 'pathauto:punctuation:' . $langcode; if ($cache = $this->cacheBackend->get($cid)) { $this->punctuationCharacters = $cache->data; } else { $punctuation = array(); $punctuation['double_quotes'] = array('value' => '"', 'name' => t('Double quotation marks')); $punctuation['quotes'] = array('value' => '\'', 'name' => t("Single quotation marks (apostrophe)")); $punctuation['backtick'] = array('value' => '`', 'name' => t('Back tick')); $punctuation['comma'] = array('value' => ',', 'name' => t('Comma')); $punctuation['period'] = array('value' => '.', 'name' => t('Period')); $punctuation['hyphen'] = array('value' => '-', 'name' => t('Hyphen')); $punctuation['underscore'] = array('value' => '_', 'name' => t('Underscore')); $punctuation['colon'] = array('value' => ':', 'name' => t('Colon')); $punctuation['semicolon'] = array('value' => ';', 'name' => t('Semicolon')); $punctuation['pipe'] = array('value' => '|', 'name' => t('Vertical bar (pipe)')); $punctuation['left_curly'] = array('value' => '{', 'name' => t('Left curly bracket')); $punctuation['left_square'] = array('value' => '[', 'name' => t('Left square bracket')); $punctuation['right_curly'] = array('value' => '}', 'name' => t('Right curly bracket')); $punctuation['right_square'] = array('value' => ']', 'name' => t('Right square bracket')); $punctuation['plus'] = array('value' => '+', 'name' => t('Plus sign')); $punctuation['equal'] = array('value' => '=', 'name' => t('Equal sign')); $punctuation['asterisk'] = array('value' => '*', 'name' => t('Asterisk')); $punctuation['ampersand'] = array('value' => '&', 'name' => t('Ampersand')); $punctuation['percent'] = array('value' => '%', 'name' => t('Percent sign')); $punctuation['caret'] = array('value' => '^', 'name' => t('Caret')); $punctuation['dollar'] = array('value' => '$', 'name' => t('Dollar sign')); $punctuation['hash'] = array('value' => '#', 'name' => t('Number sign (pound sign, hash)')); $punctuation['at'] = array('value' => '@', 'name' => t('At sign')); $punctuation['exclamation'] = array('value' => '!', 'name' => t('Exclamation mark')); $punctuation['tilde'] = array('value' => '~', 'name' => t('Tilde')); $punctuation['left_parenthesis'] = array('value' => '(', 'name' => t('Left parenthesis')); $punctuation['right_parenthesis'] = array('value' => ')', 'name' => t('Right parenthesis')); $punctuation['question_mark'] = array('value' => '?', 'name' => t('Question mark')); $punctuation['less_than'] = array('value' => '<', 'name' => t('Less-than sign')); $punctuation['greater_than'] = array('value' => '>', 'name' => t('Greater-than sign')); $punctuation['slash'] = array('value' => '/', 'name' => t('Slash')); $punctuation['back_slash'] = array('value' => '\\', 'name' => t('Backslash')); // Allow modules to alter the punctuation list and cache the result. $this->moduleHandler->alter('pathauto_punctuation_chars', $punctuation); $this->cacheBackend->set($cid, $punctuation); $this->punctuationCharacters = $punctuation; } } return $this->punctuationCharacters; }
/** * Returns metadata describing supported tokens. * * The metadata array contains token type, name, and description data as well * as an optional pointer indicating that the token chains to another set of * tokens. * * @return array * An associative array of token information, grouped by token type. The * array structure is identical to that of hook_token_info(). * * @see hook_token_info() */ public function getInfo() { if (is_null($this->tokenInfo)) { $cache_id = 'token_info:' . $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); $cache = $this->cache->get($cache_id); if ($cache) { $this->tokenInfo = $cache->data; } else { $this->tokenInfo = $this->moduleHandler->invokeAll('token_info'); $this->moduleHandler->alter('token_info', $this->tokenInfo); $this->cache->set($cache_id, $this->tokenInfo, CacheBackendInterface::CACHE_PERMANENT, array(static::TOKEN_INFO_CACHE_TAG)); } } return $this->tokenInfo; }
/** * Marks the fast cache bin as outdated because of a write. */ protected function markAsOutdated() { // Clocks on a single server can drift. Multiple servers may have slightly // differing opinions about the current time. Given that, do not assume // 'now' on this server is always later than our stored timestamp. // Also add 1 millisecond, to ensure that caches written earlier in the same // millisecond are invalidated. It is possible that caches will be later in // the same millisecond and are then incorrectly invalidated, but that only // costs one additional roundtrip to the persistent cache. $now = round(microtime(TRUE) + 0.001, 3); if ($now > $this->getLastWriteTimestamp()) { $this->lastWriteTimestamp = $now; $this->consistentBackend->set(self::LAST_WRITE_TIMESTAMP_PREFIX . $this->bin, $this->lastWriteTimestamp); } }
/** * {@inheritdoc} */ public function buildTree($token_type, array $options = []) { $options += ['restricted' => FALSE, 'depth' => 4, 'data' => [], 'values' => FALSE, 'flat' => FALSE]; // Do not allow past the maximum token information depth. $options['depth'] = min($options['depth'], static::MAX_DEPTH); // If $token_type is an entity, make sure we are using the actual token type. if ($entity_token_type = $this->entityMapper->getTokenTypeForEntityType($token_type)) { $token_type = $entity_token_type; } $langcode = $this->languageManager->getCurrentLanguage()->getId(); $tree_cid = "token_tree:{$token_type}:{$langcode}:{$options['depth']}"; // If we do not have this base tree in the static cache, check the cache // otherwise generate and store it in the cache. if (!isset($this->builtTrees[$tree_cid])) { if ($cache = $this->cacheBackend->get($tree_cid)) { $this->builtTrees[$tree_cid] = $cache->data; } else { $options['parents'] = []; $this->builtTrees[$tree_cid] = $this->getTokenData($token_type, $options); $this->cacheBackend->set($tree_cid, $this->builtTrees[$tree_cid], Cache::PERMANENT, [Token::TOKEN_INFO_CACHE_TAG]); } } $tree = $this->builtTrees[$tree_cid]; // If the user has requested a flat tree, convert it. if (!empty($options['flat'])) { $tree = $this->flattenTree($tree); } // Fill in token values. if (!empty($options['values'])) { $token_values = []; foreach ($tree as $token => $token_info) { if (!empty($token_info['dynamic']) || !empty($token_info['restricted'])) { continue; } elseif (!isset($token_info['value'])) { $token_values[$token_info['token']] = $token; } } if (!empty($token_values)) { $token_values = $this->tokenService->generate($token_type, $token_values, $options['data'], [], new BubbleableMetadata()); foreach ($token_values as $token => $replacement) { $tree[$token]['value'] = $replacement; } } } return $tree; }
/** * {@inheritdoc} */ public function getActiveThemeByName($theme_name) { if ($cached = $this->cache->get('theme.active_theme.' . $theme_name)) { return $cached->data; } $themes = $this->themeHandler->listInfo(); // If no theme could be negotiated, or if the negotiated theme is not within // the list of installed themes, fall back to the default theme output of // core and modules (like Stark, but without a theme extension at all). This // is possible, because loadActiveTheme() always loads the Twig theme // engine. This is desired, because missing or malformed theme configuration // should not leave the application in a broken state. By falling back to // default output, the user is able to reconfigure the theme through the UI. // Lastly, tests are expected to operate with no theme by default, so as to // only assert the original theme output of modules (unless a test manually // installs a specific theme). if (empty($themes) || !$theme_name || !isset($themes[$theme_name])) { $theme_name = 'core'; // /core/core.info.yml does not actually exist, but is required because // Extension expects a pathname. $active_theme = $this->getActiveTheme(new Extension($this->root, 'theme', 'core/core.info.yml')); // Early-return and do not set state, because the initialized $theme_name // differs from the original $theme_name. return $active_theme; } // Find all our ancestor themes and put them in an array. $base_themes = array(); $ancestor = $theme_name; while ($ancestor && isset($themes[$ancestor]->base_theme)) { $ancestor = $themes[$ancestor]->base_theme; if (!$this->themeHandler->themeExists($ancestor)) { if ($ancestor == 'stable') { // Themes that depend on Stable will be fixed by system_update_8014(). // There is no harm in not adding it as an ancestor since at worst // some people might experience slight visual regressions on // update.php. continue; } throw new MissingThemeDependencyException(sprintf('Base theme %s has not been installed.', $ancestor), $ancestor); } $base_themes[] = $themes[$ancestor]; } $active_theme = $this->getActiveTheme($themes[$theme_name], $base_themes); $this->cache->set('theme.active_theme.' . $theme_name, $active_theme); return $active_theme; }